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).
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.
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).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.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.src/parser.c 5a42b47abb4d494f125dbdee9138979248041689b1aa36355550fa3e28dcb8b8src/scanner.c 07a7b7818b175e9460523e705dd88d20f7b5141bac95c593d4426e6d52284996src/tree_sitter/parser.h 180b893c8734778fd32f372dfbc27bd6ad1cd2221f26150b31256ff6716320d2src/tree_sitter/alloc.h b29c1c9fb7cc82f58c84b376df1297d6e2737a1d655fd356db0859e3c29c2feasrc/tree_sitter/array.h 5bdf6ed1a78e3409fd443e085ca967a64c188a5d082aaf7f819bccd53a471c94tree-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:
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.
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.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.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.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).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)..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.packageTypes hook — library foo.bar; is invisible); interfaces
don't exist as a dart concept (abstract classes are kind class).Types: functionTypes=function_signature; classTypes=
class_definition; methodTypes=method_signature,
constructor_signature; interfaceTypes=[] ; structTypes=[];
enumTypes=enum_declaration; enumMemberTypes=enum_constant; typeAliasTypes=type_alias; importTypes=import_or_export; 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. 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):
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.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).
initialized_variable_definition: name: + one-or-more value: fields
(the RHS postfix chain is FLATTENED into multiple value-fielded siblings).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 — NEITHER is
in methodTypes → const ctors and const factory X.r() = Impl; are
INVISIBLE (no node, no refs). Fields are declaration >
(static)? (final|const|type|var) > initialized_identifier_list |
static_final_declaration_list (§Constants).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):
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 }).
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) {…}; return true). The
dispatcher then runsscanFnRefSubtree(node, 0)and never descends →
§Function-as-value for what still gets captured. Everything else → false.
**Reality of the node type (probed,probe2-cst.txt):**
static_final_declaration= afinal/constdeclaration WITH an
initializer that is **top-level** (const SHARED_MAX = 10;,final
typedTop = compute();, typed or untyped, incl. multi-declarationsfinal
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);). Instancefinal untyped = 5;/final int size;/ any
var/typed-var member →initialized_identifier→ **NO NODE** (dart
emits zerofieldnodes, ever). Top-levelvar 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).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).type_identifier → text, .replace(/<[^>]*>/g,''), .trim(), last
.-segment, must match /^[A-Za-z_]\w*$/. Pinned: Future<void> →
Future (type_arguments is a SIBLING node, so the strip is a no-op);
List<WidgetT> → 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.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).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<void> 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.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'.'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).'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.named, create, _); unnamed ctor or non-ctor → undefined (falls
to extractName §below).library_import >
import_specification > configurable_uri > uri > string_literal (else the
same chain under library_export), quotes stripped via
.replace(/['"]/g,''). Returns {moduleName, signature} (no handledRefs)
→ import NODE (name = the URI, e.g. package:torture/other.dart,
dart:async, src/reexported.dart) + the generic imports ref
(tree-sitter.ts:3183-3194) {from: file node (no namespace exists), name =
URI, line/col of the import_or_export node}. 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/part of/library directives
are different node types (part_directive, part_of_directive, library_name)
in NO type list → invisible. None of the TS/py/rust/php/ruby binding
emitters (:3197-3234) fire for dart.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).
45a53eb)| Node | Branch | Behavior |
|---|---|---|
| every node | visitNode hook first (:943) | static_final_declaration consumed (§Constants); handled → scanFnRefSubtree + 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 | plain type_alias node; getChildByField(node,'value') → null (no fields) → NO refs from the aliased type (typedef MapAlias = Map<String, WidgetT> emits nothing); returns false → children re-visited (function_type/formal_parameter_list children match nothing) |
import_or_export |
importTypes:1209 → extractImport:3170 | §Extractor config |
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<T>() → 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 hits methodTypes; initialized_identifier/list, constant_constructor_signature, redirecting_factory_constructor_signature, 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_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 |
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:
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).Pinned interleaves (extract-probe2.txt, transcribe as parity expectations):
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.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).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).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.
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.named L123→125; bodyShapes L60→96). Class/enum/etc. spans are their
own node extents.:: (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).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.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) → <anonymous>.<anonymous> — operator_signature is NOT in
the :148 inner list and methodsignature has no identifier children → the
fallback finds nothing → createNode('method', '', …) — the
name is truthy so the node IS minted (extractMethod has no <anonymous>
skip, unlike extractFunction:1549 which only guards the extractFunction
path — and even there <anonymous> only occurs for arrow/function
expression types, not dart signatures). Pinned: WidgetT operator +(…) →
method <anonymous> 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.
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.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 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).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 ✓.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.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.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<T> would ride the type_identifier + sibling
type_arguments shape → name Comparable).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.on
types are bare type_identifiers) — zero refs.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, const constructors, redirecting factories, extension_type
containers, part/part of/library/deferred imports. Initializer
side-effects: hook-consumed constants' initializers are NOT walked → no
calls/instantiates from them (only scanFnRefSubtree capture — §fn-refs);
initialized_identifier fields' initializers ARE recursed by visitNode but
only INSTANTIATION_KINDS fires there → int counter = 0; emits nothing,
but a field final w = new Widget(); would emit instantiates Widget from
the CLASS (and a top-level var w = new Widget(); from the FILE). No
static-member refs from any of these contexts (body-walker only).
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 |
generic<int>(5) |
args selector (type args ride argument_part) | bare generic |
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 |
ctor initializers (: size = seed()), enum-constant args (ok(200)), default param values, hook-consumed constant initializers |
never body-walked | NOTHING |
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.
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
previousNamedSibling is an identifier matching /^[A-Z][A-Za-z0-9_]*$/ →
references <identifier text> 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.
Class-field/constant initializers and visitNode contexts → never called.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):
void render(CanvasT c) →
references CanvasT; List<WidgetT> listRet(Map<String, WidgetT> m) →
references List (List is NOT builtin!), WidgetT, Map (not
builtin), WidgetT.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.T generic<T>(T v) → references T
×3 (return + the <T> type_parameters + the param — all
type_identifier leaves in the signature subtree).other.OtherClass → references other AND OtherClass
(two leaves).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.int get area → nothing; WidgetT get w → references WidgetT).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).decorates via the SIBLING scanDart 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 declaration they decorate (inside program / class_body).
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:
modifiers node either).annotation is in the accepted set (:5017); stop at the
first non-annotation sibling. consider(): target = first namedChild of
accepted types → the identifier (override, deprecated, pragma,
immutable, Deprecated) → <-strip + last-.-segment (@ui.Widget
style would strip to Widget) → decorates ref {from the decorated
node, name, line/col of the ANNOTATION node}. With-args annotations
emit their NAME; the argument expressions are never visited (no refs from
@Deprecated('use other')'s string).@Deprecated('x')\n@pragma('vm:entry-point')\nvoid f() → decorates
pragma FIRST, then Deprecated (pinned).declaration node — the
backward scan runs over declaration's children (constructor_signature is
namedChild(0) → declIdx 0 → no siblings scanned) → an annotation before
the declaration attaches to NOTHING. Annotated fields likewise emit
nothing (no extractor runs).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. An annotation between the comment run and the
declaration BREAKS the chain (pinned: /// Broken by annotation.
@deprecated void annotated() → doc undefined — the dominant real-world
loss since @override is ubiquitous). Docstrings attach to: functions,
methods (incl. <anonymous> 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 ;).
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!).
/[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.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).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.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).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 ✓.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 ✓
(pinned from=file / from=class); a LOCAL final alias = topLevel; →
initialized_variable_definition NOT in dispatch → not captured.final g = obj.method) → the last child is a
selector → normalizeValue [] → nothing (no member special for dart).function_expression :609, so lambdas
inside a constant's initializer don't leak candidates).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
<anonymous>. 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).new Foo(bar()) emits both instantiates and the inner call).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).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 (<Widget>[…] type args silent).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-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).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).Dart (grammars.ts:626) — cosmetic only.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).var lg = Logger(, Logger lg,) — no extraction
dependency beyond node names.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.<anonymous>; 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.--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).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.## Fixtures to build, exercised by a new
__tests__/kernel-dart-parity.test.ts (+ CRLF variants in-memory).scripts/kernel-parity.mjs <dir>, 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.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.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.__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); doc shapes (/// run,
/** */ kept, // kept, annotation-broken chain); 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 → <anonymous> 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); extension type (container invisible, members leak as bare
functions); 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).switch (x) { Init() => 1, _ => 0 } (empty object
pattern) — kernel defers (defer:), wasm serves the recovery output.library; header file — same contract.x.g.dart naming) — extraction normal but
NO function_refs / value-refs (the isGeneratedFile skips).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 <anonymous>, 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.