Analyzer::Ruby::Rails
Inherits Analyzer::Ruby::RubyEngine < Analyzer < FileHelper < Reference < Object
Constants
Devise routes generated by devise_for :scope. Tuples are
{METHOD, sub_path} appended to the scope's base path.
Routes generated by Doorkeeper's use_doorkeeper (the de-facto
OAuth2 provider for Rails — Mastodon, GitLab, Discourse plugins,
…). Each tuple is {METHOD, sub_path, group, action}. group
matches the skip_controllers symbols so a use_doorkeeper do skip_controllers :applications end block drops the right rows,
and indexes the controllers foo: 'bar' remap used to resolve
the implementing controller for params/callees. Paths are
appended to /oauth under the scope where use_doorkeeper runs.
Rails commonly writes compact inline route scopes:
collection { get "preview" } / member { post :use }.
The parser is stack-based, so normalize these to the same do/end
structure before route extraction.
A %w[...].each do |var| / %i[...].each_with_index do |var, i|
block over a STATIC literal list unrolls to one copy of its body per
element with the loop variable's #{var} interpolations substituted.
discourse alone wraps ~46 routes in %w[users u].each — without this,
get "#{root_path}/..." leaks {root_path} into ~130 endpoints (a
fabricated path param) AND the real /users + /u variants are lost.
Only literal %w/%i receivers are expanded: a dynamic
Model.scopes.each do |s| cannot be resolved to values at parse time,
so it is left for the normal parser (its block opens a neutral frame).
noir scans arbitrary untrusted repos, so loop unrolling must not be a DoS
vector — expansion is multiplicative (elements ^ nesting). A crafted
routes.rb with deep nesting or a huge %w[...] list could otherwise
blow up CPU/memory. Real apps use tiny shallow lists (discourse's biggest
is the 2-element %w[users u]), so modest caps preserve all genuine
recall while bounding the worst case. Over a limit, the block is passed
through unexpanded (the pre-unrolling behavior — a transparent frame).
parse_options runs on most DSL lines of every routes.rb file (scope,
namespace, resources, member/collection actions, verb routes, match) —
i.e. once per line in the main route-parsing hot loop. It used to build
two Regex.new("...#{value_pattern}...") regexes from scratch on every
call even though value_pattern is a fixed literal that never varies —
Crystal doesn't cache/memoize a Regex.new(String) call the way it
embeds a non-interpolated regex literal at compile time. Precompile
both patterns once at class load instead.
Class methods
Instance methods
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.