class

Analyzer::Ruby::Grape

Inherits Analyzer::Ruby::RubyEngine < Analyzer < FileHelper < Reference < Object

Constants

GRAPE_API_MARKER_RE = Regex.union("Grape::API")

Grape::API is absent from almost every .rb file in a large repo, so String#includes? scanned each one end to end. One precompiled matcher via content_matches? is the same test in a single pass.

GRAPE_CLASS_DEF = /^\s*class\s+[A-Z][\w:]*\s*<\s*([:\w][\w:]*)/

class <Child> < <Super> declaration matcher (single capture: Super).

GRAPE_CLASS_EDGE = /^\s*class\s+([A-Z][\w:]*)\s*<\s*([:\w][\w:]*)/

Full form capturing both Child and Super for the inheritance graph.

GRAPE_CONCERN_EXT = /^\s*extend\s+(?:::)?ActiveSupport::Concern\b/
GRAPE_CONCERN_ROUTE = /^\s*(?:resources?|namespace|group|segment|route_param|#{GRAPE_VERBS.join('|')})\b[^\n]*\bdo\b/m
GRAPE_CONTINUED_DSL = /^(?:resource|resources|namespace|group|segment|route_param|#{GRAPE_VERBS.join('|')})\b/

A route or prefix statement whose options continue on the next line (namespace 'conans/:name', / requirements: {...} do).

GRAPE_INCLUDE = /^include\s+((?:::)?[A-Z][\w:]*)\s*$/
GRAPE_LOOP_OPENER = /^\s*(%[wi][\[\(\{][^\]\)\}]*[\]\)\}]|\[[^\]]*\]|\{.*\})\s*\.each(?:_with_index)?\s+do\s*\|\s*([a-z_]\w*)[^|]*\|\s*$/

%w[a b].each do |x|, [:a, :b].each do |x| and { "a" => 1, "b" => 2 }.each do |x, y| over a literal receiver.

GRAPE_MODULE_DEF = /^\s*module\s+([A-Z][\w:]*)/
GRAPE_MOUNT_DECL = /^\s*mount\s+([:\w][\w:]*)(?:\s*=>\s*['"]([^'"]*)['"])?/

mount Child / mount Child => "/path" (capture 2: the mount path).

GRAPE_OR_MOUNT_MARKER_RE = Regex.union("Grape", "mount ", "ActiveSupport::Concern")

build_grape_index's whole-tree pre-pass gate OR-ed two String#includes? scans over every file's content as a standalone boolean gate. Folded into one precompiled union so it costs a single PCRE2 match instead of up to two naive substring scans per file.

GRAPE_PREFIX_DECL = /^\s*prefix\s+['":]([\w\/]+)['"]?/
GRAPE_PREFIX_EXPR = /^(?:resource|resources|namespace|group|segment)\s+([a-z_]\w*)(?:\.\w+[!?]?)*(?:\s*,[^#]*?)?\s*do\b/

resource source_type.pluralize do outside a static loop: the name is only known at runtime, so the segment becomes a {source_type} placeholder instead of vanishing from the path.

GRAPE_PREFIX_LITERAL = /^(?:resource|resources|namespace|group|segment)\s+['":]([\w\/\-:]+)[\'""]?(?:\s*,[^#]*?)?\s*do\b/
GRAPE_VERB_BARE_DO = GRAPE_VERBS.to_h do |verb| {verb, /^#{verb}\s+do\b/} end
GRAPE_VERB_WITH_PATH = GRAPE_VERBS.to_h do |verb| {verb, /^#{verb}\b(?:\s+('[^']*'|"[^"]*"|:[^\s,]+))?(?:\s*,[^#]*?|\s+\w+:\s[^#]*?)?\s*do\b/} end

Crystal recompiles an interpolated regex literal (/^#{verb}.../) on every evaluation — ~11000x slower than a precompiled one — so matching the verb DSL inside the per-line loop used to recompile 14 regexes for every line of every Grape file. Build the per-verb patterns once at load time instead. _WITH_PATH matches get '/users' do (capturing the path literal/symbol) and the path-less get urgency: :low do (options only); _BARE_DO matches the path-less get do form.

GRAPE_VERBS = ["get", "post", "put", "delete", "patch", "head", "options"]
GRAPE_VERSION_DECL = /^\s*version\s+(.+)\busing:\s*:path\b/
MAX_CONTINUATION = 8
MAX_LOOP_DEPTH = 2

Unrolling is multiplicative and noir scans untrusted repos, so it is bounded (real Grape loops are a handful of literals, one level deep).

MAX_LOOP_OUTPUT = 20000
MAX_LOOP_VALUES = 50

Class methods

tech_name
Source

Instance methods

analyze
Source
tech

Instance-side view of the same declaration. The per-file rescues live on this base class, which has no way to name the analyzer that is running inside them, so a skipped file could not be attributed to a tech. Deriving it from analyzer_for keeps the name written exactly once.

Source

Nested types