class

Analyzer::Javascript::RouterMountScanner

Inherits Analyzer::Javascript::ExpressConstants < Reference < Object

RouterMountScanner handles the two-pass scanning process for Express router mounts. It scans JavaScript/TypeScript files to discover router mount patterns like: app.use('/api', userRouter) router.use('/v1', require('./routes')) and stores the prefix information in CodeLocator for cross-file resolution.

Constants

APPLY_ROUTES_RE = /((?:require\s*\(\s*['"][^'"]+['"]\s*\))|[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)\s*\.applyRoutes\s*\(\s*[\w$.]+\s*,\s*(?:['"]([^'"]+)['"]|`([^`$]+)`)/

restify-router: usersRouter.applyRoutes(server, '/users') and the nested router.add('/v1', v1Router).

CHAINED_ROUTER_HEAD_RE = /(?:\b(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=\s*|\bexport\s+default\s+)(?:new\s+)?(?:express\s*\.\s*)?Router\s*\((?:[^()]|\([^()]*\))*\)(?=\s*\.use\s*\()/

const api = Router()\n .use(items) and export default Router().use('/api', api): the .use( links of a chain on a fresh router have no name. in front of them for the scans above. The chain's head names the caller: the bound variable, or, for an export default chain, the module itself ("" falls back to the file's own prefix).

CHAINED_USE_RE = /\G\s*\.use\s*\(/
CJS_REEXPORT_RE = /(?:\b([A-Za-z_$][\w$]*)\s*:|\bexports\.([A-Za-z_$][\w$]*)\s*=)\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)/
FIREBASE_APP_HANDLER = /(?:\A|,)\s*([A-Za-z_$][\w$]*)\s*\z/

The handler is the last argument: onRequest(app) / onRequest({ region }, app).

FIRST_PARAM_RE = /\A\s*([A-Za-z_$][\w$]*)\s*(?:,|\z)/
FORWARDER_ARROW_RE = /\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?\(/
FORWARDER_FUNCTION_RE = /\.([A-Za-z_$][\w$]*)\s*=\s*(?:async\s+)?function\b[^(]*\(/

app.lazyUse = function lazyUse(mountPath, requireFn) { app.use(mountPath, …) } and the arrow form: a method whose definition hands its first parameter to .use(…) as the mount path. A call to it is a mount, read the same way .use('/p', child) is. Read off the definition, so no wrapper name is assumed.

GLUE_PLUGIN_LIST_RE = /\bregistrations\s*:\s*\[|\bregister\s*:\s*\{\s*plugins\s*:\s*\[/

A Glue manifest lists its plugins as data, not .register( calls: registrations: [{ plugin: { register: './api' }, options: { routes } }] (Glue 4) or register: { plugins: [{ plugin: '../lib', routes }] }.

GLUE_PLUGIN_PATH_RE = /\A(?:\{\s*register\s*:\s*)?['"](\.{1,2}\/[^'"]*|\.{1,2})['"]/
HAPI_PLUGIN_FIELD_RE = /\A\s*plugin\s*:\s*(.+)\z/m
HAPI_ROUTES_PREFIX_RE = /\broutes\s*:\s*\{[^}]*?\bprefix\s*:\s*(?:['"]([^'"]+)['"]|`([^`$]+)`)/

Hapi nests the prefix under routes, on the options or per plugin item.

HONO_ROUTE_MOUNT_RE = /\.route\s*\(\s*['"]([^'"]+)['"]\s*,\s*(\w+)\s*\)/

Hono <chain>.route('/prefix', ChildApp) mounts. Registers the prefix on the child's file when ChildApp is an imported router, regardless of (or in the absence of) a named caller.

LOCAL_ROUTER_VAR_RE = /\A[A-Za-z_$][\w$]*\z/
MAX_DEFERRED_ITERATIONS = 10
MAX_REEXPORT_DEPTH = 5
MODULE_REEXPORT_RE = /\A\s*(?:['"]use strict['"];?\s*)?(?:module\.exports\s*=\s*require\s*\(\s*['"]([^'"]+)['"]\s*\)|export\s*\{\s*default\s*\}\s*from\s*['"]([^'"]+)['"])\s*;?\s*\z/

A module that is nothing but module.exports = require('./app') (or export { default } from './app') exports what ./app exports, so its prefix belongs on ./app, where the routes and mounts are.

MOUNT_CALL_NAMES = ["use", "route"] of ::String

Mount-call verbs scanned for literal prefixes (.use('/p', r) / .route('/p', app)). Compiled once per verb — interpolated regex literals would otherwise be rebuilt (full PCRE2 compile) on every scan_literal_mount_calls invocation.

MOUNT_RES = MOUNT_CALL_NAMES.map do |c| {c, mount_call_res(c)} end.to_h
REEXPORT_LIST_RE = /\bexport\s*\{([^}]*)\}\s*from\s*['"]([^'"]+)['"]/
REGISTER_BINDING_RE = /\A([A-Za-z_$][\w$]*)(?:\.([A-Za-z_$][\w$]*))?\z/
REGISTER_CALL_RE = /\.register\s*\(/
REGISTER_MODULE_RE = /\A(?:await\s+)?(?:require|import)\s*\(\s*['"]([^'"]+)['"]\s*\)(?:\.default)?\z/
REGISTER_PREFIX_RE = /\bprefix\s*:\s*(?:['"]([^'"]+)['"]|`([^`$]+)`)/
RENAMED_IMPORT_RE = /([A-Za-z_$][\w$]*)\s*(?::|\s+as\s+)\s*([A-Za-z_$][\w$]*)/
RESTIFY_ADD_RE = /\.add\s*\(\s*(?:['"]([^'"]+)['"]|`([^`$]+)`)\s*,\s*((?:require\s*\(\s*['"][^'"]+['"]\s*\))|[A-Za-z_$][\w$]*(?:\.[A-Za-z_$][\w$]*)?)\s*\)/
ROUTE_CALL_RE = /\.(get|post|put|delete|patch|all|head|options)\s*\(/
SPLIT_COMMA_RULES = Noir::TopLevelSplit::Rules.new(nest: (Noir::TopLevelSplit::Nest::Paren | Noir::TopLevelSplit::Nest::Bracket) | Noir::TopLevelSplit::Nest::Brace, quotes: "\"'`", escape: Noir::TopLevelSplit::Escape::InQuotes, strip: false, empties: Noir::TopLevelSplit::Empties::DropTrailing, per_kind: true, clamp: true)

Rules::JS on five of seven axes — same nest kinds, same three quote characters, same per-kind depth counters, same clamp, same Escape::InQuotes. It diverges on strip and empties only, and only because the hand-rolled body it replaces never stripped and kept its interior empties: Rules::JS strips and drops every empty part.

Both divergences are invisible to the single caller — the loop in parse_destructured_names does item.strip and skips the result when it is empty — so this is faithfulness to the replaced body, not a behavior requirement. File-local because no other splitter in the tree wants this combination, and promoting a preset for one caller whose two distinctive axes are unobservable would be misleading.

USE_FIRST_ARG_RE = /\.use\s*\(\s*([A-Za-z_$][\w$]*)\s*,/

Constructors

new(all_files : Array(String), base_paths : Array(String), base_path : String, logger : NoirLogger, framework : Symbol = :express)

framework is the analyzer that owns this scan — the same Symbol its route pass hands to JSRouteExtractor.other_shared_extractor_framework?. Both Express and Hono run a scanner over every JS/TS file in the tree, and every prefix they resolve lands in one process-wide CodeLocator table keyed by file. Without the symbol there is no way to tell "this file is mine" from "this file belongs to Koa", so a koa-router module picked up a / mount from the Express scanner and its routes were emitted a second time at the bare path.

Source

Class methods

mount_call_res(name : String) : MountCallRes
Source

Instance methods

scan

Main entry point: scan all files for router mount patterns

Source

Nested types