# Dart kernel port (R7b batch 4) — the bug-for-bug checklist **Status: SURVEY COMPLETE (2026-07-20), port not started.** Survey basis: every TS-side branch a `.dart` file exercises, with file:line anchors as of **`45a53eb`** (HEAD at survey time, clean main). Every extraction-behavior claim below was **pinned against the real `dist/` extractor** (the `extract-*.txt` ground-truth dumps in the session scratchpad `svy-dart/` — §Probe artifacts), and every grammar-shape claim was **probed against the production wasm** (tree-sitter-wasms 0.1.13's dart build) via full CST dumps — not inferred from reading code. Field-presence claims come from `fieldNameForChild` dumps corroborated by extractor behavior (the swift lesson: dump labels alone lie — here every field claim is backed by an extractor-output pin). Read WITH `docs/design/rust-kernel-migration-plan.md` (§0a recipe, §2 boundary, §4 tracker, §5 gates) and the format precedents (`kotlin-kernel-port-checklist.md` — the closest structural sibling: visitNode-hook constants, vendored-grammar-C build; `swift-kernel-port-checklist.md` — the shared-branch liveness style and high-deferral posture). **Blocking findings: none — but one mechanism is NOVEL and load-bearing, and one number is big.** (1) **The sibling-body double-walk (§The double-walk) is the center of gravity of this port.** Dart's grammar attaches every function/method BODY as a *next sibling* of its signature node, and the TS walkers consume the body TWICE — once via `resolveBody` (attributed to the function/method) and once via the enclosing generic walk (attributed to the file/class). The result is a deterministic pattern of duplicate nodes (local functions minted twice under different parents with the SAME id), duplicate `instantiates`/`calls` refs, and file/class-attributed `function_ref` twins — all of which the walker must reproduce byte-for-byte, in exactly the observed interleave. NO previously ported walker has this surface. (2) **Both-arm parse-error incidence is 3.4–20.7% on the gate repos** — flutter/flutter sits at 20.73% because the grammar (2025-10) cannot parse *empty* object patterns (`TimerInitial() =>`, the dominant sealed-class state-matching idiom) or the unnamed `library;` directive. Run parity sweeps with **`--max-deferral 0.3`** (swift-style exemption). 0 phantom errors were found anywhere (3 repos + a 46-snippet battery) — unlike kotlin — but the defer rule stays trust-the-flag. (3) The "grammar bump" for dart is a **byte-copy of the ALREADY-SHIPPING wasm** (unpinned-dep de-risking, not a version change), so the bump gate is trivial — expect byte-identical everything; the only real grammar gate is kernel-grammar-parity (vendored-C build ↔ vendored wasm table identity). ## Grammar prep (wasm byte-copy vendor + vendored-C kernel build) dart is NOT in `VENDORED_WASM_LANGS` (grammars.ts:291-333) — production resolves `require.resolve('tree-sitter-wasms/out/tree-sitter-dart.wasm')` (mapping `dart: 'tree-sitter-dart.wasm'` grammars.ts:36; `.dart` → dart at grammars.ts:108, no content sniffing, no dialects). tree-sitter-wasms 0.1.13's dart dependency is **UNPINNED `github:UserNobody14/tree-sitter-dart`**, built at the 0.1.13 publish (2025-10-07) — a tree-sitter-wasms update would silently change dart's grammar, which is exactly the hazard this vendor kills. - **Provenance (verified by the batch-4 grammar probe, re-verified here):** UserNobody14/tree-sitter-dart **master@`d4d8f3e337d8be23be27ffc35a0aef972343cd54`** (2025-10-04, "Fix `set` and `get` contextual keywords…" #89) — parser.c tables positionally identical to the shipped wasm (ABI **15**, STATE_COUNT 3640, SYMBOL_COUNT 524 + ALIAS_COUNT 1, TOKEN_COUNT 161, 22 fields, EXTERNAL_TOKEN_COUNT 7). - **Production wasm** sha256 `7f5364e4256cf7e55efd01dd52421ef2663caa8061b82659b7e4bf61064545ec` (984,666 bytes, `node_modules/tree-sitter-wasms/out/tree-sitter-dart.wasm`). **Vendor plan: BYTE-COPY this file** to `src/extraction/wasm/tree-sitter-dart.wasm` + `VENDORED_WASM_LANGS += 'dart'` (R7b comment: commit sha + "wasm is the byte-copied tree-sitter-wasms 0.1.13 artifact; kernel compiles the same-commit vendored C — codegraph-kernel/grammars/dart"). No rebuild, no `tree-sitter generate` — the shipped bytes ARE the reference. `copy-assets` already globs `src/extraction/wasm/*.wasm`. MIT license. - **External scanner: YES** — `src/scanner.c` PRESENT at the pinned commit. It owns 7 external tokens: the 5 string-template char classes (`_template_chars_*`), `_block_comment`, and `_documentation_block_comment` — i.e. **`/** */` doc comments are scanner-produced**; the vendored C must include scanner.c or every string/comment shape breaks. - **Vendor file list + sha256 (recorded at survey time from the commit clone):** - `src/parser.c` `5a42b47abb4d494f125dbdee9138979248041689b1aa36355550fa3e28dcb8b8` - `src/scanner.c` `07a7b7818b175e9460523e705dd88d20f7b5141bac95c593d4426e6d52284996` - `src/tree_sitter/parser.h` `180b893c8734778fd32f372dfbc27bd6ad1cd2221f26150b31256ff6716320d2` - `src/tree_sitter/alloc.h` `b29c1c9fb7cc82f58c84b376df1297d6e2737a1d655fd356db0859e3c29c2fea` - `src/tree_sitter/array.h` `5bdf6ed1a78e3409fd443e085ca967a64c188a5d082aaf7f819bccd53a471c94` - **crates.io `tree-sitter-dart` 0.2.0 is the nielsenko FORK — REJECTED** (different lineage, 480 positional table mismatches per the batch-4 probe). This supersedes the plan §4 long-tail row's "crates.io" route for dart. Do not revisit unless the TS side migrates grammars. - **Kernel side — vendored-grammar-C (the kotlin #1382 mechanism, second use):** copy the three-file set above (parser.c, scanner.c, tree_sitter/*.h) to `codegraph-kernel/grammars/dart/`; extend `codegraph-kernel/build.rs`'s cc::Build to compile both C files with the grammar's own flags (its checked-in `bindings/rust/build.rs` uses `-Wno-unused-parameter`, `-Wno-unused-but-set-variable`, `-Wno-trigraphs`, msvc `-utf-8` — same set kotlin vendored). langs.rs: ```rust extern "C" { fn tree_sitter_dart() -> *const (); } // … "dart" => Some(unsafe { tree_sitter_language::LanguageFn::from_raw(tree_sitter_dart) }.into()), ``` plus `LANGUAGES` (langs.rs:24; `[&str; 15]` at survey time — sibling batch-4 legs are landing concurrently, take whatever count is current) += `"dart"`. `__tests__/kernel-grammar-parity.test.ts:39` `GRAMMAR_LANGUAGES += 'dart'` — the id-by-id ABI/kind/field-table compare against the vendored wasm is the whole grammar gate here (both artifacts are same-commit by construction; the parity test proves it stayed that way). - **Staging:** wasm byte-copy + VENDORED_WASM_LANGS can land standalone before the walker (full suite green; the "old-vs-new" dump gate degenerates to cmp-identical since the bytes are identical — run it once on one repo as a smoke test, expect zero). The kernel C vendor + build.rs + langs.rs + grammar-parity row land with it or with the walker — same-commit from day one. - **Error incidence (both-arm reality — same grammar bytes on both arms by construction; `error-sweep.cjs`, all `.dart` ≤1 MiB, `.git`/`node_modules`/ `.dart_tool` skipped):** | Repo | files | hasError | % | phantom | |---|---|---|---|---| | dart-lang/shelf (small, server dart) | 99 | 10 | 10.10% | 0 | | felangel/bloc (medium, Flutter + packages) | 616 | 21 | 3.41% | 0 | | flutter/flutter (large, the framework) | 6,465 | 1,340 | 20.73% | 0 | Error classes (sampled + minimized to repros, `dart3-errors.txt` + `error-diag.cjs`): **(a) empty object patterns** — `Init() =>` / `case Init():` / `if (x case Init())` ALWAYS error (MISSING identifier in `constant_pattern`); patterns WITH fields (`Point(x: var a)`) parse clean. This is the modern sealed-class state-match idiom → it alone drives flutter's 20.73% and bloc's flutter examples. **(b) unnamed `library;`** (the dangling-doc idiom, dart-lang style guide) → MISSING identifier; `library foo.bar;` is clean. **(c) null-aware collection elements** (`[?x]`, `key: ?value`, Dart 3.8) → ERROR, often blowing the whole file root. **(d)** mustache-template `.dart` files (bloc's `bricks/` `{{name.snakeCase()}}`) — not real dart. **(e)** `augment class` (rare). Everything else probed CLEAN, including records, non-empty patterns, switch expressions, sealed/base/final/interface/mixin class modifiers, extension types, super-params, one-line class bodies (NO kotlin-style phantom), shebang `#!`, digit separators, and CRLF. **A non-ASCII IDENTIFIER errors (`String séance`) — but that is invalid Dart anyway** (identifiers are ASCII-only by spec); keep fixture identifiers ASCII and non-ASCII text in comments/strings. - **Deferral policy:** per-file `has_error()` → `defer:` (trust the flag); sweeps with **`--max-deferral 0.3`** (covers flutter's 20.7% with headroom; shelf/bloc sit far under). A deferral-rate JUMP vs the table is a walker bug; the rate itself is grammar reality. ## Architecture decisions 1. **No preParse, no POST_PASS.** `dartExtractor` has no `preParse` hook (languages/dart.ts — whole file) → `preParsedSource` (kernel/index.ts:108) is a no-op; both arms parse raw bytes. `POST_PASSES` (kernel/index.ts:94) is empty → `tryKernelExtractRaw` stays eligible. 2. **No dart framework resolver exists** (grep src/resolution/frameworks/ — nothing lists dart; no Flutter resolver). BUT the **universal** `vue` and `astro` resolvers (no `languages` list ⇒ applicable to every language, frameworks/index.ts:110-118) carry `extract()` hooks → in a repo where vue/astro is DETECTED, parse-worker.ts:92-99 forces `.dart` files onto the decoded `extractFromSource` path (their extract() hooks no-op on `.dart` content — transport differs, output must not). Pure dart/Flutter repos ride the raw buffers path. All three gate repos are expected raw-path — verify at sweep time; a mixed Vue+dart repo is the decoded-path smoke check. 3. **One walker module** (suggest `codegraph-kernel/src/dart.rs`), registered in langs.rs; per-file `has_error()` → `defer:`. Cribs: **kotlin.rs** for the visitNode-hook-consumed constants + hook/scan interplay; **java.rs** for the class-like scope stack, static-member refs, decorators, and type-annotation walks. **Three surfaces have NO precedent in any ported walker** and are transcription work from this doc: (a) `extractBareCall` selector-walking (dart is the first callTypes=[] language), (b) the sibling-body double-walk reproduction (§The double-walk), (c) the constructor naming/skip hooks (`resolveName`/`isMisparsedFunction`). 4. **Wire notes (KERNEL_ABI_VERSION 2, layout.ts:20):** no dart extraction path emits refs carrying `filePath` (verified across every ground-truth dump — zero refs printed one) → **REF_FLAG_FILE_PATH (layout.ts:98) stays unused, flag 0 on every ref** like swift/rust. `function_ref` = wire code 200 (layout.ts:89). The node `decorators` wire field (layout.ts:56) is **never populated for dart** (no `extractModifiers` hook — annotations are `decorates` REFS only). The node `signature` field (layout.ts:55) is **heavily used** (functions/methods AND hook-minted constants). 5. **MAX_FILE_SIZE (1 MiB) and skip dirs** (`.dart_tool`, `.pub-cache` — extraction/index.ts:173) are orchestrator/TS-side and shared. `isGeneratedFile` (generated-detection.ts:63-68) matches `.g.dart`, `.freezed.dart`, `.pb.dart`, `.pbgrpc.dart`, `.chopper.dart` — those files still EXTRACT normally but **skip the function_ref flush and the value-ref pass** (tree-sitter.ts:647/:785); build_runner output is a huge population in Flutter repos, so port the skip. 6. **Kind inventory** (what dart can emit): file, import, function, method, class, enum, enum_member, type_alias, constant. **NEVER: field, property, variable, struct, interface, trait, namespace, module.** Instance fields mint NO nodes at all (§Constants); there is no namespace/package node (no `packageTypes` hook — `library foo.bar;` is invisible); interfaces don't exist as a dart concept (abstract classes are kind `class`). ## Extractor config (languages/dart.ts — 380 lines, read it whole) Types: functionTypes=[`function_signature`] (:119); classTypes= [`class_definition`] (:120); methodTypes=[`method_signature`, `constructor_signature`, `constant_constructor_signature`, `redirecting_factory_constructor_signature`] (:126 — the last two since 2026-10-06); interfaceTypes=[] ; structTypes=[]; enumTypes=[`enum_declaration`] (:129); enumMemberTypes=[`enum_constant`] (:130); typeAliasTypes=[`type_alias`] (:131); importTypes=[`import_or_export`, `part_directive`] (:132 — the second since 2026-10-06); **callTypes=[] (:133 — extractCall NEVER runs for dart; all call refs ride `extractBareCall` in the body walker)**; variableTypes=[] (:134 — extractVariable never runs); extraClassNodeTypes=[`mixin_declaration`, `extension_declaration`] (:135). nameField=`name` (:172), bodyField=`body` (:173), paramsField=`formal_parameter_list` (:174 — DEAD, see §Type-annotation refs), returnField=`type` (:175 — DEAD likewise). Grammar-shape facts the config leans on (all probed, `mini-cst.txt` / `torture-cst.txt`): - **Signature/body split:** every function and method is a SIGNATURE node (`function_signature` / `method_signature` / bare `constructor_signature`) whose body is the **next named sibling** `function_body`. `function_body` wraps either a `block` or a bare `=>` expression (arrow bodies have no block child). The `async`/`async*`/`sync*` keyword is an anon child of function_body. - **Field truth table** (fieldNameForChild + behavior-pinned): `function_signature.name` → identifier ✓ (formal_parameter_list is an UNFIELDED child; return type is an unfielded `type_identifier`/`void_type` BEFORE the name). `method_signature` has NO fields (its single child is the inner signature). `getter_signature.name` / `setter_signature.name` ✓. `constructor_signature` has **`name` fields on BOTH identifiers** (`name: Widget`, `name: named` — childForFieldName('name') returns the FIRST = the class) + `parameters:` field. `factory_constructor_signature` children are UNFIELDED identifiers. `class_definition`: `name:` ✓, `superclass:`, `interfaces:`, `body:` ✓. `extension_declaration`: `name:` (absent for anonymous extensions), `class:` (the on-type), `body:` (extension_body). `mixin_declaration`: **NO fields at all** (children: anon-ish named `mixin` keyword node, identifier, on-types as bare type_identifiers, class_body). `enum_declaration`: `name:` + `body:` (enum_body); `enum_constant.name` ✓. `type_alias` has NO fields (both modern `typedef X = T` and legacy `typedef void X(int)` are node `type_alias`). Its name is the last direct `type_identifier` before the `=`, `type_parameters` or `formal_parameter_list` child: a legacy return type comes first (`typedef Report X(int n)`), and until 2026-10-21 the first-identifier fallback named such a typedef after its return type (`dartTypeAliasName` / `type_alias_name` now, via resolveName). `initialized_variable_definition`: `name:` + one-or-more `value:` fields (the RHS postfix chain is FLATTENED into multiple value-fielded siblings). - **In-class member shapes:** methods/getters/setters/operators/factories and BODIED constructors are `method_signature > (function_signature | getter_signature | setter_signature | operator_signature | constructor_signature | factory_constructor_signature)` + sibling `function_body`, both direct children of class_body. **BODILESS constructors** (`Widget(this.size);`, `Widget._() : x = 0;`) wrap in a `declaration` node instead (`declaration > constructor_signature [+ initializers]`) — reached by the walker via plain recursion into `declaration`. `const` constructors are `declaration > constant_constructor_signature` and redirecting factories are `declaration > redirecting_factory_constructor_signature` — both carry NO fields (children: `const_builtin`? `"factory"`? identifier [`.` identifier] formal_parameter_list, then for a redirect `=` and the target: `type_identifier` [type_arguments] [`.` name], where the target constructor's name is a `type_identifier` without type arguments and an `identifier` after them). Until 2026-10-06 neither was in methodTypes (no node, no refs); now both mint methods exactly like the bare `constructor_signature` (§isMisparsedFunction, §resolveName). Abstract and `external` members are `declaration > function_signature` too (an anon `external`/`static` keyword may precede the signature). In every one of these the member's dartdoc and annotations are siblings of the `declaration`, not of the signature — §getDeclarationWrapper. Fields are `declaration > (static)? (final|const|type|var) > initialized_identifier_list | static_final_declaration_list` (§Constants). - **Statements/expressions:** calls have NO call node — a postfix chain is FLAT siblings: `identifier selector selector …` where each `selector` wraps `unconditional_assignable_selector` (`.name`) / `conditional_assignable_selector` (`?.name`) / `argument_part > arguments > argument*`. `new Foo(1)` IS a single `new_expression` (children: unfielded type_identifier + arguments). `const Foo.bar(1)` in EXPRESSION position is a `const_object_expression`; in a `const x = …` declaration the const rides the declaration and the value is a plain flat chain. Cascades are `cascade_section > cascade_selector + argument_part` (NO selector node → invisible to everything, §Calls). Assignments are `assignment_expression` with `left:` (an `assignable_expression`) and `right:` fields. Locals are `local_variable_declaration > initialized_variable_definition` (even without initializer). Lambdas are `function_expression` (children incl. `body: function_expression_body`). String templates: `string_literal > template_substitution` wrapping either a full expression (`${…}`) or an `identifier_dollar_escaped` (`$name`). Hooks PRESENT (port each exactly — anchors into languages/dart.ts): - **visitNode (:144-157) — the constants branch.** `node.type === 'static_final_declaration'` → nameNode = first namedChild of type `identifier`; if present: valueNode = `nameNode.nextNamedSibling` (the FIRST value child only — a flattened chain like `WidgetT(0)` captures just `WidgetT`; a wrapped expression like `SHARED_MAX + 1` captures the whole expression node); initValue = its text `.slice(0,100)` (UTF-16); `ctx.createNode('constant', name, node, { signature: initValue ? \`= ${initValue}${initValue.length >= 100 ? '...' : ''}\` : undefined })`. When the constant is minted, the hook pushes it and walks the WHOLE declaration with the body walker (`ctx.walkInitializer(node)` — visitFunctionBody that records the node as walked), then pops: the initializer's calls, instantiations, static and member reads, types, closures (block bodies and local functions included) and fn-ref candidates (the `varinit` capture of the declaration itself included) are the CONSTANT's. Return **true** (consumed — even when nameNode missing? NO: name found is required for createNode but the hook returns true for EVERY static_final_declaration reached, node minted or not — transcribe the early-return shape exactly: `if (nameNode) {…; if (constant) walk}; return true`). The dispatcher then runs `scanFnRefSubtree(node, 0, walked)` — which skips a walked declaration outright, so only an unminted one is scanned — and never descends. - **visitNode — the field / top-level variable branch.** `node.type === 'initialized_identifier'` whose parent is an `initialized_identifier_list` directly under `program`, or under a `declaration` in a class_body/extension_body/enum_body → `ctx.walkInitializer(node)` with no push (the stack top is the file or the class/enum), return **true**. These declarations mint no node, so their initializers are the FILE's or the CLASS's. An `initialized_identifier` anywhere else — the second variable of a local `for (var i = 0, j = n(); …)`, under `initialized_variable_definition` — is left to its function's body walk (the double-walk's pass 2a reaches it too, so the parent check is load-bearing). The `declaration` and `program` type rows push only the DECLARED type's refs; initializer types come from the walk. Everything else → false. **Reality of the node type (probed, `probe2-cst.txt`):** `static_final_declaration` = a `final`/`const` declaration WITH an initializer that is **top-level** (`const SHARED_MAX = 10;`, `final typedTop = compute();`, typed or untyped, incl. multi-declarations `final multiA = 1, multiB = 2;` → one node per list entry) **or class-level WITH `static`** (`static const int K_MAX = 9;`, `static final sharedInst = WidgetT(0);`). Instance `final untyped = 5;` / `final int size;` / any `var`/typed-var member → `initialized_identifier` → **NO NODE** (dart emits zero `field` nodes, ever). Top-level `var topVar = 5;` / `int topTyped = 6;` → initialized_identifier → invisible too. Constants get **no docstring, no visibility, no isExported** — signature only (pinned: `/// Doc on num const?` is dropped). - **resolveBody (:158-171)** — `function_signature`/`method_signature` → `node.nextNamedSibling` if it's a `function_body`, else null (NOTE: a bare `constructor_signature` — the declaration-wrapped bodiless ctor — takes the OTHER branch: childForFieldName('body') → null → find class_body/extension_body among children → null ⇒ named bodiless ctors never walk a body, and their `initializers` sibling is left to plain visitNode recursion). For class/mixin/extension nodes: standard `body` field first (class_definition ✓, extension_declaration ✓), else the first namedChild of type `class_body` | `extension_body` (mixin_declaration has no body field → found by type). Used by extractClass/extractEnum body resolution AND createNode's endLine extension (tree-sitter.ts:1322-1334 — **LIVE and load-bearing for dart**: the comment there names Dart; a method/function node's endLine extends to the sibling body's end, e.g. `named` L123-125 spans its body). - **getDeclarationWrapper** — node's parent is a `declaration` AND node is that declaration's FIRST named child (`firstNamedChild.equals(node)`; kernel `named_child(0) == node` — an anon `external`/`static` keyword before the signature doesn't count) → the `declaration`, else undefined. Read by tree-sitter.ts's `docstringFor` (every docstring lookup, so the kernel's `docstring_of` wraps all five `preceding_docstring` calls) and by extractDecoratorsFor's scan #2 (§Decorators); scan #1 and `getBodyDocstring` keep the node itself. Only Dart defines it — never add `declaration` to DOCSTRING_WRAPPER_TYPES / docstring.rs `is_wrapper`, which C/C++ (whose `declaration` wraps declarators) share. - **getReturnType = extractDartReturnType (:80-92)** — ctor = dartCtorInfo (§below): a validated ctor returns the CLASS name (named ctors, factories → ret = enclosing class). Else sig = dartInnerSignature (:9 — method_signature unwraps to function/getter/setter signature; **NOT constructor/factory/operator** — those keep sig = the method_signature, where the type search then finds nothing) → the FIRST namedChild of type `type_identifier` → text, `.replace(/<[^>]*>/g,'')`, `.trim()`, last `.`-segment, must match `/^[A-Za-z_]\w*$/`. Pinned: `Future` → `Future` (type_arguments is a SIBLING node, so the strip is a no-op); `List` → `List`; `WidgetT?` → `WidgetT` (nullable_type unwraps positionally — the type_identifier is still first); `void` → undefined (void_type ≠ type_identifier); `num`/`dynamic`/`Object` → themselves; `T` (generic param) → `T` (leaks); **prefixed `other.OtherClass` → `other`** (the prefix and the name are SEPARATE type_identifier leaves and the FIRST is the prefix — the `.pop()` never sees a dot. BUG, PRESERVE); getters → their type (`int get area` → `int`); setters → undefined; operators → undefined (inner unwrap misses operator_signature); ctors → class name. - **isMisparsedFunction (:177-188)** — dartCtorInfo != null AND ctorName === className → **the UNNAMED constructor `Widget(this.size)` is skipped** (no node; extractFunction/extractMethod still resolve+walk the body — which is null for the declaration-wrapped form, so in practice nothing emits). Named ctors/factories are kept. The `@override (T) m()` misparse (an annotation swallowing a record return type leaves `m()` shaped like a single-identifier constructor_signature) is NOT skipped — dartCtorInfo validates ids[0] against the enclosing type name via dartEnclosingTypeName (:38 — walks parents for class_definition/mixin_declaration/ extension_declaration/enum_declaration and reads ITS name field) and `reduce` ≠ `Action` → treated as the method it is (pinned: `extract-probe4.txt` — method `reduce`, position starting AT `reduce`, sig/ret undefined, decorates override intact). dartCtorInfo reads only the identifiers BEFORE the formal_parameter_list, so a redirecting factory's target (`factory Foo() = _Impl.named;`) never names it. A `constant_constructor_signature` / `redirecting_factory_constructor_signature` that dartCtorInfo rejects (names no enclosing type — error recovery) is skipped too: it can be nothing else. - **getSignature (:189-208)** — method_signature unwraps to function/getter/setter signature (NOT ctor/factory/operator); params = find namedChild `formal_parameter_list`; retType = find namedChild `type_identifier` | `void_type`; neither → undefined; result = `[retType text + ' '] + [params text]`, trimmed. Pinned shapes: `void (int a, String b)`; getter → `int` (retType only); setter → `(int v)` (params only); generic return `Future load()` → **`Future ()`** (the type_arguments are NOT included — retType is just the identifier); full param text is a RAW source slice — named/optional brackets, defaults, nested function-typed params all verbatim (`void ({int? named, required WidgetT child, String note = 'x'})`); bodied named ctor (method_signature > constructor_signature) → **undefined** (unwrap misses ctor; no direct formal_parameter_list on method_signature); **bodiless named ctor (declaration > constructor_signature) → `"()"`-style params-only sig** (node IS the ctor signature; its formal_parameter_list is found by type) — pin BOTH ctor sig shapes; operators → undefined. `const` ctors and redirecting factories → params only too: no retType is looked up for them (a redirect's `= _Impl` type_identifier is its target, not a return type). - **getVisibility (:209-222)** — method_signature → unwrap to function/getter/setter signature → its first `identifier` child; other nodes → childForFieldName('name'). Name starts `_` → 'private', else 'public'. QUIRKS, PRESERVE: **every constructor is 'public'** — for method_signature-wrapped ctors the unwrap misses constructor_signature (nameNode null → public); for bare constructor_signature the `name` FIELD is the CLASS identifier (`Widget`, not `_`) → `Widget._()` is 'public' (pinned). Operators → public. `_check`/`_privateMethod` → private ✓. Classes/enums get visibility via the same hook at their extractors: class_definition name field → `_Private` class would be 'private'. - **isAsync (:223-233)** — nextNamedSibling is function_body → scan ALL its children (anon included) for child.type === `'async'` → true. Pinned: `async` → TRUE; **`async*` and `sync*` → FALSE** (different token text → different anon type — generators are not "async"); bodiless (external/ abstract) → false. Note the anon child's TYPE is exactly `async` (probed via behavior). - **isStatic (:234-243)** — node.type === 'method_signature' → scan ALL children for type `'static'` → true; else false. `static WidgetT make()` → true ✓ (the `static` keyword is an anon child of method_signature); bare constructor_signature → false. Top-level functions → false. - **resolveName (:244-260)** — dartCtorInfo: named ctor/factory (`const` and redirecting ones included) → the ctor name (`named`, `create`, `_`); unnamed ctor or non-ctor → undefined (falls to extractName §below). - **extractImport (:261-304)** — importText = trimmed full node slice; moduleName = the URI string content: `library_import > import_specification > configurable_uri > uri > string_literal` (else the same chain under `library_export`; a `part_directive` holds its `uri` directly), quotes stripped via `.replace(/['"]/g,'')` (dartUriText). Returns {moduleName, signature} (no handledRefs) → import NODE (name = the URI, e.g. `package:torture/other.dart`, `dart:async`, `src/reexported.dart`, `x.g.dart`) + the generic `imports` ref (tree-sitter.ts:3183-3194) {from: file node (no namespace exists), name = URI, line/col of the directive node — at its first annotation when it has one}. `as alias`, `show`/`hide` combinators: IGNORED (not read). **`import 'x.dart' deferred as y;` is INVISIBLE** — the deferred form's import_specification holds a bare `uri` (NO configurable_uri wrapper) → hook returns null → falls through the multi-import inline handlers (none match import_or_export) → no node, no ref (pinned, `extract-mini.txt`). **`part 'x.g.dart';` is an import since 2026-10-06** (signature `part 'x.g.dart';`): the resolver reads its URI by the import/export rules (`dartDirectiveFile`), so the library's file links the part's file and a change to the part reaches the library's dependents. `part of` and `library` (part_of_directive, library_name) are in NO type list → invisible: the library's `part` edge already joins the two files. None of the TS/py/rust/php/ruby binding emitters (:3197-3234) fire for dart. - **extractBareCall (:305-379)** — §Calls, the full matrix. Hooks ABSENT (the walker must NOT do these): `preParse`, `recoverMangledName`, `classifyClassNode` (class_definition is always kind `class` — abstract/ sealed/base/final/interface-modified classes included), `classifyMethodNode`, `extractPropertyName`, `propertyTypes`, `fieldTypes`, `getReceiverType` (never diverts extractFunction:1522; no receiver QNs; no owner-contains :1799 — extension members ride the extension's own class node), `isConst`, `isExported` (**undefined everywhere except the file node's literal `false`**), `interfaceKind`, `extractPackage`/`packageTypes` (no namespace node — top-level QNs are bare), `extractModifiers` (node `decorators` never set), `synthesizeMembers`, `skipBodilessClass` (bodiless `class Base {}` still mints — dart classes always have `{}` anyway), `methodsAreTopLevel`, `resolveTypeAliasKind` (type_alias is always kind `type_alias`). Registration: `EXTRACTORS.dart` (languages/index.ts:56), `FN_REF_SPECS.dart` (function-ref.ts:394). ## tree-sitter.ts branches (anchors as of `45a53eb`) ### visitNode dispatch — what each dart node hits (ladder at 936-1303) | Node | Branch | Behavior | |---|---|---| | every node | visitNode hook first (:943) | `static_final_declaration` consumed and body-walked for its constant; a field's or top-level variable's `initialized_identifier` consumed and body-walked for the class/file (§Constants); handled → scanFnRefSubtree (skipping what was walked) + STOP | | every node | maybeCaptureFnRefs (:990) | fires for `arguments`/`assignment_expression`/`pair`/`list_literal`/`static_final_declaration` in visitNode context — the source of the file/class-attributed fn-ref twins (§double-walk) | | `function_signature` | functionTypes:994 | methodTypes does NOT include it → **always extractFunction:1517**, even inside a class (isInsideClassLike && !methodTypes.includes → else-arm). In-class function_signatures occur only for ABSTRACT/bodiless methods, reached via their `declaration` wrapper (`declaration > function_signature`, probed) → **kind `function` contained by the class** (pinned: `AbstractT::mustImpl` is a function; the sibling `declaration > getter_signature` abstract getter stays invisible). skipChildren | | `class_definition` | classTypes:1005 | no classifyClassNode → always extractClass:1679 (abstract/sealed/base/etc. included) | | `mixin_declaration`, `extension_declaration` | extraClassNodeTypes:1022 | extractClass(node) → kind **`class`** | | `method_signature`, `constructor_signature` | methodTypes:1027 | **NOT class-gated at the ladder** — extractMethod:1737 runs anywhere; its own gate :1747 (not class-like, no methodsAreTopLevel, no receiver) falls back: parent `object`/`object_expression`? (never in dart) else **extractFunction** — this is how extension-TYPE members and any stray non-class method_signature become plain `function` nodes (pinned: `extension type MetersT` → function `km`, bare QN) | | `enum_declaration` | enumTypes:1064 → extractEnum:1914 | §Class family | | `type_alias` | typeAliasTypes:1071 → extractTypeAlias:2890 | type_alias node named by the resolveName hook (§Extractor config); `getChildByField(node,'value')` → **null** (no fields), so the dart branch — `pushDartTypeAliasRefs` / `type_alias_refs`, since 2026-10-21 (#2327; nothing before) — emits a `references` ref from the node for each type named in every named child but the name and `annotation`s, through the isDartTypeName gate (`typedef MapAlias = Map` → `Map`, `WidgetT`). A type parameter in scope is skipped unless written `p.T`: the typedef's own, and those of any node that declares `type_parameters` (a generic `function_type`, an old-style generic function-typed `formal_parameter`) within it. Returns false → children re-visited (they match nothing type-related; an annotated typedef's arguments are fn-ref capture points) | | `import_or_export`, `part_directive` | importTypes:1209 → extractImport:3170 | §Extractor config. No skipChildren: the directive's children are still recursed, so an annotated directive's arguments are fn-ref capture points (`@Tag(f) import 'a.dart';` captures `f`). The kernel arm recurses too — since 2026-10-06; before, its `import_or_export` arm returned and missed those (TortureDirectives.dart pins both) | | `new_expression` | INSTANTIATION_KINDS:1255 (`new_expression` ∈ :354-361) | extractInstantiation:4610 → ctor field lookups null → namedChild(0) = type_identifier → `instantiates` ref from stack top; `<`-strip + last-`.`-segment apply (`new p.Foo()` → `Foo`). findAnonymousClassBody → always null for dart. Children still recursed | | `function_body` (sibling of a consumed signature) | **no branch** | recursed → THE DOUBLE-WALK (§below) | | `declaration` (fields, bodiless ctors) | no branch | recursed → constructor_signature, constant_constructor_signature and redirecting_factory_constructor_signature hit methodTypes; each field `initialized_identifier` hits the hook (body-walked for the class); initializers, annotations-in-place: nothing | | `getter_signature` / `setter_signature` BARE (top level) | no branch | **top-level getters/setters are INVISIBLE** (no node; their sibling function_body is visitNode-recursed where calls don't extract) — in classes they're method_signature-wrapped → methods | | `const_object_expression`, `selector`, `cascade_section`, `assignment_expression`, `local_variable_declaration`, patterns, `extension_type_declaration`, `part_of_directive`, `library_name`, lambdas | no branch | recursed; calls only extract in the BODY walker (`extractBareCall` is not consulted by visitNode!) — §Calls for the consequences | | `property_signature`/`method_signature` TS branch (:1282) | **shadowed** | method_signature is consumed at :1027 first; property_signature isn't a dart kind — branch unreachable | ### THE DOUBLE-WALK (sibling bodies) — reproduce it exactly Because bodies are SIBLINGS, every signature-consuming extractor walks its body via resolveBody, and then the ENCLOSING loop (program level, class-body loop, or an outer visitFunctionBody) visits the same `function_body` node again as an ordinary child. What fires on each pass: - **Pass 1 (visitFunctionBody, attributed to the function/method):** the full body matrix — extractBareCall calls, instantiates, static-member refs, nested named functions (:5245), fn-ref capture (:5137). - **Pass 2a (visitNode recursion — program/class-body sibling visit):** ONLY the visitNode-dispatched branches: `new_expression` → **instantiates from the FILE (top level) or CLASS (class body)**; nested `function_signature` → **a SECOND extractFunction** (functionTypes:994) minting a DUPLICATE node — same (kind, name, line) ⇒ **the SAME node id** — whose parent is the file/class (contains edge from file/class; QN `localFn` at top level, `Holder::methodLocal` in a class) and whose own body walk re-emits its refs attributed to the duplicate; maybeCaptureFnRefs:990 → **fn-ref candidate twins from the file/class**; `static_final_declaration` never occurs inside bodies (locals are initialized_variable_definition) — no constant dupes. Bare calls, static reads, cascades: NOTHING (no extractBareCall/extractStaticMemberRef in visitNode). - **Pass 2b (outer visitFunctionBody sibling visit — a LOCAL function's body seen by ITS enclosing body walker):** the full body matrix again, attributed to the ENCLOSING function — bare calls inside a local fn hence emit from the local fn (pass 1) AND from the enclosing fn (pass 2b) AND from the duplicate local fn (pass 2a's own body walk). Pinned interleaves (`extract-probe2.txt`, transcribe as parity expectations): - Top-level `hostFn` with local `localFn { inner(n); }` and local `localWithNew { new Holder(1); }`: nodes `hostFn::localFn` (contained by hostFn) … then `localFn` (bare QN, contained by file, SAME id) after the pass-2a revisit; refs in order: `calls inner from=localFn` (pass 1 of nested extract), `calls inner from=hostFn` (pass 2b), `calls localFn from=hostFn`, `instantiates Holder from=localWithNew`, `instantiates Holder from=hostFn`, `calls localWithNew from=hostFn`, — then the program-level revisit: `calls inner from=localFn` (2nd node's walk), `instantiates Holder from=localWithNew` (2nd), `instantiates Holder from=file`. - In-class `useNew` with a local `methodLocal { h.touch(); }` and `new Holder(2)`: `instantiates Holder from=useNew`, `calls h.touch from=methodLocal`, `calls h.touch from=useNew`, `calls methodLocal from=useNew`, then the class-body revisit: `instantiates Holder from=class:Holder`, `calls h.touch from=methodLocal` (2nd) — and node `Holder::useNew::methodLocal` + duplicate `Holder::methodLocal` (kind function both, same id). - fn-ref twins: `register(seedValue)` in a top-level fn → `function_ref seedValue from=refTaker` AND `from=file:…` (flush order: all in-scope candidates first, then the revisit's file/class twins — capture order); in a class method → `from=method:wire` AND `from=class:H` twins (`fnref2.dart` pin). - Per-declaration order: ALL of a function's pass-1 refs come before its pass-2a refs, which come before the NEXT declaration's refs (the program loop visits signature then body then the next signature). Same per class member inside class_body. **Dedupe reality:** node dupes share an ID (`generateNodeId` has no uniquifier) — two node rows, two contains edges; downstream the store last-writer-wins by id, but the WIRE/parity dump carries both rows — byte-parity requires emitting both, in order. The kernel `node_ids` vec must therefore NOT self-dedupe: emit exactly what TS emits. ### Node creation, IDs, qualified names - createNode (:1308): id = `generateNodeId(filePath, kind, name, startRow+1)` = `` `${kind}:${sha256(`${filePath}:${kind}:${name}:${line}`).hex.slice(0,32)}` `` (tree-sitter-helpers.ts:18-30). FILE node id = literal `file:${filePath}` (:509), name = basename, qualifiedName = filePath, endLine = `source.split('\n').length`, isExported false. - **endLine extension (:1322-1334) is LIVE** for function/method kinds: resolveBody's sibling function_body extends endLine past the signature (`named` L123→125; `bodyShapes` L60→96). Class/enum/etc. spans are their own node extents. - contains edge from nodeStack top for every created node (:1363); extractModifiers merge (:1355) is a no-op (hook absent); captureValueRefScope (:1374). - qualifiedName = nodeStack names joined `::` (buildQualifiedName:1447; file excluded; namespacePrefix always empty outside C/C++). No package node → top-level QNs are bare; members `WidgetT::render`; nested `WidgetT::useNew::methodLocal` (and the duplicate's shallower `WidgetT::methodLocal` — §double-walk). - isInsideClassLikeNode (:1486): stack-top kind ∈ {class, struct, interface, trait, enum, module} — for dart that means class (incl. mixins/extensions) and enum. - Node position = the SIGNATURE node (functions/methods): `externalFn` starts col9 (the `external` keyword sits OUTSIDE function_signature); the `@override (T) m()` misparse starts at `m` (col16 pin); constants sit at the static_final_declaration (name start), enum members at the enum_constant, imports at import_or_export. ### extractFunction / extractMethod (:1517 / :1737) - extractFunction: no receiver hook → never diverts. Name via extractName (:90): resolveName (ctor names) → nameField `name` (function_signature ✓) → the method_signature inner-unwrap fallback (:148-167 — the dart-specific branch: find inner function/getter/setter/constructor/factory_constructor signature, take its first `identifier` child) → first identifier/type_identifier child (:178-189) → ``. - **Operator methods: name = ``** — operator_signature is NOT in the :148 inner list and method_signature has no identifier children → the fallback finds nothing → createNode('method', '', …) — the name is truthy so the node IS minted (extractMethod has no `` skip, unlike extractFunction:1549 which only guards the extractFunction path — and even there `` only occurs for arrow/function_ expression types, not dart signatures). Pinned: `WidgetT operator +(…)` → method `` vis=public sig/ret undefined, its param/return type_identifiers still emitted as references FROM it, body calls attributed to it. Multiple operators in one class → same name, different lines → distinct ids. - extractMethod extras {docstring, signature, visibility, isAsync, isStatic, returnType}; extractFunction adds isExported (undefined). Then extractTypeAnnotations (:1594/:1816 — §Type-annotation refs), then extractDecoratorsFor (:1599/:1819 — §Decorators), then push + body walk via resolveBody + pop. - Bodiless-in-class function_signature (abstract members) → extractFunction → **kind `function`** node with class-prefixed QN (pinned `AbstractT:: mustImpl`); resolveBody → nextNamedSibling is NOT a function_body (next member) → no body walk. Abstract GETTERS (`int get abstractGetter;`) are bare getter_signatures → invisible entirely. - extractMethod's fallback for non-class method_signatures (extension types, hypothetical top-level method_signature) → extractFunction (:1760) after the object-literal parent check (:1751 — never true in dart). ### The class family — extractClass / extractEnum - **extractClass (:1679)** for class_definition + mixin_declaration + extension_declaration: resolvedBody via hook (class_body/extension_body); no skipBodilessClass. extras {docstring, visibility (name-underscore rule), isExported undefined}. extractInheritance (§below) BEFORE the body walk; extractCsharpPrimaryCtorParamRefs — **WARNING, NOT a no-op by language gate**: :5938 checks `this.language !== 'csharp'` → returns early ✓ (cheap early-out — port as a no-op). extractDecoratorsFor (§Decorators: `@immutable class` → decorates from the class). Push, visit body namedChildren via visitNode, pop. - Extension names: `extension WidgetTExt on WidgetT` → name field → `WidgetTExt`; **anonymous `extension on String` → the extractName fallback finds the FIRST type_identifier = the ON type → a class node named `String`** (pinned — members become `String::anonExt`). The extended type is otherwise INVISIBLE (no ref — extractInheritance matches nothing on extension/mixin nodes). - Mixins: `mixin MixA on BaseT` → class `MixA`; the `on` types are bare type_identifier children matching NO inheritance clause → **no refs**. `mixin MixB implements DrawT` → the `interfaces` child DOES match → implements DrawT ✓. - **extractEnum (:1914)**: body REQUIRED (enum_body via `body` field — present). extras {docstring, visibility, isExported undefined}. extractInheritance on enum_declaration: `interfaces` child → implements ✓; **the `mixins` child is a DIRECT child of enum_declaration (no superclass wrapper) and matches NO clause → `enum StatusT with MixB` emits NOTHING for the mixin** (pinned — the class-side mixin handling lives inside the `superclass` branch only). Body loop (:1941-1950): `enum_constant` ∈ enumMemberTypes → extractEnumMembers; everything else (declaration-wrapped fields/const ctors → nothing; method_signature getters/statics → extractMethod with the enum pushed) → visitNode. - **extractEnumMembers (:1958)**: `getChildByField(node,'name')` — the `name` field EXISTS on enum_constant → **ONE enum_member node per constant, positioned at the ENUM_CONSTANT node** (extent covers `ok(200)` — id line = the constant's line), then RETURN. Ctor arguments (`argument_part`) are NEVER walked → no refs from enum-constant arguments. The identifier-scan fallback (:1967) is dead for dart. - Inheritance — extractInheritance (:5291), the dart rows (probed): - `superclass` child (class_definition only) → **the dart branch (:5368-5393)**: for each namedChild of the superclass node — direct `type_identifier` → **`extends`** ref; a `mixins` child → one **`implements`** ref per `type_identifier` inside. `class WidgetT extends BaseT with MixA, MixB` → extends BaseT + implements MixA + implements MixB; `class OnlyMix with MixA {}` → superclass holds ONLY mixins → implements MixA, NO extends (pinned). Generic supertypes: the type_identifier is the base, `type_arguments` a sibling → `extends Base` clean. Position = each type_identifier. - `interfaces` child (:5437-5459, class + enum): targets = its namedChildren (no type_list in dart) → one **`implements`** per child, name = FULL child text (children are type_identifiers; a generic `implements Comparable` would ride the type_identifier + sibling type_arguments shape → name `Comparable`). - Order: extends+mixins first (superclass child precedes interfaces in source), then interfaces — all BEFORE decorates and body members (pinned ref order: extends BaseT, implements MixA, implements MixB, implements DrawT, decorates immutable, then member refs). - The python `argument_list`-under-`class_definition` branch (:5463) shares dart's class node TYPE — but a dart class_definition never has an argument_list child → dead; port nothing but know why it's safe. - Extensions/mixins: extractInheritance runs but nothing matches (`on` types are bare type_identifiers) — zero refs. ### Constants (the visitNode hook) — and what NEVER minted Covered in §Extractor config. Emission pins (`extract-torture.txt`): top-level `const SHARED_MAX = 10` → constant, QN bare, sig `= 10`; `final DERIVED_VAL = SHARED_MAX + 1` → sig `= SHARED_MAX + 1` (whole expression node); `final typedTop = compute()` → sig `= compute` (flattened chain → FIRST value child only); `static final sharedInst = WidgetT(0)` → constant under the class, sig `= WidgetT`; multi-declarations → one node each with own columns. **NO nodes ever**: instance fields (typed/untyped/ late/var), `static var`, top-level var/typed vars, top-level getters/ setters, extension_type containers, `part of`/`library`/deferred imports. (Named `const` constructors and redirecting factories mint methods since 2026-10-06, and a `part` directive an import; the unnamed constructors still mint nothing.) **Initializers are code:** every initializer is walked with the body walker — a constant's for the constant (riverpod's `final repoProvider = Provider((ref) => Repository(ref.watch(dioProvider)));` → calls `Provider`, `Repository`, `ref.watch` from `repoProvider`), a field's (instance, `static var`, typed, `late`) for its class, a top-level `var`/typed/`late final` one for the FILE. The full body matrix applies: calls, instantiates, static-member and member reads, type refs, closures with block bodies (a local function inside one mints a `function` under the constant, with the double-walk's pass 2b on its body), interpolation calls, fn-ref candidates. Each is walked ONCE: the hook consumes the entry, so the visitNode recursion that used to reach `new_expression` / maybeCaptureFnRefs inside a field initializer no longer does. ### Calls — extractBareCall (dart.ts:305-379) in the body walker (:5159-5173) Only visitFunctionBody consults extractBareCall; the ref is {from: nodeStack top, name, line/col of the MATCHED node (the selector/const-object node — NOT the chain head)}. The dart matrix (all pinned in extract-torture.txt / extract-mini.txt): | Source shape | Matched node | Emitted `calls` ref | |---|---|---| | `helper(count)` | selector>argument_part; prev = identifier | `helper` at the selector position | | `WidgetT(1)` | same | `WidgetT` (constructor = plain capitalized call; the resolution side's CONSTRUCTS_VIA_BARE_CALL — name-matcher.ts:918 — depends on exactly this shape) | | `obj.method(x)` | args selector; prev = `.method` selector; accessorPrev = identifier | `obj.method` (receiver kept, lowercase or not) | | `ConfigT.load()` | same | `ConfigT.load` **+ a `references ConfigT` static-member ref from the `.load` selector (§Static-member) — the double emission** | | `WidgetT.named(3)` | same | `WidgetT.named` + references WidgetT | | `other.OtherClass()` (prefixed ctor) | same | `other.OtherClass` | | `this.own()` / `super.parent()` | args selector; prev = `.own` selector; accessorPrev = `this`/`super` node (not identifier) | bare `own` / `parent` | | `a.b.call3(x)` (2+ hops) | accessorPrev = another non-argpart selector | bare `call3` | | `FactoryT.create().run()` | outer args selector; accessorPrev = inner argument_part selector | **`FactoryT.create().run`** — the #750 re-encode via dartCalleeOfArgPart (:100-116): innerCallee `FactoryT.create` is `/^[A-Z]/` → `` `${innerCallee}().${method}` ``; plus the inner `FactoryT.create` ref from its own selector; plus references FactoryT | | `WidgetT.named(3).chainTail()` | same | `WidgetT.named().chainTail` + `WidgetT.named` + references WidgetT | | `lower().chain()` | innerCallee `lower` lowercase | bare `chain` + `lower` | | `xs.map((e) => …).toList()` | innerCallee `xs.map` lowercase | bare `toList` + `xs.map` | | `w?.render()` | conditional_assignable_selector | `w.render` — **`?.` is encoded exactly like `.`** | | `y2..add(1)..add(2)` (cascades) | cascade_section (argument_part NOT inside a selector) | **NOTHING — cascade calls are completely invisible** | | `new WidgetT(2)` | new_expression → INSTANTIATION branch :5145 FIRST | `instantiates WidgetT` (extractBareCall's new_expression arm :363-367 is DEAD — the else-if never reaches it); args still recursed | | `pad(const EdgeInsetsT.all(8.0))` | const_object_expression :369-376 | `EdgeInsetsT.all` at the CONST node position (typeId + '.' + nameId; type-only form → `EdgeInsetsT`); children recursed after. The FIRST type_identifier: `const p.X.named()` → `p.named` (prefix bug, preserved) | | `BlocProviderT.value(…);` as a statement | args selector; prev = `.value` selector; accessorPrev = a selector holding only `type_arguments`, whose prev is the identifier | `BlocProviderT.value` (dartTypeArgumentsReceiver, since 2026-10-06 — bare `value` before); the `.value().tail` re-encode reads it the same way | | `=> BlocProviderT.value(…)`, and as an initializer, argument or `return` value | `constructor_invocation` (type_identifier [`.` type_identifier] type_arguments `.` identifier arguments) | `BlocProviderT.value` at the node position — the LAST type_identifier, so `p.X.named()` → `X.named` (since 2026-10-06; nothing before) | | `generic(5)` | args selector (type args ride argument_part) | bare `generic` | | `ref.read(p)`, `Provider((ref) => 0)`, `X(name: v)` parsed as two comparisons, `(ref.read < Repo) > (p)` (§Misparsed generic calls) | the `<` relational_operator (dartMisparsedGenericCall) | the parsed call's ref — `ref.read` / `Provider` / `X`, at the `<` | | `await fetch()` | recursion through unary/await_expression | `fetch` at the selector | | `throw StateError('bad')` | recursion | `StateError` | | `'sum ${a + compute()}'` | template_substitution recursion | `compute` (interpolation calls EMIT); `$name` → identifier_dollar_escaped → nothing | | local-lambda body `final lam = (int a) { helper(a); }` | function_expression recursed transparently | `helper` attributed to the ENCLOSING function; `lam(5)` → `lam` | | `final p = Provider((ref) => Repo(ref.watch(d)))`, `static var s = make()`, `final _c = Controller();` in a class, `var v = load();` | the hook's walkInitializer (§Constants) | the full matrix, from the constant / the class / the file | | ctor initializers (`: size = seed()`), enum-constant args (`ok(200)`), default param values | never body-walked | NOTHING | | `tester //` + newline + `.state(x)`, `box.grow /* by */ (3)` | comments are named SIBLINGS between the chain's parts | `tester.state` / `box.grow` — every sibling step skips `comment` and `documentation_comment` (dartPrevNamed / dartNextNamed; kernel `prev_named` / `next_named`), so a comment changes nothing | Every sibling step along a member chain goes past comments: the callee before an argument part, dartReceiverOf / receiver_of, dartCalleeOfArgPart's two, dartTypeArgumentsReceiver's type, and dartMemberRead's receiver and its next-selector check. tree-sitter-dart keeps a comment as a named node wherever it is written, so before (2026-10-06) a comment between a member and its arguments lost the call and emitted a member read, a getter read or static access after a comment (`Config //`, dart format's line-break idiom) was lost, and a call after one kept only its bare name. The misparsed-generic-call layout checks below still read the raw siblings: a comment there means the code is not laid out as a call. extractCall (:3684), LITERAL_RECEIVER_TYPES (:373-388), SKIP_RECEIVERS, the parenthesized-conversion regex (:4530), template-strip — ALL UNREACHABLE for dart (callTypes empty). Do not port them. #### Misparsed generic calls (dartMisparsedGenericCall / misparsed_generic_call) tree-sitter-dart parses a generic call with arguments as two comparisons about as often as it parses it as a call — the GLR tie goes either way with the code around it: `final a = ref.watch(p);`, `ref.read(p);`, `int g(Ref ref) => ref.read(1);` and `Provider((ref) => 0)` come out as `relational_expression(relational_expression( < ) > )`, while `print(ref.watch(p) + 1)`, `obj.a.b(p)` and `ref.read(a, b)` parse as calls (2,853 nests vs 2,836 parsed generic calls with arguments across bloc, riverpod and flutter/samples). The args are a `parenthesized_expression`, or a `record_literal` when named (`DropdownButton(items: …)`); the type part is `identifier`, a `.Name` selector (`p.Repo`) and/or a type_arguments-only selector (`Map`); the callee part sits under `unary_expression` / `await_expression` for `await` and prefix operators. Recovered at the `<` when the type names a type (`/^[_$]*[A-Z]/` or `int double num bool dynamic void`), `<` touches the callee and `(` touches the `>`; `a < B > (c)`, `a(c)` and `a (c)` stay comparisons, and so does `a.b /* c */(c)`: the comment is a sibling of its own against the `<`, so it is rejected as the callee (else the gate passed with no call, and the comment-skipping member read was suppressed). The recovered form emits what the parsed call does: - `calls` at the `<` (the parsed call's argument_part starts there); - `references ` for the type argument, via isDartTypeName (§Type refs); the static-member path skips `Map` in `>` so it is not doubled; - no member read of the callee (`ref.read`, also under `await`) or of a prefixed type (`p.Repo`). A call chained on a recovered one keeps its bare name (`increment` in `BlocProvider.of(context).increment()`), unlike the parsed chain's `BlocProvider.of().increment`. The resolver gives both the same method: a bare link is typed from the chain written before it, and so is an encoded chain whose inner call declares no type of its own to return — a generic factory's type parameter (`static T of`) or a factory outside the project (#750). Nothing is gained by re-encoding the recovered chain. The grammar can also end an arrow function in front of a generic call: `(ref) => ref.watch(p)` → `((ref) => ref).watch(p)`, in the relational and the selector form alike. dartReceiverOf / receiver_of read a receiver in that place from the end of the arrow's body (Dart writes a member access on an arrow itself in parentheses), for callee names and static-member refs: `ref.watch`, `BlocProvider.of`. dartCalleeOfArgPart / callee_of_arg_part do not, so `() => BlocProvider.of(context).increment()` keeps `increment` bare, which resolves as above. ### Static-member / value-read refs — dart branch (:4759-4767), STATIC_MEMBER_LANGS:346 Called from the body walker only (:5218). The DART-SPECIFIC branch (the shared MEMBER_ACCESS_TYPES path is never reached — it returns first): node.type === `selector` AND it has NO `argument_part` child AND the receiver (dartReceiverOf: the previous named sibling past comments) is an `identifier` matching `/^[A-Z][A-Za-z0-9_]*$/` → `references ` from the enclosing symbol at the **IDENTIFIER's (receiver's) position** (pushStaticMemberRef :4800). Pins: - `ConfigT.setting;` → references ConfigT (value read). - **`ConfigT.load()` → references ConfigT TOO** — the `.load` selector has no argument_part (the args are the NEXT selector) and the dart branch has NO callee-of-call skip → every capitalized-receiver method call double-emits (references + calls). PRESERVE — this is the single biggest ref-volume quirk on real repos. - `util.Config.load()` → nothing (first selector's prev = lowercase `util`; later selectors' prev = selectors). - `this.x` → prev is a `this` node → nothing. Case patterns (`case ColorT.blue:`) → `constant_pattern > qualified` shape, no selector → NOTHING (pinned gap). Cascade sections → no selector → nothing. visitNode contexts → never called; initializers are body-walked, so a read written in one emits from its constant / class / file. ### Type-annotation references — dart ∈ TYPE_ANNOTATION_LANGUAGES (:5753), the dart branch (:5819-5833) is LIVE For every function/method node, extractTypeAnnotations takes the DART path: sig = node; if node.type === 'method_signature' → sig = first inner function/getter/setter/constructor/factory_constructor signature (**?? node — operators fall back to the method_signature itself**). Then `extractTypeRefsFromSubtree(sig)` (:6090) — one `references` ref per `type_identifier` LEAF in the whole signature subtree, skipping BUILTIN_TYPES (:5768-5782), at each leaf's position. Consequences (pinned): - Return types AND param types in one sweep: `void render(CanvasT c)` → references CanvasT; `List listRet(Map m)` → **references List** (List is NOT builtin!), WidgetT, **Map** (not builtin), WidgetT. - BUILTIN suppressions that matter for dart: `int`, `double`, `String` (capital-S — the Scala row), `Boolean`, `bool`, `float`, `long`, `char`. **NOT suppressed (noise refs, PRESERVE): `num`, `dynamic`, `Object`, `List`, `Map`, `Set`, `Future`, `Stream`, `Iterable`, `T`-style generic params.** `void` is a `void_type` node → structurally silent. - Generic declarations self-reference: `T generic(T v)` → **references T ×3** (return + the `` type_parameters + the param — all type_identifier leaves in the signature subtree). - Prefixed types: `other.OtherClass` → references `other` AND `OtherClass` (two leaves). - Ctors: bodied named ctor (method_signature > constructor_signature) → sig = constructor_signature → param types emit (`Widget.named(WidgetT w)` → references WidgetT); `this.`-params (constructor_param) hold no type_identifier → nothing. Bodiless declaration-wrapped ctors: extractMethod runs on the bare constructor_signature → sig = node → same; so do `const` ctors. A redirecting factory walks its named children one by one and skips a DIRECT type_identifier that is not UpperCamel (`/^[_$]*[A-Z]/`): after `=` those are an import prefix (`= p.Impl`) or the target constructor's name (`= _$QuestionImpl.fromJson`, where `fromJson` is a type_identifier) — `references _$QuestionImpl` only. - Getters: references from the getter's TYPE (suppressed if builtin — `int get area` → nothing; `WidgetT get w` → references WidgetT). - extractVariableTypeAnnotation (:6074) needs a `type_annotation` child — **no such node type in this grammar → dead**; the body-walker `variable_declarator` branch (:5230) — no such node type → dead. Local `WidgetT w = …;`, field types, `is`/`as` types, collection type args in bodies → **NO refs** (pinned). ### Decorators — dart annotations DO emit `decorates` via the SIBLING scan Dart annotations are `annotation` (with `name:` field; args form has an `arguments` child) or `marker_annotation`-free — probed: both `@override` (bare) and `@Deprecated('x')` (args) are node type `annotation`, PRECEDING SIBLINGS of the function or method they decorate (inside program / class_body); a class-like declaration's annotations open its own node instead (scan #1). extractDecoratorsFor (:4897) is called for classes (:1710), functions (:1599), methods (:1819) — NOT for hook-minted constants, enums(!), or type aliases (extractEnum/extractTypeAlias never call it — an annotated enum emits nothing). Mechanics for dart: - Scan #1 (direct children :4976-4988): annotations are never children of the signature → inert for functions and methods (no `modifiers` node either). A class-like declaration is different: class, mixin, extension and extension type nodes START at their first annotation, which is the node's own leading child, so scan #1 emits it (`@immutable class Foo` → decorates `immutable`). Enums and typedefs are built the same way, but nothing scans them. - Scan #2 (preceding siblings :5002-5023): walk BACKWARD from the declaration; `annotation` is in the accepted set (:5017); step over `comment` / `documentation_comment` siblings (since 2026-10-06, below); stop at the first other non-annotation sibling. consider(): target = first namedChild of accepted types → the `identifier` (`override`, `deprecated`, `pragma`, `immutable`, `Deprecated`) or `scoped_identifier` (`meta.immutable`, `Foo.named`, `p.Foo.named`) → `<`-strip + last-`.`-segment (`@meta.immutable` → `immutable`, `@p.Foo.named(…)` → `named`; the strip runs first, so `@Foo.named(…)`, which parses with an ERROR inside the name, → `Foo`) → **`decorates` ref {from the decorated node, name, line/col of the ANNOTATION node, i.e. its `@`}**. With-args annotations emit their NAME; the argument expressions are never visited (no refs from `@Deprecated('use other')`'s string). - **Stacked annotations emit in REVERSE source order** (the backward walk): `@Deprecated('x')\n@pragma('vm:entry-point')\nvoid f()` → decorates `pragma` FIRST, then `Deprecated` (pinned). - For a method: the previous member's function_body (or any declaration) breaks the chain correctly. (The docstring walk steps OVER annotations since 2026-10-06 — §Docstrings.) - **Comments between the annotations and the declaration are stepped over** (since 2026-10-06). An annotation always belongs to the next declaration, so dart.ts sets `decoratorStepOverTypes: ['comment', 'documentation_comment']`, which scan #2 skips, and the kernel's `extract_decorators_for` skips the same `DECORATOR_STEP_OVER` list. No other language sets one. Until then the comment ended the scan and the annotation above it attached to nothing. Now these all attach: `@override` `// ignore: must_call_super` `void f()`, `@x` `/// Doc below.` `void f()`, `@x /* c */ void f()`, a comment on the annotation's own line, and comments between stacked annotations (still in reverse order). Pinned in TortureAnnotatedComments.dart and dart-annotation-comments.test.ts. - Still ends the scan, comments or not: a field's `declaration`, a top-level variable, an import, the previous member's body (`void a() {} // note` `@x void b()` gives `b` only `x`), a class-like declaration. - Class-likes are unaffected. Their leading annotations are scan #1's, which reads every child, comments among them included. - Validation (bloc @b9be1e2 / riverpod @4ba1be2, kernel loaded, on top of the annotation resolution below): `decorates` refs +7 / +54, edges +0 / +16, nothing else in the dumps changed. bloc's 7 are all `@override`. riverpod's 54 (on 48 declarations) are `@override` 32, `@internal` 6 and `@riverpod` 16 (lint fixtures and one website snippet), and each `@riverpod` links `const riverpod`. Kernel and wasm full-index dumps are byte-identical, and the parity sweeps show 0 diffs. - The AST scan's 55th riverpod site is `@override` `// ignore: …` on a FIELD, which no extractor records. A scan that also counts annotation → comment runs INSIDE class-like nodes finds riverpod's 24 `@riverpod` `// ignore: …` `class X` lint fixtures and bloc's `@JsonEnum(…)` `/// …` `enum`. Scan #1 already reads those (an enum's, never: see above). - Members with no body (declaration-wrapped ctors, const ctors, redirecting factories, abstract and `external` members): the signature's PARENT is the `declaration`, so until 2026-10-06 the backward scan ran over the declaration's own children (the signature is namedChild(0) → declIdx 0 → nothing scanned) and an annotation before the declaration attached to NOTHING (`@visibleForTesting Foo._();`, `@protected void m();`). Now scan #2 starts at getDeclarationWrapper's `declaration`: same backward walk, same reverse order, and a field's `declaration` in between still stops it. Annotated fields themselves emit nothing (no extractor runs). Validation (bloc @b9be1e2 / riverpod @4ba1be2): +10 / +186 `decorates` refs, all SDK or package:meta names (`useResult`, `override`, `internal`, `visibleForTesting`, …), so no edges; before #2380, riverpod's 23 new `@internal` refs on `X.internal(…)` ctors resolved to the ctor itself. - **Resolution is TS-side and Dart-specific** (since 2026-10-06): resolveOneInner hands every Dart `decorates` ref to name-matcher.ts's `matchDartAnnotation` before any other strategy, and nothing falls through. A Dart annotation is a const variable or a const constructor call, never a method, getter or function, so the shared decorator ranking (findBestMatch: +25 function/method, +15 class, nothing for a constant) never sees one. That ranking sent riverpod's 506 `@riverpod` to the analyzer's extension getter `RiverpodAnnotatedAnnotatedNodeOfX::riverpod` (0.4) instead of `const riverpod = Riverpod();`. - The matcher re-reads the written form from the `@` in comment-blanked source. It is a call only when `(` follows on the same line, so a record return type on the next line is not taken for arguments. - `@x`: on a member, the enclosing type's own static `constant` / `enum_member` `x` (a class-like's own annotations stand outside its body); else a top-level `constant` the library can see (#2386's model, the own library shadowing imports). `@X(…)`: a visible class `X`, not an extension. - `@p.x` / `@p.X(…)`, when `p` is an import prefix (dart-libraries.ts `dartImportPrefixes`, the imports of the file's whole library): the same through that prefix. Otherwise `@T.x` → T's own `constant` / `enum_member` `x`, and `@T.named(…)` → T's named constructor (#2380's `isDartConstructor`); `@p.T.x` / `@p.T.named(…)` likewise through `p`. - A sole candidate links at 0.9 (`exact-match`, or `qualified-name` through a type). Several (only where the library model can't establish the library) fall back to findBestMatch's proximity at 0.7 / 0.4. - Not modelled: a `final` is not told apart from a `const` (compiling code never annotates with a visible `final`), nor a typedef used to call a constructor. - Validation (main 99246848, kernel loaded): bloc 0 edges changed (no annotation there names a project declaration); riverpod exactly the 506 `@riverpod` edges retargeted to `const riverpod` at 0.9, every other `decorates` edge (`$internal`, `Riverpod`, `Dependencies`, …) identical, nodes, refs and files identical. Every extracted annotation in both repos is written with one name, so the prefixed and `T.x` forms are pinned by dart-annotation-targets.test.ts only. ### Docstrings (tree-sitter-helpers.ts:95-127) — dartdoc is KEPT, both forms Dart comment node kinds: `comment` (`//` and `/* */`) and `documentation_comment` (**`///` AND `/** */`** — the block form is scanner-produced, external `_documentation_block_comment`). BOTH kinds are in getPrecedingDocstring's accepted set → **`///` runs, `/** */` blocks, and plain `//` comments all become docstrings and accumulate together** (pinned: `/// Line doc kept.` + `// Plain comment also kept?` → joined two-line doc; `/** Block dartdoc kept. */` → kept). cleanCommentMarkers: the `/*`-open strip + `^\/\/[/!]?\s?` + `^\s*\*\s?` gm strips fire — **all `gm` strips ride `js_multiline_strip` in docstring.rs (#1329 CRLF semantics) — call the shared code, port nothing**. DOCSTRING_WRAPPER_TYPES contains no dart kinds → no anchor climbing; instead `docstringFor` starts from getDeclarationWrapper's `declaration` for a member with no body (until 2026-10-06 those looked inside the wrapper and found nothing, e.g. `const BlocProvider.value(…)`'s dartdoc; validation added 32 docstrings on bloc and 126 on riverpod, none changed or removed). **The walk steps OVER `annotation` siblings** (since 2026-10-06). Until then an annotation between the comment run and the declaration ended the walk: `/// Builds it.` `@override` `Widget build(…)` lost its dartdoc, the dominant real-world loss since `@override` is everywhere. Now dart.ts sets `docstringStepOverTypes: ['annotation']`, which docstringFor hands to getPrecedingDocstring's `stepOver`. The kernel's `docstring_of` calls docstring.rs's `preceding_docstring_stepping_over(…, &["annotation"])`. `preceding_docstring` and every other language keep the empty list. - Stepped over: stacked annotations, multi-line ones (`@Deprecated(` … `)`), inline ones (`/* c */ @x void f()`, freezed's `@optionalTypeArgs TResult maybeMap…` on the declaration line), and blank lines, which are not nodes. This works from a `declaration` wrapper too. - Comments on either side of an annotation keep the join semantics: `/// a` `@x` `// b` `void f()` → `a\nb` (was `b`), and `/// Doc.` `@override` `// ignore: …` → `Doc.\nignore: …`. - Still ends the walk: any other named sibling, such as the previous member's `function_body`, a field's or a bodiless member's `declaration`, a top-level variable (`static_final_declaration_list` / `initialized_identifier_list`), an `import_or_export` or `part_directive`, or a class-like declaration. A comment trailing the previous member on its line (`void a() {} // note`) is collected, as it already was for a member with no annotation. - Class-like declarations needed nothing. A class, mixin, extension, extension type, enum or typedef node starts at its first annotation (§Decorators), so the dartdoc above the annotations is the node's previous sibling and was always kept. A comment written BETWEEN such an annotation and the keyword (`@JsonEnum(…)` `/// The state…` `enum LinterRuleState`) is inside the node and is NOT read (pinned). bloc has 1 such dartdoc, riverpod none (23 `// ignore:` lines and 1 plain comment). A member's comment below its annotation IS read: it is the signature's previous sibling. - Local functions get no docstring with or without annotations: the signature sits in `local_function_declaration > lambda_expression`, with no previous sibling. - Validation (bloc @b9be1e2 / riverpod @4ba1be2, 3f96f80f plus the declaration-wrapper fix, kernel loaded): docstrings +26 / +1,072, changed 0 / 9 (each a `// ignore:` line below the annotation, now joined after the dartdoc above), removed 0. Everything else in the dumps (nodes, edges, refs, files) is byte-identical; kernel and wasm full-index dumps are byte-identical; the parity sweeps show 0 diffs. bloc's 26 are all hand-written dartdoc, mostly behind `@mustCallSuper` / `@override`. Of riverpod's 1,072, 667 runs hold a dartdoc (401 in generated `.freezed.dart` / `.g.dart` files, 266 hand-written) and 405 are plain comments only (`// ignore: riverpod_lint/…` in the lint fixtures, the website's snippet markers). Docstrings attach to: functions, methods (incl. `` operators), classes/mixins/extensions, enums, type aliases. NOT to: hook-minted constants (extra carries only signature — pinned drop), enum members, imports, the file node. No comment-gluing into import extents (probed — import_or_export ends at the `;`). ### Value-reference edges (:398-931) — dart IS in VALUE_REF_LANGS (:401) Port the full machinery (crib java.rs/kotlin.rs): `CODEGRAPH_VALUE_REFS=0` kill; MAX_VALUE_REF_NODES = 20,000 caps both DFS passes; isGeneratedFile skip (`.g.dart` and friends!). - **Targets** (captureValueRefScope:735): kind constant|variable, name ≥3 chars AND `/[A-Z_]/`, parent id prefix ∈ {file:, class:, module:, struct:, enum:}. Dart mints ONLY `constant` (kind variable never occurs) → targets = hook constants under file: or class: (an enum-scoped `static const` would ride enum:). `lowercase_const`/`plain`/`low` (no capital, no `_`) → not targets; `kLimit`/`typedTop` (embedded capitals) → targets. - **Reader scopes** (:764): every function/method/constant node (dart has no variable nodes). **The Dart sibling-body pull (:883-892) is LIVE and load-bearing**: a function/method reader scope's node is the SIGNATURE — its `nextNamedSibling`, when of type `function_body` (or `block`), is pushed into the reader DFS; without it every method/function body read would be invisible. Constants' reader subtree is the static_final_declaration itself (initializer reads: pinned `DERIVED_VAL → SHARED_MAX` edge); their nextNamedSibling is another static_final_declaration (multi-lists) → not pulled ✓. Duplicate local-fn nodes (§double-walk) are ALSO reader scopes — same reads, both scopes (dedupe is per (scope,target), scopes differ → **duplicate value-ref edges from the twin scopes when a local fn reads a target** — include the shape in a fixture if a local fn reads a constant). - **Shadow prune** (:803-878): the dart declarator cases (:844-850) — `static_final_declaration` (the target itself), `initialized_identifier` (fields/top-level vars), `initialized_variable_definition` (locals) — each bumps its first `identifier`-typed namedChild. **Uninitialized locals (`int DERIVED;`) still bump** (pinned prune); a method-local `final SHARED_MAX = 1;` prunes the file-wide target (pinned). **`assignment_ expression` is NOT a prune case** (the :829 `assignment` case is python's — the swift-lesson check comes out DEAD for dart): an assignment-only rebind never bumps, and `low = 5;` style writes are invisible to the prune. fileScopeValueCounts: conditional double-defs don't occur in dart (const redefinition is illegal) — counts are 1 each. - **Emission** (:880-930): per reader scope, stack-DFS (namedChildren pushed in order, POPPED — reverse-source-order visitation; edge order follows); match node type `identifier` (:907 — `constant`/`name`/`simple_identifier` never occur in dart trees). Any identifier text mapping to a live target emits — including the member half of `Table.COL_LIMIT` navigation (pinned) and `${SHARED_MAX}` interpolations (template_substitution > identifier). **`$SHARED_MAX` (no braces) is `identifier_dollar_escaped` — NOT accepted → no read** (pinned asymmetry). Skip self-id + same-name, dedupe per (scope,target) → EDGE {kind:'references', metadata:{valueRef:true}}, appended AFTER all other edges (last rows of every dump). ### Function-as-value capture (#756) — DART_SPEC (function-ref.ts:310-320) idTypes = {`identifier`} (bare identifiers ARE candidates). dispatch: `arguments` → args; `assignment_expression` → rhs (field `right`); `pair` → value (field `value`); `list_literal` → list; `static_final_declaration` → varinit (NO field → last-named-child rule :471-487: requires ≥2 named children; the name-field guard is inert — no `name` field on static_final_declaration). layers: `argument` → null (fan out). NO special, NO unwrap, NO ungatedModes, NO addressOfOnly. Pins: - `register(topLevel)` → candidate via arguments→argument→identifier ✓. - **Named arguments are NOT captured**: `reg(cb: onlyNamed)` → the child is `named_argument` — not in layers, not an idType → NOTHING (pinned, `namedarg.dart`). Flutter's `onPressed: handler` idiom is therefore invisible — bug-for-bug, do NOT "fix" in the port (candidate future accuracy PR: add `named_argument`/`label` handling TS-side first). - `obj.cb = assigned` → rhs capture ✓; param-storage skip (:425-443) compares the LHS's trailing identifier to the rhs text — `this.cb = assigned` kept (cb ≠ assigned), a hypothetical `this.cb = cb` skipped. `cb = selfStore` where selfStore is a PARAMETER shadowing a same-named function → captured + gate-passed (false positive, PRESERVE — pinned). - `[topLevel, blockDoc]` list ✓ (locals' list_literals capture too — the dispatch fires wherever the node is walked); `{'k': topLevel}` pair ✓; `final aliasTop = aliased;` top-level/static → varinit bare-identifier ✓ (from the constant `aliasTop` — the hook walks the declaration with it pushed); a LOCAL `final alias = topLevel;` → initialized_variable_definition NOT in dispatch → not captured. - obj.method member values (`final g = obj.method`) → the last child is a selector → normalizeValue [] → nothing (no member special for dart). - Capture points: visitFunctionBody:5137 (bodies, and every initializer the hook walks — from the constant / class / file, lambdas included), visitNode:990 (the §double-walk twins from file/class scope), scanFnRefSubtree (only a `static_final_declaration` whose constant wasn't minted — the scan skips what the hook walked, so nothing is captured twice; it halts at `function_expression` :609). - **Flush gate (:639-728): effectively "defined in this file" ONLY** — dart import refs are URIs (`package:foo/util.dart`, `dart:async`) which match neither SIMPLE_NAME nor QUALIFIED_IMPORT (`:` and `/` excluded) → importedNames is always EMPTY for dart. definedHere = same-file function/method NAMES — which includes single-letter method names (a method `a` gates any bare arg `a` — pinned false positive, PRESERVE) and ``. No `this.`/`::` forms are ever produced (no special) → every candidate takes the definedHere gate. Dedupe `${fromNodeId}|${name}` → survivors as {referenceKind:'function_ref'} (wire 200) appended after all walk refs — in-scope candidates first, then the double-walk file/class twins (capture order). ### visitFunctionBody (:5129-5286) — dart rows - maybeCaptureFnRefs (:5137) per node. - callTypes (:5143) — empty, dead. INSTANTIATION_KINDS (:5145) → new_expression → extractInstantiation (children still recursed — a nested `new Foo(bar())` emits both instantiates and the inner call). - **extractBareCall (:5159-5173)** — §Calls. No skip/return → children always recursed after (inner chain selectors each get their own shot). - extractStaticMemberRef (:5218) — §Static-member. - variable_declarator branch (:5230) — dead (no such kind). - Nested `function_signature` (:5245) → named → extractFunction → local functions as `function` nodes (QN nested via stack), THEN the enclosing walker also re-walks the local's sibling body (§double-walk pass 2b) and the outer visitNode revisit re-extracts it (pass 2a). - classTypes/enumTypes in bodies (:5255/:5268) — grammatically impossible in dart (no local classes/enums) → dead. - Recursion is transparent through if/for/while/switch statements (but case PATTERN internals use pattern node kinds — `constant_pattern > qualified` — where neither extractBareCall nor static refs match → enum reads in case labels are invisible, pinned), try/catch, await/unary, throw, string templates, function_expression lambdas (calls attribute to the enclosing symbol), collection literals (`[…]` type args silent). ### Misc shared paths - Positions: `line = startPosition.row + 1`, `column = startPosition.column` — **UTF-16 code units** (textutil::col16), as are startIndex/endIndex substrings (getNodeText everywhere: signatures, constant initializers `.slice(0,100)`, import signature `.trim()`). - extract() wrap (:454-568): file node → (no package hook → no namespace) → root visitNode walk → flushFnRefCandidates → flushValueRefs → pop. Table order: nodes in creation order (INCLUDING double-walk duplicates); contains edges interleaved with creation; value-ref EDGES appended last; walk-order refs then function_ref refs at flush. Store/harness are rowid-order-sensitive — reproduce exactly. - **CRLF: probed clean end-to-end** — the CRLF-converted torture fixture extracts BYTE-IDENTICALLY to LF (`extract-crlf.txt` diff-0 after filename normalization). dart.ts has NO multi-line regexes (`/<[^>]*>/g`, `/^[A-Za-z_]\w*$/`, `/['"]/g`, `/^[A-Z]/` are all single-token); the only CRLF-sensitive shared path is cleanCommentMarkers' `gm` strips → `js_multiline_strip` in docstring.rs. Multi-line signature slices and ≥100-char initializer captures include raw `\r` bytes on CRLF sources — identical on both arms by construction (same source slice). - Defer policy: per-file `has_error()` → `defer:`; expected incidence per §Grammar prep (3.4–20.7%); `--max-deferral 0.3`. No phantom class found — but defer on the FLAG regardless (never on ERROR-node presence). - Language display name `Dart` (grammars.ts:626) — cosmetic only. ## Frameworks & synthesis consumers (stay TS-side — pin the walker's output contract) - **No dart framework resolver, no dart callback-synthesizer pass, dart ∉ CC_LANGUAGES** — grep-verified. Nothing consumes dart node EXTENTS beyond the generic paths; the endLine extension still matters for context slices and explore output. - **Resolution contracts the walker's output feeds** (behavior to hold steady, verified at 45a53eb): - `CONSTRUCTS_VIA_BARE_CALL` (name-matcher.ts:918) — bare capitalized `calls WidgetT` refs resolve as constructions; depends on ctor calls staying PLAIN calls refs (dart emits `instantiates` ONLY for `new_expression`). - `matchDottedCallChain` (name-matcher.ts:2147-2166, dart listed) — consumes the `Foo.create().run` re-encode + the method nodes' `returnType` (extractDartReturnType's class-name-for-ctors rule is what makes `WidgetT.named(3).chainTail()` resolve — #750/#645). - Local-variable receiver inference (name-matcher.ts:1202-1209) — regexes over raw source (`var lg = Logger(`, `Logger lg,`) — no extraction dependency beyond node names. - CHAIN_LANGUAGES (resolution/index.ts:42) includes dart — same inputs. - `matchMethodCall`'s bare-fn-only carve-out EXCLUDES dart (:226-235) — bare fn-ref names may resolve to METHODS (implicit-self) — walker just supplies names. - rnCrossPlatformEdges / expo / fabric: dart NOT in their language sets. ## Parity mechanics (all have bitten before) - **Emission order** per §Misc + §double-walk: file → source-order walk (per construct: node + contains → for classes: extends/implements → decorates → members; per function/method: node + contains → type-refs → decorates → body refs → then the sibling-revisit twins) → function_ref refs (in-scope then twins) → value-ref EDGES last. - **generateNodeId inputs**: (filePath, kind, name, startRow+1). Names: operators are ``; ctors are the ctor NAME (`named`, `_`, `create`); anonymous extensions are the ON-type text; imports are the URI; enum members the constant name at the enum_constant's line. **Duplicate local-fn nodes share an id — emit both rows.** - **UTF-16 columns/slices** everywhere; non-ASCII lives ONLY in comments/strings (identifiers are ASCII by spec — a non-ASCII identifier ERRORS and defers). The torture fixture's unicode comment + string pin the column math. - **CRLF**: fixture variants derived in-memory (kernel-tsjs-parity pattern); expected: byte-identical output modulo the source bytes themselves. - **Defer policy**: has_error → defer, `--max-deferral 0.3`, expected counts shelf 10/99, bloc 21/616, flutter ~1340/6465 (flutter HEAD drifts — re-sweep at port time; the RATE is the signal). - **node_ids self-check**: compare ID STRINGS; do NOT dedupe (double-walk). ## Gates (per plan §5, adapted to the byte-copy vendor) - **Vendor gate (replaces the usual bump gate):** wasm byte-copy + `VENDORED_WASM_LANGS += 'dart'` + kernel C vendor + langs.rs + `GRAMMAR_LANGUAGES += 'dart'` — full suite green; one smoke dump-diff (old resolution path vs vendored path) on shelf, expected byte-identical (same bytes); kernel-grammar-parity proves C-build ↔ wasm table identity. - **Torture fixtures** per `## Fixtures to build`, exercised by a new `__tests__/kernel-dart-parity.test.ts` (+ CRLF variants in-memory). - **Parity sweeps** (`scripts/kernel-parity.mjs `, order-sensitive full-object, **`--max-deferral 0.3`**): - `svy-dart/gate-repos/shelf` (small, 99 files — server dart; re-clone dart-lang/shelf if gone) - `…/bloc` (medium, 616 files — Flutter widgets + pure-dart packages + generated-file population) - `…/flutter` (large, 6,465 files — the framework; the deferral-heavy arm) (all three cloned fresh at survey; re-clone public OSS — agent-eval policy). Expect 0-diff on every non-deferred file and ~the §Grammar-prep deferral counts. Then **full-init dump-diffs byte-identical** (kernel arm vs `CODEGRAPH_KERNEL=0`, `scripts/dump-graph.mjs`, cmp) on the same three. - **Census spot-check** (double-walk belt-and-suspenders): after a bloc kernel-arm index, `select kind, count(*) from nodes where language='dart' group by kind` must equal the wasm arm EXACTLY — the duplicate-node rows are where a "helpful" dedupe would silently diverge before the dump gate runs. - **Suite**: torture + CRLF + a defer fixture (empty-object-pattern file — kernel defers, wasm output served) + the `library;` defer shape; full suite ×2 green with `CODEGRAPH_KERNEL_EXPECT=1`. - **`DEFAULT_ROUTED += 'dart'`** (kernel/index.ts:37) only after ALL of the above; changelog rides the existing kernel entry. - Post-route perf sanity: all three gate repos ride the raw path (§arch-2); a mixed Vue+dart repo is the decoded-path smoke check. The dart speedup lands on ~79–97% of files (deferral costs). ## Fixtures to build 1. `__tests__/fixtures/kernel-parity/torture.dart` — seed from the survey's `svy-dart/torture.dart` (231 lines, parses clean; its `extract-torture.txt` is the expected-output pin). Inventory by branch: imports (dart:, package: with `as`+`show`, export with `hide`, **deferred → invisible**, part → invisible — an import since 2026-10-06, pinned with every directive form in TortureDirectives.dart, and `part of` by library name in TorturePartOfName.dart); doc shapes (`///` run, `/** */` kept, `//` kept, a `///` above an annotation); annotations (bare, with-args, stacked → reverse order, on class); top-level constants (CAPS/lowercase/multi/typed/derived, sig truncation ≥100 chars); top-level var/typed-var/getter/setter (all invisible); async vs async*/sync* (isAsync true/false/false); returnType matrix (builtin, generic container, nullable, prefixed → `other` bug, `T` leak, void); signature matrix (params verbatim incl. named/optional/defaults/ function-typed, getter type-only, setter params-only, generic-return `Future ()`); external fn (position after keyword); the full ctor set (unnamed skipped; named bodied — no sig; named bodiless — `()` sig + initializer-call invisibility; factory; **const factory + const ctor invisible**); operator → `` method; private `_` names → visibility; class with extends+with+implements (ref kinds/order) + with-only class; mixin `on` (nothing) + mixin implements; named + **anonymous extension (class named after the ON type)**; abstract class (bodiless members → kind `function`; abstract getter invisible); sealed/base modifiers (plain classes); enum (simple + enhanced: ctor'd constants at constant positions, args unwalked, `with` GAP vs `implements` ✓, members after `;`); typedef ×3 (modern/legacy/generic — zero refs until 2026-10-21, the aliased types since; every form is in TortureTypedefs.dart); extension type (container invisible, **members leak as bare `function`s**); the FULL call matrix (§Calls rows verbatim — incl. cascade invisibility, `?.`, const-object args, chain re-encodes, interpolation calls, `$x` vs `${x}`); static reads (`X.member`, `X.method()` double-emission, 3-segment nothing, case-pattern GAP); local shapes (locals/uninit/lambdas/local fns — pinning the §double-walk interleave exactly); fn-refs (positional arg, **named-arg NOT captured**, rhs + param-storage-skip + shadow false positive, list, pair, top-level/static bare-identifier varinit, local alias not captured, undefined-name gated, file/class twins); value refs (targets CAPS + kName; readers via sibling pull; constant→constant; `${X}` read + `$X` non-read; `X.member` member-half read; local shadow prune; uninitialized-local prune; assignment non-prune; lowercase non-target); a unicode comment + string before a symbol (UTF-16 columns). 2. **CRLF variants** derived in-memory (kernel-tsjs-parity pattern). 3. **Defer fixture #1**: `switch (x) { Init() => 1, _ => 0 }` (empty object pattern) — kernel defers (`defer:`), wasm serves the recovery output. 4. **Defer fixture #2**: `library;` header file — same contract. 5. **Generated-file fixture** (`x.g.dart` naming) — extraction normal but NO function_refs / value-refs (the isGeneratedFile skips). ## Probe artifacts (session scratchpad `svy-dart/`) `cst-dump.cjs` (full CST + field labels via fieldNameForChild) + `mini-cst.txt`/`probe2-cst.txt`/`probe3-cst.txt`/`probe4-cst.txt`/ `torture-cst.txt`; `extract-probe.cjs` (the REAL dist extractor — nodes/edges/refs dumps) + `extract-mini.txt` (imports/deferred-import, ctor set, operator ``, enum shapes, mixin/extension, call matrix), `extract-probe2.txt` (THE double-walk interleave pins, local fns, async*/sync*, annotations, value-ref prune, top-level shapes), `extract-probe3.txt` (signatures verbatim, num/dynamic/Object refs, external position, redirecting/const factories, lambdas, decorates order), `extract-probe4.txt` (block dartdoc, extension-type leak, record-misparse rescue, this/super calls, case-pattern gap), `extract-probe5.txt` (value-ref matrix, prefixed calls, docstring drops, unicode), `extract-torture.txt` + `extract-crlf.txt` (the master ground truth, LF + CRLF byte-equal); fixtures `mini.dart`, `probe2-5.dart`, `namedarg.dart`, `fnref2.dart`, `fnref3.dart`, `torture.dart` (+ `torture-crlf.dart`); `probe-dart3.cjs` + `dart3-errors.txt` (46-construct error battery); `error-sweep.cjs` + `errors-{shelf,bloc,flutter}.txt` + `flutter-err-files.txt` (incidence + per-file lists); `error-diag.cjs` (first-error context — the class minimizations); `tree-sitter-dart/` (the d4d8f3e commit clone — vendor-file shas recorded in §Grammar prep). Scratch dirs are throwaway — re-derive from this doc if gone.