class Analyzer::Swift::Vapor

Defined in:

analyzer/analyzers/swift/vapor.cr

Constant Summary

FUNCTION_SIGNATURE_PATTERN = /\bfunc\s+([A-Za-z_]\w*)\s*\(/
GROUP_ASSIGN_PATTERN = /\b(?:let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.)?grouped\s*\(/

The base may be a dotted receiver (app.routes.grouped(...)), an implicit self (bare grouped(...)), or a single identifier (router.grouped(...)).

GROUP_CLOSURE_PATTERN = /([A-Za-z_]\w*)\.group(?:ed)?\s*\(/
LOOKAHEAD_LIMIT = 20

Maximum number of lines to look ahead for function parameters

ON_ROUTE_PATTERN = /([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.on\s*\(/
ROUTE_CALL_RE = Regex.union(".get(", ".post(", ".put(", ".delete(", ".patch(", ".on(")

route_definition? runs on every scanned line (route detection, param lookahead, body-boundary checks). One precompiled Regex.union scan (PCRE2 JIT) replaces six naive String#includes? char scans; union auto-escapes each literal so it is provably equivalent to the OR-of-substrings it replaces.

ROUTE_PATTERN = /([A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*)\.(get|post|put|delete|patch)\s*\(/

Patterns for route definitions in Vapor: app.get("path") { ... } app.post("path", "segment") { ... } routes.get("path", ":param") { ... }

ROUTER_BINDING_PATTERN = /\b(?:let|var)\s+([A-Za-z_]\w*)\s*=\s*(?:try\s+|await\s+)*Application\b/
ROUTER_EXTENSION_PATTERN = /\bextension\s+(?:RoutesBuilder|Router)\b/

extension RoutesBuilder { ... self.get(...) ... } — inside such an extension self (and bare grouped(...), handled by GROUP_ASSIGN_PATTERN) is router-like.

ROUTER_PARAM_PATTERN = /(?:_\s+)?([A-Za-z_]\w*)\s*:\s*(?:some\s+|any\s+|inout\s+)*(?:RoutesBuilder|Router|Application)\b/

A function parameter (or binding) typed as a Vapor router: func routes(_ app: Application), func boot(routes: RoutesBuilder), func boot(router: Router) (Vapor 3), let app = Application(...). Tracking these makes route detection receiver-aware, so that non-router .get/.delete/... calls — Environment.get("DATABASE_URL"), model.delete(on: req.db), req.client.get(url) — stop surfacing as phantom endpoints.

ROUTER_TYPE_RE = Regex.union("RoutesBuilder", "Router", "Application")

Gate for register_router_params. All three patterns it runs require one of these three words, so a line without any of them cannot contribute a receiver and does not need to be matched at all.

That pattern opens with an unanchored ([A-Za-z_]\w*), so PCRE2 tries it at every offset of the subject and backtracks the greedy \w* on each one — quadratic in the line length. Up to roughly half a megabyte the JIT absorbs it; past that the JIT stack overflows, PCRE2 falls back to the interpreter, and the cost becomes visible all at once: a 600 KB single-line .swift file (generated code, a vendored bundle) took 75 s in this one scan where 300 KB took milliseconds.

A literal union is a plain memchr-class search with no backtracking, and since the full pattern requires one of these three words the gate cannot change what is found.

Class Method Summary

Instance Method Summary

Class methods inherited from class Analyzer::Swift::SwiftEngine

swift_test_path?(relative_path : String) : Bool swift_test_path?, swift_vendor_path?(relative_path : String) : Bool swift_vendor_path?

Instance methods inherited from class FileScanEngine

analyze analyze, analyze_file(path : String) : Array(Endpoint) analyze_file

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

def self.tech_name : String #

[View source]

Instance Method Detail

def analyze_file(path : String) : Array(Endpoint) #

[View source]
def extract_function_params(lines : Array(String), start_index : Int32, endpoint : Endpoint) #

Extract parameters from function body


[View source]
def extract_path_params(route : String, endpoint : Endpoint) #

Extract path parameters from the route pattern (e.g., :id, :userID)


[View source]
def parse_route_path(route_args : String) : String #

Parse route path from route arguments Examples: "hello" -> /hello "users", ":id" -> /users/:id "api", "users", ":userID" -> /api/users/:userID


[View source]
def tech : String #

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.


[View source]