class

Analyzer::Crystal::CrystalEngine

Inherits FileScanEngine < Analyzer < FileHelper < Reference < Object

Constants

CLOSES_BLOCK_RE = /^end\b/
DEF_DECL_RE = /^(?:(?:private|protected)\s+)?def\s+(?:self\.)?([A-Za-z_]\w*[!?=]?)/
DO_WORD_RE = /\bdo\b/

Precompiled block-structure probes used by extract_crystal_do_block / extract_crystal_def_block on every body line. Constant shapes — load once, match many times. Cheap includes? gates reject the common non-structural body lines before any PCRE2 scan.

END_WORD_RE = /\bend\b/
HEREDOC_OPEN = /(?<!['"])<<-['"]?([A-Z_]\w*)/

Opening token of a Crystal heredoc: <<-DELIM, optionally quoted (<<-'SQL'). The delimiter must start with an uppercase letter or underscore — the universal Crystal convention (MD/HTML/SQL/EOF/…) — which keeps the matcher from firing on arr << -value. The negative lookbehind rejects a <<- that sits inside a string literal ("<<-MD"), so a stray quoted token can't open a phantom heredoc and blank the rest of the file.

INTERPOLATION_RE = /\#\{([^}]+)\}/

Crystal "…" strings interpolate #{expr}. The Kemal/Lucky/ Amber/Marten/Grip route extractors capture the literal characters between quotes, so get "/api/#{VERSION}/items" came out as /api/#{VERSION}/items with the #{VERSION} leaking into the URL. Rewrite it as {name} so the path- parameter extractor picks it up and the URL template reads cleanly. Mirrors the Python f-string, Ruby #{}, and PHP $var fixes earlier this pass.

Most route literals have no interpolation; skip the PCRE2 gsub when the #\{ marker is absent.

NAMESPACE_DECL_RE = /^(?:abstract\s+)?(?:module|class|struct)\s+([A-Z]\w*(?:::[A-Z]\w*)*)/

Track the enclosing module/class by indentation rather than by counting do/end. Method bodies in real controllers are full of heredocs, {% … %} macros, and XML.build do … end blocks that throw off keyword-based depth counting and prematurely "close" the module, dropping every method defined after them. Indentation is the one signal those constructs can't corrupt: a namespace stays open until a line dedents to or past the column where it was declared.

Precompiled: this loop runs once per line of every .cr file when building the cross-file action index (--include-callee / --ai-context). The shapes never change, so hoist the two PCRE2 patterns to class constants.

SIMPLE_ROUTE_RE = /(?:^|[^.\w])(get|post|put|delete|patch|head|options|ws)\s*(?:\(\s*)?['"](.+?)['"]/

Group 1 = verb (or ws), group 2 = path. ws is folded in so a single scan covers WebSocket routes too; the caller maps ws → GET + protocol=ws.

STRUCT_OPEN_RE = /(?:^|=[^=>])\s*(?:if|unless|case|begin|while|until|for|class|module|def|macro)\b/

Nested types