# C# kernel port (R7b) — the bug-for-bug checklist **Status: PORT COMPLETE (2026-07-20)** — walker `codegraph-kernel/src/csharp.rs`, all gates passed (parity sweeps 0-diff on serilog 211/216 / Newtonsoft.Json 914/945 / jellyfin 2104/2105 — every deferral matching §arch-6's predictions; full-init dump gates byte-identical ×3; kernel-csharp-parity suite + grammar row; suite ×2 under CODEGRAPH_KERNEL_EXPECT=1; DEFAULT_ROUTED += csharp). This doc remains the quirk reference for the walker. Survey basis: every TS-side branch a `.cs` file exercises, with file:line anchors as of **`f1ca991`** (HEAD at survey time, clean main). Every grammar-shape claim below was **probed against the vendored tree-sitter-c-sharp 0.23.5 wasm** (`src/extraction/wasm/tree-sitter-c_sharp.wasm` — note the underscore filename); probe scripts + raw outputs live in the survey session scratchpad (`/private/tmp/claude-501/-Users-colby-Development-CodeGraph-codegraph/0c11bda1-0b19-4fec-bcd9-d0cb4b2d6e8a/scratchpad/svy-csharp/`: `probe1-core.mjs`, `probe2-shapes.mjs`, `probe3-edge.mjs`, `census-wasm.mjs`, `error-incidence.mjs`, `blank-impl.mjs` + `probe*-out.txt`). Read WITH `docs/design/rust-kernel-migration-plan.md` (§0a recipe, §5 gates) and the rust/ccpp checklists (format precedents). **Blocking findings: none** — the vendored wasm is table-identical to the crates.io 0.23.5 crate (below), so no grammar bump is needed; grammar prep is a Cargo pin + parity-test entry only. **Grammar provenance (verified, not assumed):** - Vendored wasm landed in **PR #717** (commit `80db274e`, 2026-06-07 — the #237 primary-constructor fix), replacing tree-sitter-wasms' ABI-13 build. The commit message says "tree-sitter-c-sharp 0.23.5 (ABI 15)" but does NOT claim a parser.c sha-match, so this survey verified compatibility directly against the crates.io `tree-sitter-c-sharp-0.23.5` tarball (`src/parser.c` sha256 `0a2651e4…`, `src/scanner.c` sha256 `00920daa…`; upstream tag `v0.23.5` → commit `cac6d5fb`): - wasm ABI **15** == parser.c `LANGUAGE_VERSION 15`; - wasm `stateCount` **8053** == parser.c `STATE_COUNT 8053`; - wasm `nodeTypeCount` **533** == `SYMBOL_COUNT 530 + ALIAS_COUNT 3`; - wasm `fieldCount` **26** with a field-name set identical to the crate's node-types.json (accessors…value); - named node-kind set: wasm ⊆ crate exactly (the 9 crate-only names are node-types.json *supertypes* — `declaration`, `expression`, `pattern`, … — which never instantiate; zero real divergence); - the crate ships an **external scanner** (`scanner.c`, 12 external tokens) and the wasm embeds it — probed: interpolated raw strings (`$"""… {Interp(u)} …"""`), raw string literals, and verbatim strings all parse clean. - **Cargo plan:** add `tree-sitter-c-sharp = "=0.23.5"` to `codegraph-kernel/Cargo.toml` following the exact-pin comment block (the `=0.24.2` c/rust rows); crate symbol is **`tree_sitter_c_sharp::LANGUAGE`** (a `LanguageFn`, `.into()` like the other 0.23-era crates); register `"csharp" => Some(tree_sitter_c_sharp::LANGUAGE.into())` in `codegraph-kernel/src/langs.rs` + add `"csharp"` to `LANGUAGES`. The crate's build.rs compiles parser.c **and scanner.c** (both in the tarball) — no extra work. `__tests__/kernel-grammar-parity.test.ts` (id-by-id ABI + node-kind + field-table compare) is the real gate and needs a csharp row; **no wasm change and no TS-side behavior change** — the production wasm stays exactly as shipped since #717, so this port has NO grammar-bump-first step. ## Architecture decisions 1. **preParse stays TS-side — already hoisted, port NOTHING.** `csharpExtractor.preParse = blankCsharpPreprocessorDirectives` (languages/csharp.ts:26, #237) blanks `#if/#elif/#else/#endif` directive LINES (both branches of the guarded code are KEPT). The route point already applies it for kernel calls — `preParsedSource` (kernel/index.ts:82) names csharp explicitly — so both arms parse identical blanked bytes and the deferred-file memo reuses them (`takeDeferredPreParse`, kernel/index.ts:120; wasm fallback `sourceIsPreParsed`, tree-sitter.ts:499). **Never port the regex to Rust**: it is a JS `(?m)^`-anchored replace (`/^([ \t]*)#[ \t]*(if|elif|else|endif)\b[^\n]*/gm`) whose `^` matches after `\r` (JS multiline semantics) and whose `[^\n]*` EATS the `\r` of a CRLF line (probed: `"…\r\n#if X\r\n…"` blanks to `"…\r\n \n…"` — byte count preserved, `\r` becomes a space). Both are JS-semantics traps (the #1329 class) that the hoist makes moot. 2. **ASP.NET repos take the DECODED path, not raw buffers.** `aspnetResolver` (resolution/frameworks/csharp.ts:11, `languages: ['csharp']`, registered in frameworks/index.ts:62) has an `extract()` hook (line 133), and parse-worker.ts:93-100 forces any language with an applicable framework `extract()` onto the decoded `extractFromSource` path. detect() (csharp.ts:15) fires on `.csproj` AspNetCore markers, `Program.cs`/ `Startup.cs` shapes, or controller-source signatures — so web-app repos (jellyfin-class) decode per file while pure libraries (serilog, Newtonsoft.Json) ride the raw buffers-to-store transport. Don't chase a raw-path perf number on an ASP.NET repo and conclude the port is broken. 3. **The framework extractor itself needs NO port** — regex over raw source (§Frameworks below), runs in extractFromSource:6736-6758 after either arm. 4. **One walker module** (suggest `codegraph-kernel/src/csharp.rs`), registered in langs.rs; per-file `has_error()` → `defer:` like every walker. **java.rs is the skeleton** (§java.rs mapping below) — same namespace-node concept, same field/constant split, same static-member/value-ref/fn-ref chassis. 5. **`.cs` → `csharp`** at detectLanguage (grammars.ts:87), wasm filename `tree-sitter-c_sharp.wasm` (grammars.ts:31), vendored-set membership at grammars.ts:292, provenance comment at grammars.ts:252-261. No content sniffing, no dialects (`.cshtml`/`.razor` are language `razor`, a T3 standalone extractor — NOT this port). MAX_FILE_SIZE (1 MiB) and generated-file skips are orchestrator/TS-side and shared. 6. **Deferral expectations (measured on the gate repos, post-blank = what the kernel sees):** serilog **5/216 = 2.31%** (raw 8.33% — the blanking hoist halves it), Newtonsoft.Json **31/945 = 3.28%** (raw 6.88%), jellyfin **1/2105 = 0.05%**. Keep the sweep default `--max-deferral 0.1` — C# needs NO c/cpp-style 0.5 exemption. The residual error class is grammar-inherent both-branches-kept damage (`#if FEATURE_DEFAULT_INTERFACE` around interface members in serilog's ILogger.cs; half-expression `#if HAVE_DATE_TIME_OFFSET` guards in Newtonsoft's JsonReader.cs) — those files error on BOTH arms and defer by policy. C# 12 collection expressions (`[a, b]`) parse natively (probed) — the lone jellyfin error is unrelated. ## Extractor config (languages/csharp.ts — 163 lines, read it whole) Types: **functionTypes=[] — C# has NO function branch anywhere** (this single fact kills several generic paths below: no nested-fn extraction, no arrow-name recovery, extractMethod's fallback-to-extractFunction is unreachable on non-erroring files). classTypes=[`class_declaration`, `record_declaration`] with **classifyClassNode (csharp.ts:69): a record_declaration with an anonymous `struct` keyword child → `'struct'`, else `'class'`** (probed: EVERY record form is record_declaration — `record struct` / `readonly record struct` included; `record_struct_declaration` in structTypes is forward-compat only, the grammar has no such node). methodTypes=[`method_declaration`, `constructor_declaration`]; interfaceTypes=[`interface_declaration`] (no interfaceKind → kind `'interface'`); structTypes=[`struct_declaration`, `record_struct_declaration`]; enumTypes=[`enum_declaration`]; enumMemberTypes=[`enum_member_declaration`]; typeAliasTypes=[] (a `using Alias = …` NEVER makes a type_alias — it goes through extractImport, quirk below); packageTypes=[`namespace_declaration`, `file_scoped_namespace_declaration`]; importTypes=[`using_directive`]; callTypes=[`invocation_expression`]; variableTypes=[`local_declaration_statement`]; fieldTypes=[`field_declaration`]; propertyTypes=[`property_declaration`]. nameField=`name`, bodyField=`body`, paramsField=`parameters`, returnField=`type` (**note: `type` is a dead value for methods** — the 0.23.x grammar renamed the method return field to `returns`, and both consumers, getReturnType and extractCsharpTypeRefs, read `returns` themselves; returnField is only reachable via the generic extractTypeAnnotations path which csharp short-circuits out of). Hooks PRESENT (port each exactly): - **preParse** — TS-side, hoisted (§arch-1). Not ported. - **getReturnType = extractCsharpReturnType (csharp.ts:43)** — reads the **`returns`** field; `predefined_type` (void/int/string/…) or `array_type` → undefined; else raw text trim → strip trailing `\?+` (nullable) → strip `/<[^>]*>/g` (generics) → last `.`-segment → must match `/^[A-Za-z_]\w*$/` else undefined. QUIRKS (same class as rust): the non-nested `<[^>]*>` strip breaks on nested generics — `Task>` → `"Task>"` → regex fails → **undefined** (single-level `Task` → `Task` survives); `Ns.Foo` → `Foo`; `Foo?` → `Foo`. Constructors have no `returns` field → undefined. This feeds the #645/#608 chained-call resolution (name-matcher.ts:2156 `matchDottedCallChain`) — emission shape must hold. - **getVisibility (csharp.ts:102)** — scan ALL children (`node.child(i)`) for type `modifier`; FIRST text match among public/private/protected/internal wins; none → `'private'`. Modifiers are individual named `modifier` children (probed — NO Java-style `modifiers` wrapper). So `protected internal` → `'protected'`, `private protected` → `'private'`. Called for classes, structs, enums, methods, properties, fields (NOT interfaces — extractInterface never asks). - **isStatic (csharp.ts:115)** — any `modifier` child with text `static`. - **isConst (csharp.ts:127)** — `const` modifier → true; else `static` AND `readonly` both present → true. Consumed ONLY by extractField's kind gate (tree-sitter.ts:2058-2062) and extractVariable:2546 (moot — see §local_declaration_statement). - **isAsync (csharp.ts:140)** — any `modifier` child with text `async`. **WORKS for C#** (unlike rust's dead-code isAsync) — probed: `async` is a direct `modifier` child of method_declaration. - **extractImport (csharp.ts:149)** — signature = trimmed full node text (UTF-16 substring). moduleName = FIRST namedChild of type `qualified_name`'s text, else FIRST namedChild of type `identifier`'s text, else null. Probed shapes and the resulting QUIRKS (all PRESERVE): - `using System;` → identifier → `System`. - `using System.Collections.Generic;` → qualified_name → full dotted text. - `using static System.Math;` → qualified_name → `System.Math` (the `static` keyword is an anonymous token — no trace). - `global using GlobalNs.Thing;` → same as a plain using (the `global` keyword is anonymous) → `GlobalNs.Thing`. - **`using Alias = Some.Type;` → the find(qualified_name) hits the TARGET, whose raw text INCLUDES generic args** — moduleName `System.Collections.Generic.Dictionary` (probed verbatim). - **`using Short = SomeType;` (single-identifier target) → no qualified_name → find(identifier) returns the FIRST identifier, which is the `name:` field = the ALIAS** → moduleName `Short`, the target `SomeType` is lost. - The hook never returns null on real shapes probed; if it did, tree-sitter.ts:3350 (`if (this.extractor.extractImport) return;`) means NO import node at all — mirror that gate. Hooks ABSENT (the walker must NOT do these): `getSignature` — **C# methods, constructors and classes carry `signature: undefined`** (only properties/fields get signatures, built inline by their extractors); `getReceiverType` (receiver machinery inert — extractMethod:1799 owner-lookup never runs); `resolveName`, `recoverMangledName`, `isMisparsedFunction`, `isExported` (every node's isExported is undefined; the file node's is false), `resolveBody`, `classifyMethodNode`, `extractPropertyName`, `extraClassNodeTypes`, `extractModifiers`, `synthesizeMembers` (**no Lombok analogue — confirmed; the java.rs Lombok section has no C# counterpart**), `extractBareCall`, `visitNode` hook, `skipBodilessClass` (bodiless CLASSES still mint nodes — but see extractStruct/extractEnum's own body gates), `methodsAreTopLevel`, `interfaceKind`. ## The namespace node (extractFilePackage — the biggest structural quirk) `extract()` (tree-sitter.ts:531-532) calls `extractFilePackage(root)` ONCE, before the walk: it scans root's DIRECT namedChildren for the FIRST packageTypes node, calls `extractPackage` (csharp.ts:86 — `name` field ?? first `qualified_name`/`identifier` child; probed: both namespace forms carry a `name:` field, identifier or qualified_name), and mints ONE `kind:'namespace'` node (name = dotted text, e.g. `My.App`; id = `generateNodeId(file,'namespace','My.App',line)`; contains edge from the file node) which stays PUSHED for the ENTIRE file walk. Consequences (all probed, all PRESERVE): - qualifiedNames join with `::` but the namespace segment keeps its dots: class `Widget` in `namespace My.App` → QN **`My.App::Widget`**, method → `My.App::Widget::Build`. - **visitNode has NO packageTypes branch** — namespace_declaration falls through the whole ladder and its children are visited generically. So: a NESTED namespace (`namespace Outer { namespace Inner { class Deep } }`) leaves NO trace of `Inner` — Deep's QN is `Outer::Deep`; a file with TWO top-level namespaces scopes the SECOND one's types under the FIRST's node — `namespace Second { class Other }` after `namespace Outer {…}` → Other has QN `Outer::Other` and a contains edge from the `Outer` namespace node. - A file-scoped `namespace My.App.Sub;` behaves identically (node spans just the declaration line). - `using` directives INSIDE a namespace block are reached by the generic walk and behave exactly like top-of-file ones; ALL import refs and import nodes in a namespaced file hang off the namespace node (nodeStack top), not the file node. - `isInsideClassLikeNode()` (1486) checks the stack TOP against class/struct/interface/trait/enum/module — **`namespace` is NOT class-like**, so top-level types under a namespace extract exactly like file-scope ones. ## tree-sitter.ts branches (anchors as of `f1ca991`) ### visitNode dispatch — what each C# node hits | Node | Branch | Behavior | |---|---|---| | `class_declaration` | classTypes:1005 → classify:1007 → extractClass:1679 | kind `class`; body walk via declaration_list; skipChildren | | `record_declaration` | classTypes → classify | `struct` keyword child → extractStruct:1869 (record struct); else extractClass. **Bodiless `record Empty;` still mints a class node** (no skipBodilessClass; body-walk loop then iterates the record node's own children harmlessly) | | `struct_declaration` | structTypes:1059 → extractStruct:1869 | body (`declaration_list`) required: **bodiless `struct Fwd;` mints NO node** (1876 exempts only `record_declaration`) | | `interface_declaration` | interfaceTypes:1054 → extractInterface:1834 | kind `interface`; NO visibility read; members visited with interface pushed (class-like) — so interface methods/properties become method/property nodes | | `enum_declaration` | enumTypes:1064 → extractEnum:1914 | body `enum_member_declaration_list` required (bodiless → no node); extractInheritance sees `base_list` → **the underlying type `: byte` emits an `extends` ref named `byte`** (quirk, §inheritance); `enum_member_declaration` children → extractEnumMembers:1958 — `name` field path: ONE `enum_member` node per member, positioned at the member node (attributes included in its span), values/attributes ignored; non-member children (preproc_*, comment) → visitNode (no-op) | | `method_declaration` | methodTypes:1027 → extractMethod:1737 | classifyMethodNode absent → always extractMethod. Gate 1747 passes via class-like (a method_declaration outside a type does not occur in non-erroring C# — top-level `void M(){}` parses as local_function_statement, probed); bodyless interface/partial signatures mint nodes with no body walk; **expression-bodied methods have `body: arrow_expression_clause` (a real body FIELD, probed) → walked** | | `constructor_declaration` | methodTypes → extractMethod | name field = the class-name identifier → **method node named like the class**; returnType undefined; **`constructor_initializer` (`: base(args)` / `: this(args)`) is a sibling of the body field → NEVER walked → calls inside initializer args are LOST** (probed); expression-bodied ctor body = arrow_expression_clause → walked | | `property_declaration` (inside class-like) | propertyTypes:1075 → extractProperty:1986 | property node + scanFnRefSubtree (capture-only) + skipChildren → **accessor bodies (`get { … }`, `get => …`) and the `=> expr` value clause are NEVER walked — calls inside property getters/setters/expression bodies emit NOTHING** (only fn-ref candidates). §property below | | `field_declaration` (inside class-like) | fieldTypes:1084 → extractField:2046 | field/constant nodes per declarator + scanFnRefSubtree + skipChildren → **field initializers emit no calls/instantiates/static-member refs** (fn-ref candidates only). §field below | | `local_declaration_statement` | variableTypes:1098 (only reachable at top level — global statements; body locals go through visitFunctionBody instead) | not class-like → extractVariable:2538 → **generic fallback (2863-2881) finds no direct `identifier`/`variable_declarator` children (the declarator nests inside `variable_declaration`, probed) → ZERO nodes minted**; isClassScopeConstantAssignment (1508) needs node.type `assignment` → never true. skipChildren=true + scanFnRefSubtree → **a top-level `var builder = WebApplication.CreateBuilder(args);` produces NO node, NO calls ref, NO instantiates** — only fn-ref candidates. PRESERVE | | `using_directive` | importTypes:1209 → extractImport:3170 | hook (§config) → import node + ONE generic `imports` ref {fromNodeId: nodeStack top (namespace node if present, else file), referenceName: moduleName, line/col of the directive}; **no per-binding emitter** (the TS/py/rust/php/ruby ladder at 3197-3234 excludes csharp) | | `invocation_expression` (top level — global statements) | callTypes:1248 → extractCall:3684 | fires with caller = file/namespace node; children still visited (no skipChildren) so nested invocations recurse | | `object_creation_expression` (top level, expression-statement position) | INSTANTIATION_KINDS:354/1255 → extractInstantiation:4610 | + findAnonymousClassBody:4815 checks for a `class_body`/`declaration_list` child — **probed: C# object_creation children are [new, type, argument_list, initializer_expression] — the anon branch NEVER fires on non-erroring C#** (C# has no anonymous classes; object initializers are `initializer_expression`). Mirror the check anyway (java.rs:1550 already has it) | | `global_statement` / `expression_statement` / `await_expression` / lambdas / `namespace_declaration` / `preproc_*` / `global_attribute` / `delegate_declaration` / `event_declaration` / `event_field_declaration` / `operator_declaration` / `conversion_operator_declaration` / `indexer_declaration` / `destructor_declaration` / `local_function_statement` / `type_parameter_constraints_clause` | no branch | fall through → children visited. Consequences (all probed, all PRESERVE): **delegates mint NO node; events mint NO node; operators/conversions/indexers/destructors mint NO node but their bodies' calls attribute to the ENCLOSING CLASS** (visitNode descends into accessor/body blocks with the class still on top); **local functions mint NO node** (functionTypes empty — visitFunctionBody:5245 gate never passes) and their bodies' calls attribute to the enclosing method (or file node at top level); `where T : IEntity` constraint types emit NOTHING (constraints are class-node children outside the body — never visited by extractClass's body loop, and extractCsharpTypeRefs doesn't walk them) | | `property_signature` / `method_signature` / `impl_item` / export/store branches | TS/rust-only node types | never C# | ### 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). File node id = literal `file:${filePath}` (tree-sitter.ts:509). Same-(kind,name,line) ID collisions are routine — **dedupe/self-checks compare ID STRINGS** (`node_ids` vec pattern; partial methods `partial void Hook();` + `partial void Hook() { }` are two method nodes on different lines — fine). - resolveBody endLine extension (1329) is a no-op (no hook). - contains edge from nodeStack top for every created node (1363); `captureValueRefScope` after every create (1374). - qualifiedName = stack names joined `::` (buildQualifiedName:1447; namespacePrefix always empty for csharp — it's cpp-only). No receiver QNs (no getReceiverType). - File node: kind `file`, name basename, qualifiedName = filePath, endLine = `source.split('\n').length` (post-preParse source — identical bytes both arms), isExported false. ### extractClass / extractStruct / extractInterface / extractEnum - **extractClass (1679)**: node = class/record; docstring (getPrecedingDocstring), visibility, isExported=undefined → createNode(kind 'class') → **extractInheritance (1704) → extractCsharpPrimaryCtorParamRefs (1707) → extractDecoratorsFor (1710, NO-OP for C# — §decorators) → push → body loop over `declaration_list` namedChildren via visitNode → pop.** No synthesizeMembers. EXACT ORDER of the three pre-body calls is emission-order-sensitive. - **extractStruct (1869)**: body gate (record exemption 1876); createNode('struct') → extractInheritance (1891) → primaryCtor refs (1895) → body walk only when body exists (bodiless positional `record struct M(…);` mints the node, walks nothing). NOTE extractStruct does NOT call extractDecoratorsFor — struct attributes emit nothing anyway. - **extractInterface (1834)**: createNode('interface', {docstring, isExported}) — **NO visibility field** (never asked); extractInheritance; body loop with interface pushed. Interface members: `Task Get(int id);` → method node (no body); `string Label { get; }` → property node; default-impl methods (arrow or block body) → method node WITH body walk. - **extractEnum (1914)**: body required; createNode('enum', {docstring, visibility, isExported}); extractInheritance (→ the `: byte` extends quirk); body loop: enumMemberTypes children → extractEnumMembers (name-field path: 1960-1963 — one `enum_member` node, NO docstring, NO value), everything else → visitNode. Enum members with attributes: the member NODE's position starts at the attribute_list (probed — `[Obsolete] ReadAsInt = 1` spans from `[`). ### extractProperty (1986) — property_declaration docstring; visibility; isStatic (`?? false` — always a concrete bool). Name = `name` field (property_declaration always has one, probed). Type/signature: `isTsJsField` false → typeNode = **FIRST namedChild whose type is NOT modifier/modifiers/identifier/accessor_list/accessors/equals_value_clause** (2013-2017); signature = `typeText name` (the `.replace(/^:\s*/,'')` is inert for C#) or bare `name`. QUIRKS (probed, PRESERVE): - A property whose declared type is a BARE identifier (`public Widget Parent { get; init; }`) finds NO typeNode (identifier is excluded) → signature = `"Parent"` — **identifier-typed properties lose their type in the signature** (predefined_type `string`, generic_name `List`, qualified_name, nullable_type etc. all keep theirs). - An expression-bodied property (`public int Computed => MaxItems + 1;`) has children [modifier, predefined_type, identifier, **arrow_expression_clause (`value:` field)**] → typeNode = predefined_type → signature `"int Computed"`; the arrow clause is NEVER walked (calls inside lost). - `{ get; } = new();` initializers: the `value:` implicit_object_creation and the accessor_list are both skipped/excluded → no refs, no instantiates. Then extractDecoratorsFor (no-op) and **extractTypeAnnotations (2037) → extractCsharpTypeRefs** — the `type` field IS walked for refs (so `public List Items` emits references `List` + `Foo` even though the signature kept the raw text). Return value feeds no body walk (the classifyMethodNode initializer-walk path at 1031-1047 is TS-only). ### extractField (2046) — field_declaration - kind (2058-2062): `(java|csharp) && isConst(node)` → **`constant`** (`const` fields, `static readonly` fields), else `field`. Evaluated once per DECLARATION — all declarators share it. - Declarators: direct `variable_declarator` filter finds none → **the C# wrapper path (2070-2075): the `variable_declaration` child's variable_declarator children** (probed: field_declaration → [modifier…, variable_declaration[type field, declarator, declarator…]]). - typeText (2110-2116): first namedChild of the variable_declaration that isn't modifiers/modifier/variable_declarator/variable_declaration/ marker_annotation/annotation → the `type` node's raw text. - Per declarator: name = `name` field (?? first identifier); signature = `` `${typeText} ${name}` ``; createNode(kind, name, **decl** — the declarator node, so line/col/ID anchor to the declarator); then extractDecoratorsFor(node, id) (no-op) and **extractTypeAnnotations(node, id) → extractCsharpTypeRefs on the OUTER field_declaration** — which walks the variable_declaration's `type` field (5905-5909) → **multi-declarator fields (`Foo A, B;`) emit the type refs ONCE PER DECLARATOR**, each from its own field node. - docstring/visibility/isStatic computed once from the outer declaration, shared by all declarators. The PHP property_element and bare-fallback branches (2078-2154) are unreachable for C#. - `event_field_declaration` is NOT a field_declaration — no nodes (§dispatch). ### extractMethod (1737) — method_declaration + constructor_declaration receiverType = undefined (no hook) → gate 1747 passes via class-like → name = `name` field (constructor: the class name; explicit interface impl `void IDisposable.Dispose()`: name = **`Dispose`** — the explicit_interface_specifier is a separate child, probed); no isMisparsedFunction. Node props: docstring, **signature: undefined** (no getSignature hook), visibility, isAsync (real), isStatic, returnType (getReturnType — constructors undefined). Then extractTypeAnnotations (1816 → extractCsharpTypeRefs: `returns`-field refs FIRST, then per-parameter type refs — order matters), extractDecoratorsFor (1819, no-op), push, walk the `body` FIELD only (block or arrow_expression_clause), pop. The receiver-contains lookup (1799-1813) never runs (no receiverType). ### extractCall (3684) — the C# paths Generic else-branch (4312+): `func = childForFieldName('function') ?? namedChild(0)`. C# invocation_expression always has a `function:` field (probed). The 4364 member_expression/field_expression branch does NOT match `member_access_expression`, so C# calls route as: 1. **`func.type === 'member_access_expression'` → the csharp branch (4502-4517)**: recv = `expression` field, methodName = `name` field text. - recv is `invocation_expression` (chained call `Foo.Create(1).Bar()`): innerFunc = recv's `function` field; innerCallee = innerFunc raw text with `/\s+/g` stripped; calleeName = `` `${innerCallee}().${methodName}` `` (empty innerCallee → bare methodName). **C# re-encodes EVERY call-receiver chain** — no capitalization gate (unlike kotlin/scala): `GetThing().Bar()` → `GetThing().Bar`; multi-line `Foo.Create(1)\n .Bar()` → `Foo.Create().Bar` (whitespace stripped on the INNER only). - recv anything else → **calleeName = RAW full text of the member_access_expression** (getNodeText — UTF-16 substring, whitespace and newlines INCLUDED). Probed consequences, all PRESERVE: `this.Run(x)` → **`this.Run`** (SKIP_RECEIVERS never applies — the 4400 set is in the branch C# doesn't take); `base.Method()` → `base.Method` (recv is the anonymous-token `base` via the field — childForFieldName returns it); `_repo.Save(x)` → `_repo.Save`; `builder\n .Services\n .AddSingleton()` → the full multi-line text `builder\n .Services\n .AddSingleton` (generic args included, newlines included); **literal receivers are NOT filtered** — `"lit".ToUpper()` → `"lit".ToUpper` (LITERAL_RECEIVER_TYPES is consulted only in the non-C# branch); `p!.Force()` → `p!.Force`; 2-hop reads keep everything. 2. `func.type === 'scoped_identifier'` (4499) — never C# (no `::` node). 3. **else → calleeName = raw func text**: bare `Helper()` → `Helper`; generic invocation `Generic(local)` → func = generic_name → **`Generic` kept verbatim** (the template strip at 4542 is c/cpp-gated); `nameof(Widget)` → func = identifier `nameof` → **a `calls` ref named `nameof`** (probed — nameof is a plain invocation in this grammar); conditional access `request?.Method()` → func = conditional_access_expression → raw text **`request?.Method`**; delegate parens `(myDel)(x)` → func = parenthesized_expression → text `(myDel)` → **the conversion regex at 4530 (`/^\(\s*\*?\s*([A-Za-z_][\w.]*)\s*\)$/`) FIRES → `myDel`** (the one shared normalization C# actually hits — port it). Post-processing: template strip (4542) and cpp fn-ptr fan-out (4556) are c/cpp-gated — NOT csharp. Final push: one `calls` ref {fromNodeId = nodeStack top, referenceName, line = call startRow+1, column = call startColumn (UTF-16)}. Children of the invocation are still traversed afterward (both walkers), so inner calls of a chain emit separately: `Foo.Create(1).Bar()` → refs `Foo.Create().Bar` AND `Foo.Create`. `typeof(…)`, `default(…)`, casts, `is`/`as` patterns, element access, switch expressions, `??`/ternary, interpolated-string interpolations, tuple expressions: no dedicated handling — plain recursion reaches any invocation_expression inside them (probed shapes in probe3-out.txt). ### extractInstantiation (4610) — object_creation_expression ctor = constructor/`type`(hit)/name/namedChild(0) fields → the `type:` field (probed). Generic path: raw text → strip from first `<` (`new List()` → `List`) → vbnet paren strip (inert) → keep last `.`/`::` segment (`new Ns.Foo()` → `Foo`) → trim → `instantiates` ref at the object_creation_expression's position. Fires from visitNode:1255 (expression statements at top level) AND visitFunctionBody:5145. QUIRKS, PRESERVE: **`implicit_object_creation_expression` (`new()`) and `anonymous_object_creation_expression` (`new { X = 1 }`) and `array_creation_expression` (`new Widget[10]`) are NOT in INSTANTIATION_KINDS → no instantiates refs** (target-typed `new()` — everywhere in modern C# — is invisible); object/collection initializer args and `new[] { Mk() }` contents still recurse to their own calls. Top-level `var w = new Widget();` emits nothing at all (§local_declaration_statement). ### extractStaticMemberRef (4750) — csharp ∈ STATIC_MEMBER_LANGS (345) Called for EVERY node in visitFunctionBody (5218) — body walker only (never visitNode, so class-level field initializers and top-level statements emit no static refs). Node gate: MEMBER_ACCESS_TYPES (323) contains `member_access_expression`. Skip when the access IS a call's callee (4772-4779: parent ∈ callTypes && callee.startIndex === node.startIndex — so `Console.WriteLine(…)`'s access is skipped but `DoThing(Constants.MAX)`'s argument is not). recv = `expression` field (hit) → must be type identifier/type_identifier/simple_identifier/name/scoped_type_identifier — for C# only `identifier` occurs — and text must match `/^[A-Z][A-Za-z0-9_]*$/` → `references` ref {name = recv text, line/col of RECV}. Probed: `ReadType.ReadAsDouble` → ref `ReadType`; `Outer.Inner.DEEP` → the outer access's recv is a member_access (skip) but the INNER `Outer.Inner` emits ref `Outer`; lowercase receivers (`u.Age`, `builder.Services`) → nothing; `this.X` → recv type `this` → nothing. ### Decorators/attributes — NO-OP for C# (probed, PRESERVE) extractDecoratorsFor (4897) accepts node types decorator/annotation/ marker_annotation/attribute (+solidity modifier_invocation). C# attributes are **`attribute_list` nodes containing `attribute` children**, and attribute_list is (a) a DIRECT child of the declaration (class/method/enum-member — probed), so the direct-children scan sees `attribute_list` (not in the set → skipped, and its `attribute` grandchildren are never reached); (b) never inside a `modifiers` wrapper (C# has none); (c) as a PRECEDING sibling — only for global_attribute, which is never a decorated declaration. **Net: zero `decorates` refs for C#.** The walker needs no decorator code — but keep the call sites' ORDER slots (they emit nothing). Corollary (contrast rust): an attribute between a doc comment and its declaration does NOT break the docstring chain — the attribute_list is a child of the declaration, so the comment remains previousNamedSibling (probed: `/// doc` + `[Attr]` + method → docstring found). ### Inheritance — extractInheritance (5291), base_list branch (5577-5593) The ONLY child type that matters for C# types is **`base_list`** (extends/ implements/superclass/base_class_clause etc. never occur). The branch iterates ALL namedChildren of base_list with NO type filter and emits ONE **`extends`** ref each (C# never emits `implements` from extraction — the comment at 5574-5576 documents the deliberate conflation; interface-vs-class splitting happens at resolution/synthesis): - `identifier` → text (`BaseItem`, `IWidget`). - `generic_name` → its FIRST `identifier` child's text (`ClientBase` → `ClientBase`) — but position = the generic_name node. - `qualified_name` → FULL dotted text (`Sys.ICloneable` — no last-segment reduction). - **QUIRK (probed): a C# 12 class primary-ctor base `class Svc(…) : Base(repo), IThing` parses base_list children as [identifier(Base), argument_list((repo)), identifier(IThing)] → emits extends `Base`, extends `(repo)` (!), extends `IThing`.** The `(repo)` ref is garbage that never resolves — PRESERVE it. - **QUIRK (probed): a RECORD's base-with-args wraps differently — `record UserDto(…) : BaseDto(Name), IThing` → base_list children are [primary_constructor_base_type, identifier] → emits extends **`BaseDto(Name)`** (full text, argument list included) + `IThing`.** - **QUIRK (probed): an enum's underlying type `enum ReadType : byte` puts predefined_type(byte) in base_list → extends ref `byte`** (likewise `: int`, etc.). extractInterface/extractStruct/extractEnum all route through the same branch. The 5652 recursion (field_declaration_list/class_heritage) never matches C#. ### Type-annotation references — csharp ∈ TYPE_ANNOTATION_LANGUAGES (5753) extractTypeAnnotations (5788) **short-circuits into extractCsharpTypeRefs (5798-5801)** — the generic params/return/type_annotation path below it never runs for C#. extractCsharpTypeRefs (5893): 1. directType = `type` field ?? `returns` field → walkCsharpTypePosition (methods: `returns`; properties: `type`; constructors: neither). 2. a `variable_declaration` child's `type` field (fields) → walk. 3. `parameters` field → per `parameter` child (type filter — probed: `bracketed_parameter_list` of indexers never reaches here since indexers aren't dispatched) → its `type` field → walk. walkCsharpTypePosition (5955): `predefined_type` → nothing; `identifier` → `references` ref unless name ∈ BUILTIN_TYPES (5768 — port the WHOLE set verbatim: the Java/C# row int/long/short/byte/float/double/char, plus cross-language rows — a C# type named `String`/`Boolean`/`error` IS suppressed (Scala/Go rows) while `Task`/`List`/`IRepo`/**`dynamic`** are emitted — probed: `dynamic` parses as identifier → a `references` ref named `dynamic`); `qualified_name` → LAST `.`-segment as referenceName (position = the whole qualified_name node) unless builtin; `tuple_element` → its `type` field only (element names gated out); everything else (generic_name, nullable_type, array_type, pointer_type, tuple_type, ref_type, scoped_type, …) → recurse ALL namedChildren — so `List` emits `List` AND `Foo`; `Task>` emits `Task`, `List`, `Foo`; `Foo?`/`Bar[]` unwrap; generic args inside type_argument_list all surface. Called from extractMethod (params+returns), extractProperty (type), extractField (per declarator), and extractCsharpPrimaryCtorParamRefs. **extractCsharpPrimaryCtorParamRefs (5938)** — from extractClass:1707 and extractStruct:1895 (NOT interface/enum): finds a `parameter_list` child BY TYPE (the primary ctor hangs as an unnamed-field child — probed on class, record, record struct), walks each `parameter`'s `type` field → `references` refs from the TYPE node (`class Svc(IRepo repo, ICache cache)` → refs `IRepo`, `ICache`). Extension-method `this` params ride the normal parameter path (the `this` is a modifier child; type field still walked). **Body-local type annotations: NONE.** visitFunctionBody:5230's variable_declarator branch calls extractVariableTypeAnnotation (6074), which looks for a `type_annotation` child — C# has no such node (the type is a field of variable_declaration) → structurally inert. Cast/typeof/is/as/catch types emit nothing (§extractCall). ### Docstrings (tree-sitter-helpers.ts:95) `///` XML doc comments and `//` comments are `comment` nodes (probed); consecutive preceding named siblings accumulate (unshift → source order); cleanCommentMarkers strips `^\/\/[/!]?\s?` per line — so the stored docstring is the XML markup minus slashes (`Doc line.`). DOCSTRING_WRAPPER_TYPES contains no C#-relevant wrappers (`variable_declaration` /`variable_declarator` ARE in the set but no C# docstring call site passes those nodes — extractField/extractProperty pass the outer declaration). Block `/** */` comments are also `comment` nodes and `/*`-stripped. Attributes don't break the chain (§decorators). **Reuse `docstring::preceding_docstring` + `js_multiline_strip` as-is** — the CRLF `^`-after-`\r` semantics (#1329) are already handled there; C#'s XML docs on CRLF checkouts hit exactly that path. Docstring recipients: class/record/struct/interface/enum/method/ctor/property/ field (NOT enum members, NOT the namespace node, NOT imports). ### Value-reference edges — csharp ∈ VALUE_REF_LANGS (401) Port the full machinery (crib java.rs:1246 `flush_value_refs` — Java's cases are csharp's minus/plus the declarator switch): `CODEGRAPH_VALUE_REFS=0` kill; MAX_VALUE_REF_NODES=20_000 caps the prune scan and each reader scan; isGeneratedFile skip. - Targets (captureValueRefScope:735): created nodes of kind constant/variable, name length ≥3 AND `/[A-Z_]/`, parent scope id starting `file:`/`class:`/`module:`/`struct:`/`enum:`. For C# that means **`const` / `static readonly` fields (kind constant) inside class:/struct: parents** — namespace: parents do NOT qualify (deliberate: a namespace-scoped… doesn't exist in C# anyway), and C# mints no `variable` nodes at all. Last same-named target wins the map; counts accumulate per name. - Reader scopes: every function/method/constant/variable node — for C#: methods (ctors included) + constant fields. - Shadow prune (803-878): DFS the whole tree; C#-live cases of the declarator switch: **`variable_declarator` (818) → bump(namedChild(0))** — counts field declarators (the targets) AND method-body locals AND top-level-statement declarators (the shadow sources); **`property_declaration` (856) fires but is inert** — no variable_declaration child, and firstSimpleIdentifier (tree-sitter.ts:261) hunts `simple_identifier` which C# lacks → bump(null). All other cases (const_item, assignment, init_declarator, …) are other grammars' node types — a C# tree never contains them. After the scan: declCount > fileScopeCount → target deleted (a method-local `var MaxItems = …` shadows the class const). - Emission (880-930): per reader scope, stack-DFS its node subtree (push-children-in-order / pop-last → REVERSE-index traversal order — mirror exactly, dedupe is first-wins per (scope,target)); each `identifier` whose text maps to a target, target ≠ self, name ≠ scope's own name → EDGE {source: scopeId, target: targetId, kind:'references', metadata:{valueRef:true}}. The `constant`/`name`/`simple_identifier` reader types are other-language rows — inert but harmless to mirror. Dart/Pascal sibling-body pull (891) is inert. Flush order: fn-ref candidates flush FIRST (unresolvedReferences), then value refs (edges) — extract():538-539. ### Function-as-value capture (#756) — CSHARP_SPEC (function-ref.ts:250) idTypes={identifier}; dispatch: `argument_list`→args, `assignment_expression`→rhs(field `right`) — covers `+=`/`-=` event subscription (compound assignment is still assignment_expression, probed), `initializer_expression`→list (object/collection initializers), `variable_declarator`→varinit (NO field — the last-named-child path). layers: `argument`→null (descend named children). special: {member_access_expression}. No unwrap/ungatedModes/addressOfOnly. - varinit mechanics (function-ref.ts:471-486): C# variable_declarator has no `value` field → value = LAST namedChild, require ≥2 namedChildren and value ≠ name child (probed: `f = x => Compute(x)` → [name, lambda_expression]; initializer-less `_repo` → 1 child → skipped). Destructuring gate (object/array/tuple/struct_pattern) never matches C#. - rhs param-storage skip (430-444): LHS last-identifier == RHS text → skip (`this.status = status`). `button.Click += OnClick` → LHS last-name `Click` ≠ `OnClick` → candidate. - normalizeValue: bare `identifier` → candidate (NAME_STOPLIST = this/self/super/null/nil/true/false/undefined/new/NULL/nullptr/None — note `value`, `args`, and `base` are NOT stoplisted; `base`/`this` can't become candidates anyway, they parse as non-identifier tokens); `member_access_expression` → normalizeSpecial (738-746): name field required; receiver must be `this` — expr field type `this_expression` OR `this` (the vendored grammar yields the anonymous-token **`this`** via the field — probed; the text-prefix fallback exists for field-less shapes) → candidate = **the BARE member name** (`this.HandleThing` → `HandleThing` — NOT `this.`-prefixed, unlike TS/Java; it then gates against definedHere). `C.StaticHandler` (type receiver) → NOTHING. Lambdas/anonymous methods → nothing (not idTypes/special). - Capture fires from visitNode:990, visitFunctionBody:5137, and scanFnRefSubtree (property/field/variable declarations + top-level statements, depth ≤12, capture-only). **scanFnRefSubtree's halt list (tree-sitter.ts:606-612) includes the literal type `lambda_expression` — which IS C#'s lambda node** — so at depth>0 the scan STOPS at a lambda in a field/property initializer (`Action A = () => Register(H);` yields no candidates from inside the lambda), while `anonymous_method_expression` (`delegate() { … }`) is NOT in the list and is scanned through. Method-BODY lambdas are unaffected (visitFunctionBody recursion has no halt — capture fires per node). - Flush gate (flushFnRefCandidates:639): generated-file skip; `this.`-prefixed names skip the gate (C# never produces them — its this-forms are bare); otherwise name ∈ definedHere ∪ importedNames. **definedHere = same-file function/method node NAMES — for C#: methods + constructors (= class names); local functions are NOT nodes so their names don't gate in.** importedNames: from `imports`-kind refs — C# using-refs are dotted → QUALIFIED_IMPORT (`.`/`\` separators) admits the LAST segment (`using MyApp.Helpers;` → `Helpers`), single-segment `using Xunit;` → `Xunit`; the alias-quirk moduleNames ride the same rules. Survivors dedupe on `${fromNodeId}|${name}` → `function_ref` refs. ### Closure-collection / other passes - csharp ∉ CC_LANGUAGES (callback-synthesizer.ts:77 — swift/kotlin only): the closure-collection synthesis pass ignores C#. Nothing to port; nothing to regress. - Local-variable receiver inference (#1108) and typed-param receivers (#1125/#1129/#1130) are RESOLUTION-side: name-matcher.ts:1164 has the csharp regex rows (`= new Logger()` and `Logger lg;`-shaped declared types, covering params via the `[=;,)]` tail). They consume the `recv.Method` member-access text extraction emits — the walker just has to reproduce the emission shapes above. - Chained-call resolution (#645/#608/#750): csharp is in matchDottedCallChain (name-matcher.ts:2156) — consumes the `inner().Method` encoding + declared return types (getReturnType). Resolution-side; extraction contract pinned above. - LANGUAGE_FAMILY: csharp+razor = 'dotnet' (name-matcher.ts:149) — the razor extractor (T3, TS-side) resolves against C# nodes; unaffected by the port. ## java.rs skeleton mapping (what to crib, what diverges) Crib directly (same chassis, adjust node kinds): `Scope`/`NodeMeta`/`Extra` structs, `create_node` + contains + capture_value_ref_scope, `push_ref` helpers, visibility/is_static/is_const modifier scans (java.rs:405-447 — but C# reads bare `modifier` children, java reads a `modifiers` wrapper), extract_field's wrapper-declarator loop (java.rs:704 — C# adds the `variable_declaration` indirection), extract_enum/extract_enum_members, extract_static_member_ref (java.rs:938 — swap field_access → member_access_expression + `expression` field), value-ref flush (java.rs:1246 — swap the declarator cases for variable_declarator + inert property_declaration), fn-ref capture/flush (java.rs:1120-1243 — swap JAVA_SPEC for CSHARP_SPEC incl. bare identifiers as idTypes and the this-receiver special), find_anonymous_class_body (java.rs:1550 — keep, unreachable), the package/namespace node (java.rs package_declaration ↔ extractFilePackage semantics — but C#'s is FIRST-CHILD-ONLY with the second-namespace and nested-namespace quirks, and the namespace node kind is `namespace` in both). DIVERGES from java.rs — do NOT copy: Lombok synthesis (java.rs:1339-1548 — no C# counterpart); method_invocation `this.field` unwrap + Java's chain encoding (C#'s csharp-branch semantics at tree-sitter.ts:4502 differ: raw full text, no SKIP_RECEIVERS, unconditional re-encode); annotations→decorates (C# emits none); anonymous classes (`` — unreachable for C#); signature_of (C# methods have NO signature); Java's type_list inheritance (C# is the unfiltered base_list loop + its three quirk shapes); getVisibility default (`private` for C#). csharp-NEW (no java.rs precedent): extractCsharpTypeRefs/walkCsharpTypePosition (the type-ref engine), extractCsharpPrimaryCtorParamRefs, the extractProperty branch (java has no propertyTypes), classifyClassNode record→struct, the extractImport alias/static/global quirks, the local_declaration_statement zero-emission path, preproc_* passthrough. ## Frameworks that consume C# extraction artifacts (stay TS-side) `aspnetResolver` (resolution/frameworks/csharp.ts) — detect: .csproj AspNetCore refs / Program.cs / Startup.cs / controller-source scan. - **`extract()` (csharp.ts:133 — regex over RAW source via stripCommentsForRegex, runs in extractFromSource AFTER either arm — NO port needed):** emits `route` nodes with id `` `route:${filePath}:${line}:${METHOD}:${path}` `` (NOT hashed), kind `route`, name `` `${METHOD} ${path}` ``, qualifiedName `` `${filePath}::route:${path}` ``, language `csharp`; plus one `references` ref per handler FROM the route node (framework refs DO carry filePath+language, unlike extraction refs). Covers `[HttpGet]`-family attributes (bare or with path, class-level `[Route]` prefix joined) and minimal-API `.MapGet("/p", handler)` chains. Because it reads RAW source (not extraction output), the port cannot break it — but parse-worker routing (§arch-2) means its presence forces the decoded path. - **`resolve()`** consumes plain unresolved refs by suffix (Controller/Service/Repository/ViewModel/Dto) + directory conventions — extraction-shape-agnostic beyond the referenceName spellings pinned above. ## Parity mechanics (all have bitten before — be explicit) - **Emission ORDER per file** (rowid-order-sensitive downstream): file node → namespace node (when present) → walk order exactly as the dispatch table above (per declaration: inheritance refs → primary-ctor refs → [decorators: nothing] → body/member walk; methods: type-annotation refs (returns, then params) before the body's calls; fields: per-declarator node → its type refs → next declarator) → flushFnRefCandidates (refs) → flushValueRefs (edges, reverse-index DFS order). Refs carry NO filePath/language (the store denormalizes) — the wire contract is exactly extractFromSource's return. - **generateNodeId inputs**: (filePath, kind, name, startRow+1) — name is the EXACT string pinned per branch above (e.g. constructors use the class name; enum members the name-field text; imports the moduleName with its generic-args quirk; the namespace node the dotted package text). - **UTF-16 everywhere**: positions/columns (textutil::col16 — verified against the wasm: emoji-preceded call at column 37 in UTF-16 units), every getNodeText slice (raw substring — multi-line callee texts keep newlines), the import signature trim. No `.slice(0,100)` truncations fire for C# (that path is the variable-initializer signature, unreachable), but signatures built from raw type text are UTF-16 substrings. - **CRLF hazards**: the ONLY multiline regexes on the C# path are (a) the preParse blanking — TS-side, hoisted, never ported (§arch-1) — and (b) docstring cleaning — already CRLF-correct via `js_multiline_strip` (docstring.rs, #1329). The walker itself must add NO `(?m)`/`^`/`$` regex without the same scrutiny. getReturnType/extractImport regexes are single-line and anchor-free except `/^[A-Za-z_]\w*$/` (whole-string match — use `Regex::is_match` on the full string, `\A…\z` semantics). - **Defer policy**: per-file `has_error()` → `defer:` signal (kernel receives BLANKED source; incidence per §arch-6). The one-slot defer memo already covers csharp (preParse hoist listed it from day one). - **MAX_FILE_SIZE + generated-file skips**: shared/orchestrator-side; nothing csharp-specific (isGeneratedFile gates fn-ref flush + value refs — mirror the calls, the patterns live in textutil.rs). - **Node-ID collisions**: compare ID strings in fn-ref/value-ref self-checks (`node_ids` vec pattern — java.rs already does this). ## Gates (per plan §5, no exceptions) - **Torture fixture `Torture.cs`** (+ CRLF variant derived in-memory, normalization-proof) pinning at minimum: block namespace + file-scoped namespace file + **two-namespaces-in-one-file (second nests under first)** + nested namespace (inner leaves no trace); class with base_list (identifier + generic_name + qualified_name), attributes (NO decorates), `///` docstring incl. doc-over-attribute; record class / record struct / readonly record struct (+ positional params → primary-ctor type refs) / bodiless `record Empty;` (node) / `record UserDto(…) : BaseDto(Name), IThing` (extends `BaseDto(Name)` full-text quirk); C#12 class primary ctor with base args (extends `Base` + **`(repo)`** + `IThing` quirk); bodiless `struct Fwd;` (NO node); interface with bodyless method + property + default-impl arrow method; enum with `: byte` (extends `byte` quirk), attributed member, valued members; const + static-readonly (→ `constant`) + multi-declarator + instance fields (signatures `Type name`); `protected internal` (→ protected); property shapes: predefined-type, bare-identifier type (signature loses type), generic type, expression-bodied (`=>` calls LOST), `{ get; } = new();`, accessor bodies with calls (LOST); event_field_declaration + event_declaration with add/remove bodies (no nodes; accessor calls → class); operator + conversion operator + indexer + destructor (no nodes; body calls → class); constructor with `: base(Compute())` (initializer calls LOST) + expression-bodied ctor (walked); explicit interface impl (`Dispose` bare name); local function (no node, calls → enclosing method); calls: bare, generic (`Generic` verbatim), `this.Run`/`base.Method` (prefixes kept), `_repo.Save`, multi-line fluent chain (raw newlines), literal receiver (`"lit".ToUpper` emitted), `?.` (raw `request?.Method`), `p!.Force`, chained `Foo.Create(1).Bar()` (re-encode + inner both) + `GetThing().Bar()`, `(myDel)(x)` (conv regex → `myDel`), `nameof(Widget)` (calls ref `nameof`); `new Widget(…) { … }` (instantiates + initializer calls) + `new Ns.Foo()` (strip both) + `new()` / `new { }` / `new Widget[10]` (all NOTHING); static value reads (`ReadType.ReadAsDouble` → `ReadType`; `Outer.Inner.DEEP` → `Outer`; skip-as-callee; lowercase skip); type refs: params (nullable/array/tuple-element/generic/qualified/`dynamic`), returns (`Task>` → refs Task+List+Foo but returnType undefined), suppressed builtins (`String`, `int`); `where T : IEntity` (NO refs); using forms: plain/dotted/static/global/alias-to-qualified (generic-args moduleName)/alias-to-identifier (alias-name quirk) + using inside namespace (refs from namespace node); top-level-statements file: `var x = F();` (NOTHING), expression-statement calls (caller = file), top-level local function + trailing `partial class Program`; fn-refs: `Register(HandleThing)`, `Register(this.HandleThing)` (bare name), `Register(C.StaticHandler)` (nothing), `Click += OnClick`, initializer_expression list, varinit (`Action g = () => …` no candidate; `Del d = Handler;` candidate), `this.x = x` param-storage skip; value-refs: const target + reader methods + a `var MaxItems = …` local shadow (prune) + `static readonly` multi-target; preprocessor: `#region`/`#endregion`/`#pragma`/`#nullable`/`#define` interleaved in class + enum bodies (parse-through, no emissions), and the **`#if/#else/#endif`-in-enum shape as the DEFER fixture pre-blank** — plus an intentionally-erroring file (e.g. an unclosed brace or the both-branches-kept interface shape) asserting the `defer:` signal; LINQ query + interpolated/raw/verbatim strings + switch expression + collection expressions (calls inside all reach the graph). - **Parity sweeps** (`scripts/kernel-parity.mjs`, ORDER-sensitive full-object): **serilog (small), Newtonsoft.Json (medium), jellyfin (large)** — survey clones live at `/private/tmp/claude-501/-Users-colby-Development-CodeGraph-codegraph/765a9532-0a92-43de-8d50-7c8ca1cb345c/scratchpad/{serilog,Newtonsoft.Json,jellyfin}` (re-clone fresh if gone). Expected deferrals per §arch-6 (2.31% / 3.28% / 0.05%) — **default `--max-deferral 0.1`, NO c/cpp 0.5 exemption**; a double-digit rate means a broken walker, not grammar reality. Then **full-init dump-diffs byte-identical** (kernel arm vs `CODEGRAPH_KERNEL=0`, `scripts/dump-graph.mjs`, `cmp`) on the same three. jellyfin is an ASP.NET detect() hit — its per-file path is decoded either way (§arch-2); the dump gate is arm-vs-arm and unaffected. - **Suite**: new `__tests__/kernel-csharp-parity.test.ts` (torture + CRLF + defer fixtures, strict full-object compare, `CODEGRAPH_KERNEL_EXPECT=1` aware); add the csharp row to `__tests__/kernel-grammar-parity.test.ts` (crate `tree_sitter_c_sharp::LANGUAGE` vs vendored wasm, id-by-id); existing pinned behavior lives in `__tests__/extraction.test.ts` (describe blocks at 1342 `C# Extraction`, 2548 `C# imports`, 8924 `C# records`) and `__tests__/resolution.test.ts` — full suite green ×2 with `CODEGRAPH_KERNEL_EXPECT=1`. - **`DEFAULT_ROUTED += csharp`** (src/extraction/kernel/index.ts:37) only after ALL of the above; changelog rides the existing kernel entry. - Post-route perf sanity: §arch-2 — measure library repos (serilog/ Newtonsoft.Json) for the raw-buffer win; ASP.NET repos take the decoded path by design. ## Fixtures to build (inventory) 1. `__tests__/fixtures/kernel-parity/Torture.cs` — the full pin list above (one file is fine; keep the two-namespace case in it). 2. `TortureFileScoped.cs` — file-scoped namespace + records + primary ctors + top-level `using` alias quirks (a file-scoped-ns file cannot host top-level statements — keep those separate). 3. `TortureTopLevel.cs` — top-level statements program (global statements, local function, trailing type decl). 4. CRLF variants of all three — derived in-memory in the test (the kernel-tsjs-parity pattern), never checked in as CRLF bytes. 5. `TortureDefer.cs` — the `#if`-in-enum ERROR shape with blanking DISABLED in the test arm… no: blanking is hoisted and always on — instead use a both-branches-kept interface shape (the serilog ILogger class) or a truly malformed tail that still errors POST-blank, asserting kernel `defer:` + wasm-fallback byte-parity through extractFromSource. 6. In-memory micro-fixtures inside the parity test for: the alias-import moduleName quirks, the `(repo)` extends quirk, the enum `: byte` quirk, `nameof` calls ref, `this.`/`base.` callee prefixes, and the property-signature bare-identifier-type loss — cheap unit pins that make a future grammar bump scream early.