class
Analyzer::Ruby::Rails
- Analyzer::Ruby::Rails
- Analyzer::Ruby::RubyEngine
- Analyzer
- Reference
- Object
Defined in:
analyzer/analyzers/ruby/rails.crConstant Summary
-
DEVISE_ROUTES =
[{"GET", "/sign_in"}, {"POST", "/sign_in"}, {"DELETE", "/sign_out"}, {"POST", ""}, {"GET", "/sign_up"}, {"GET", "/edit"}, {"PUT", ""}, {"PATCH", ""}, {"DELETE", ""}, {"GET", "/password/new"}, {"POST", "/password"}, {"GET", "/password/edit"}, {"PUT", "/password"}, {"PATCH", "/password"}, {"GET", "/confirmation/new"}, {"POST", "/confirmation"}, {"GET", "/confirmation"}, {"GET", "/unlock/new"}, {"POST", "/unlock"}, {"GET", "/unlock"}] -
Devise routes generated by
devise_for :scope. Tuples are {METHOD, sub_path} appended to the scope's base path. -
DOORKEEPER_ROUTES =
[{"GET", "/oauth/authorize", "authorizations", "new"}, {"POST", "/oauth/authorize", "authorizations", "create"}, {"DELETE", "/oauth/authorize", "authorizations", "destroy"}, {"POST", "/oauth/token", "tokens", "create"}, {"POST", "/oauth/revoke", "tokens", "revoke"}, {"POST", "/oauth/introspect", "tokens", "introspect"}, {"GET", "/oauth/token/info", "token_info", "show"}, {"GET", "/oauth/applications", "applications", "index"}, {"POST", "/oauth/applications", "applications", "create"}, {"GET", "/oauth/applications/new", "applications", "new"}, {"GET", "/oauth/applications/:id", "applications", "show"}, {"GET", "/oauth/applications/:id/edit", "applications", "edit"}, {"PATCH", "/oauth/applications/:id", "applications", "update"}, {"PUT", "/oauth/applications/:id", "applications", "update"}, {"DELETE", "/oauth/applications/:id", "applications", "destroy"}, {"GET", "/oauth/authorized_applications", "authorized_applications", "index"}, {"DELETE", "/oauth/authorized_applications/:id", "authorized_applications", "destroy"}] -
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}.groupmatches theskip_controllerssymbols so ause_doorkeeper do skip_controllers :applications endblock drops the right rows, and indexes thecontrollers foo: 'bar'remap used to resolve the implementing controller for params/callees. Paths are appended to/oauthunder the scope whereuse_doorkeeperruns. -
INLINE_ROUTE_BLOCK =
/^(member|collection|new)\s*\{\s*(.+?)\s*\}\s*$/ -
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. -
LOOP_OPENER =
/^%([wi])[\[\(\{]([^\]\)\}]*)[\]\)\}]\s*\.each(?:_with_index)?\s+do\s*\|\s*([a-z_]\w*)[^|]*\|\s*$/ -
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/%ireceivers are expanded: a dynamicModel.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). -
MAX_LOOP_DEPTH =
3 -
noir scans arbitrary untrusted repos, so loop unrolling must not be a DoS vector — expansion is multiplicative (elements ^ nesting). A crafted
routes.rbwith 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). -
MAX_LOOP_OUTPUT =
20000 -
MAX_LOOP_VALUES =
100 -
PARSE_OPTIONS_HASHROCKET_RE =
Regex.new(":(\\w+)\\s*=>\\s*(#{PARSE_OPTIONS_VALUE_PATTERN})") -
PARSE_OPTIONS_KEY_VALUE_RE =
Regex.new("(\\w+):\\s*(#{PARSE_OPTIONS_VALUE_PATTERN})") -
PARSE_OPTIONS_VALUE_PATTERN =
"nil|['\"][^'\"]+['\"]|:[a-zA-Z_]\\w*|\\[[^\\]]*\\]|%i[\\[\\(][^\\]\\)]*[\\]\\)]|%w[\\[\\(][^\\]\\)]*[\\]\\)]" -
parse_optionsruns 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 twoRegex.new("...#{value_pattern}...")regexes from scratch on every call even thoughvalue_patternis a fixed literal that never varies — Crystal doesn't cache/memoize aRegex.new(String)call the way it embeds a non-interpolated regex literal at compile time. Precompile both patterns once at class load instead.
Class Method Summary
Instance Method Summary
- #analyze
- #controller_to_endpoint(path : String, url : String, resource : String, path_prefix : String = "", only : Array(String) = [] of String, except : Array(String) = [] of String, singular : Bool = false)
-
#tech : String
Instance-side view of the same declaration.
Instance methods inherited from class Analyzer::Ruby::RubyEngine
line_to_endpoint(content : String, details : Details | Nil = nil) : Endpoint
line_to_endpoint
Class methods inherited from class Analyzer::Ruby::RubyEngine
ruby_test_path?(path : String) : Bool
ruby_test_path?
Instance methods inherited from class Analyzer
analyze
analyze,
base_path : String
base_path,
base_paths : Array(String)
base_paths,
base_relative_path(path : String) : String
base_relative_path,
callees_needed? : Bool
callees_needed?,
content_matches?(content : String, markers : Regex) : Bool
content_matches?,
http_header_name(name : String) : String | Nil
http_header_name,
line_number_for_index(content : String, char_index : Int32) : Int32
line_number_for_index,
logger : NoirLogger
logger,
parallel_analyze(files : Array(String), &block : String -> Nil)
parallel_analyze,
read_file_content(path : String) : String
read_file_content,
result : Array(Endpoint)
result,
tech : String
tech,
unique_params(params : Array(Param)) : Array(Param)
unique_params,
url : String
url,
web_root_path(path : String, markers : Array(String)) : String
web_root_path
Constructor methods inherited from class Analyzer
new(options : Hash(String, YAML::Any))
new
Macros inherited from class Analyzer
analyzer_for(tech)
analyzer_for
Instance methods inherited from module FileHelper
all_files : Array(String)
all_files,
get_files_by_basename(basename : String) : Array(String)
get_files_by_basename,
get_files_by_extension(extension : String) : Array(String)
get_files_by_extension,
get_files_by_extensions(extensions : Array(String)) : Array(String)
get_files_by_extensions,
get_files_by_prefix(prefix : String) : Array(String)
get_files_by_prefix,
get_files_by_prefix_and_extension(prefix : String, extension : String) : Array(String)
get_files_by_prefix_and_extension,
get_files_by_relative_path(relative_path : String, root : String = "") : Array(String)
get_files_by_relative_path,
get_public_dir_files(base_path : String, folder : String) : Array(String)
get_public_dir_files,
get_public_files(base_path : String, anchors : Array(String) = ["shard.yml", "Gemfile"]) : Array(String)
get_public_files,
walked_path(expanded : String) : String
walked_path
Class Method Detail
Instance Method Detail
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.