module

Analyzer::Dart::Helper

Shared helpers for the Dart framework analyzers (Dart Frog, Shelf, Serverpod). Kept framework-agnostic so each analyzer can opt in without duplicating path/string conventions.

Constants

FORM_BODY_REGEX = /\.formData\s*\(\s*\)/
HANDLER_REFERENCE_REGEX = /\A[A-Za-z_]\w*(?:\.[A-Za-z_]\w*)*\z/

Matches a bare handler reference (_createUser, auth.handler) passed as a route's handler argument.

HEADER_INDEX_REGEX = /\.headers\s*\[\s*r?["']([^"']+?)["']\s*\]/
HEADER_VALUE_REGEX = /\.headers\s*\.\s*value\s*\(\s*r?["']([^"']+?)["']/
INDEX_READ_REGEX = /(?<![\w$.])([A-Za-z_]\w*)\s*\[\s*r?["']([^"']+?)["']\s*\]/
JSON_ALIAS_REGEX = /\b([A-Za-z_]\w*)\s*=\s*(?:await\s+)?(?:jsonDecode\s*\(\s*(?:await\s+)?)?[\w.]*\.(?:json|readAsString)\s*\(\s*\)/
JSON_BODY_REGEX = /\.(?:json|readAsString)\s*\(\s*\)/
QUERY_ALIAS_REGEX = /\b([A-Za-z_]\w*)\s*=\s*[\w.]*\.(?:uri|url|requestedUri)\s*\.\s*queryParameters(?:All)?\s*;/

final q = request.url.queryParameters; / final body = await request.json(); / jsonDecode(await request.readAsString()) — the variable is then read as q['x'].

QUERY_PARAM_REGEX = /\.(?:uri|url|requestedUri)\s*\.\s*queryParameters(?:All)?\s*\[\s*r?["']([^"']+?)["']\s*\]/

Request-input reads. uri is dart:io's and Dart Frog's request URI, url/requestedUri are Shelf's.

ROUTES_DIR_RE = /(?:\A|\/)routes\//
SPLIT_ARGS_RULES = Noir::TopLevelSplit::Rules.new(nest: ((Noir::TopLevelSplit::Nest::Paren | Noir::TopLevelSplit::Nest::Bracket) | Noir::TopLevelSplit::Nest::Brace) | Noir::TopLevelSplit::Nest::Angle, quotes: "\"'", escape: Noir::TopLevelSplit::Escape::InQuotes, strip: false, empties: Noir::TopLevelSplit::Empties::Keep, per_kind: true, clamp: true)

Each bracket kind ((), {}, [], <>) gets its own counter so a generic argument (Map<String, int>) and a collection literal ([a, b]) both keep their inner commas, and string literals are skipped so a comma inside 'a,b' never splits. Nothing is stripped and every part is kept, the trailing one included, so a single-argument call yields a one-element array.

File-local rather than a Rules preset: it is the only splitter in the tree that counts <> and also keeps every part unstripped, so a central name would have exactly one user.

TEMPLATE_PATH_RE = /__brick__|\{\{/
TEST_DIR_RE = /(?:\A|\/)test\//

Whole path segments, matched on the scan-base-relative path, so test_app/ or a checkout that itself lives under tests/ never trips them.

TEST_DIRS_RE = /(?:\A|\/)(?:tests|testing|test_utils|test_driver|integration_test)\//

Instance methods

build_endpoint(url : String, verb : String, path : String, line : Int32, callees : Array(Noir::DartCalleeExtractor::Entry)) : Endpoint

Build an endpoint from an already-normalized URL, promoting every {name} capture in it to a path param and attaching the callees. Analyzers whose endpoints carry extra state (Dart Frog's websocket flag, dart:io HttpServer's inherited params) keep their own.

Source
extract_string_literal(text : String) : String | Nil

Pull the contents of a leading single/double-quoted string literal from an argument expression, honouring backslash escapes. Returns nil when the expression doesn't start with a string literal.

Source
find_matching_paren(text : String, open_idx : Int32, offsets : Noir::CharOffsets | Nil = nil) : Int32 | Nil

Index of the ) closing the ( at open_idx, or nil when the expression never balances. String literals are skipped so a paren inside 'a)b' doesn't close the call, and backslash escapes inside them are honoured.

Both the argument and the result stay in CHAR space, consistent with Regex::MatchData#begin, Analyzer#line_number_for_index and the char_index_to_byte_index conversions the analyzers use for callee extraction. Pass offsets (built over text) when calling once per route: without it the char-to-byte seek is O(open_idx) per call on any source holding a multi-byte char.

Source
first_top_level_comma(text : String, start : Int32, limit : Int32) : Int32 | Nil

Char index of the first comma at paren/brace/bracket depth zero between start and limit, or nil when the call has a single argument.

Source
handler_callees(handler_arg : String, content : String, handler_start : Int32, path : String, line : Int32) : Array(Noir::DartCalleeExtractor::Entry)

Callees for a route's handler argument, where handler_start is the CHAR index at which the handler expression begins (typically the char just past the first top-level comma).

A plain function reference (_createUser, auth.handler) can't be resolved cross-file yet, so the reference itself is recorded as the callee.

extract_body_after scans by BYTE offset, hence the char-to-byte conversion. It skips past the leading (req, res) lambda params to the =>/{ body, so a trailing middleware: argument after the body is naturally excluded.

Shelf keeps its own variant: it derives the handler start from the call's closing paren instead of a comma index, so the two are not interchangeable.

Source
request_params(body : String) : Array(Param)

Query, header and body inputs a handler body reads. body should be comment-stripped. A body read whose keys can't be seen becomes one body param, as in the dart:io analyzer.

Source
split_top_level_args(text : String) : Array(String)

Split a call's argument list on top-level commas.

Note: Serverpod#split_top_level_commas is deliberately NOT this method — it tracks only </( on one shared counter and is not string-aware, matching the narrower shapes it parses. It goes through the same shared splitter with its own Rules, so the difference is now two constants to compare rather than two loops to diff.

Source
test_path?(path : String, base_path : String | Nil = nil) : Bool

Standard Dart test-file conventions. The Dart tooling discovers tests under a project-root test/ directory and via the *_test.dart suffix; neither ever serves real traffic. Dart Frog in particular mirrors the route tree under test/routes/, so a naive /routes/ match would surface every mock handler as a live endpoint. Centralized so every Dart analyzer can opt in via next if Helper.test_path?(path, base_paths).

  • test/ — Dart's dart test discovery root and the Dart Frog test/routes/ mirror tree
  • tests/, testing/, test_utils/, test_driver/, integration_test/ — test servers and Flutter's test roots (Serverpod's tests/serverpod_test_server, pub_worker's lib/src/testing/server.dart); not below a routes/ directory, where folder names are URL segments
  • *_test.dart — the canonical Dart unit-test suffix
  • __brick__, {{ — Mason / Serverpod scaffold templates, which only become routes once rendered
Source
test_path?(path : String, base_paths : Array(String)) : Bool
Source

Nested types