class

Analyzer::Swift::Vapor

Inherits Analyzer::Swift::SwiftEngine < FileScanEngine < Analyzer < FileHelper < Reference < Object

Constants

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 methods

tech_name
Source

Instance methods

analyze_file(path : String) : Array(Endpoint)
Source
extract_function_params(lines : Array(String), start_index : Int32, endpoint : Endpoint)

Extract parameters from function body

Source
extract_path_params(route : String, endpoint : Endpoint)

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

Source
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

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