Skip to content

feat(csharp): C# MethodExtractor config#582

Merged
magyargergo merged 12 commits into
mainfrom
feat/csharp-method-extractor
Mar 30, 2026
Merged

feat(csharp): C# MethodExtractor config#582
magyargergo merged 12 commits into
mainfrom
feat/csharp-method-extractor

Conversation

@magyargergo

@magyargergo magyargergo commented Mar 29, 2026

Copy link
Copy Markdown
Collaborator

Summary

Closes #573. Adds C# method extraction config following the established JVM pattern from PR #576.

  • New file: gitnexus/src/core/ingestion/method-extractors/configs/csharp.ts — C# MethodExtractionConfig with detection for:
    • isAbstract: abstract modifier or interface body method without body
    • isFinal: sealed modifier (C# terminology)
    • isStatic: static modifier
    • params arrays → isVariadic: true
    • out/ref parameter modifiers → type prefixed (e.g., "out int")
    • Optional parameters with defaults → isOptional: true
    • [Attribute] syntax → annotations array (prefixed with @)
    • Visibility: public, private, protected, internal (default: private)
  • Modified: languages/csharp.ts — wires createMethodExtractor(csharpMethodConfig) into the C# language provider
  • Tests: 18 new test cases covering classes, interfaces, abstract classes, structs, records, constructors, params/out/ref/optional parameters, sealed methods, attributes, and visibility modifiers

Depends on

PR #576 (merged)

Test plan

  • All 55 method extraction tests pass (37 existing Java/Kotlin + 18 new C#)
  • TypeScript type check passes clean (tsc --noEmit)
  • Pre-commit hooks pass (eslint, prettier, tsc)
  • CI pipeline passes

Add C# method extraction config mirroring the JVM pattern from PR #576.
Wire csharpMethodConfig into the C# language provider and add 18 tests
covering classes, interfaces, abstract classes, structs, records,
constructors, params/out/ref/optional parameters, sealed methods,
attributes, and visibility modifiers.
@vercel

vercel Bot commented Mar 29, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
gitnexus Ready Ready Preview, Comment Mar 30, 2026 7:32am

Request Review

@github-actions

github-actions Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

CI Report

All checks passed

Pipeline Status

Stage Status Details
✅ Typecheck success tsc --noEmit
✅ Tests success unit tests, 3 platforms
✅ E2E success gitnexus-web changes only

Test Results

Tests Passed Failed Skipped Duration
4799 4753 0 46 179s

✅ All 4753 tests passed

46 test(s) skipped — expand for details
  • buildTypeEnv > known limitations (documented skip tests) > Ruby block parameter: users.each { |user| } — closure param inference, different feature
  • Swift constructor-inferred type resolution > detects User and Repo classes, both with save methods
  • Swift constructor-inferred type resolution > resolves user.save() to Models/User.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > resolves repo.save() to Models/Repo.swift via constructor-inferred type
  • Swift constructor-inferred type resolution > emits exactly 2 save() CALLS edges (one per receiver type)
  • Swift self resolution > detects User and Repo classes, each with a save function
  • Swift self resolution > resolves self.save() inside User.process to User.save, not Repo.save
  • Swift parent resolution > detects BaseModel and User classes plus Serializable protocol
  • Swift parent resolution > emits EXTENDS edge: User → BaseModel
  • Swift parent resolution > emits IMPLEMENTS edge: User → Serializable (protocol conformance)
  • Swift cross-file User.init() inference > resolves user.save() via User.init(name:) inference
  • Swift cross-file User.init() inference > resolves user.greet() via User.init(name:) inference
  • Swift return type inference > detects User class and getUser function
  • Swift return type inference > detects save function on User (Swift class methods are Function nodes)
  • Swift return type inference > resolves user.save() to User#save via return type of getUser() -> User
  • Swift return-type inference via function return type > resolves user.save() to User#save via return type of getUser()
  • Swift return-type inference via function return type > user.save() does NOT resolve to Repo#save
  • Swift return-type inference via function return type > resolves repo.save() to Repo#save via return type of getRepo()
  • Swift implicit imports (cross-file visibility) > detects UserService class in Models.swift
  • Swift implicit imports (cross-file visibility) > resolves UserService() constructor call across files (no explicit import)
  • Swift implicit imports (cross-file visibility) > resolves service.fetchUser() member call across files
  • Swift implicit imports (cross-file visibility) > creates IMPORTS edges between files in the same module
  • Swift extension deduplication > detects Product class
  • Swift extension deduplication > resolves Product() constructor despite extension creating duplicate class node
  • Swift extension deduplication > resolves product.save() to Product.swift (primary definition)
  • Swift constructor call fallback (no new keyword) > resolves OCRService() as constructor call across files
  • Swift constructor call fallback (no new keyword) > resolves ocr.recognize() member call via constructor-inferred type
  • Swift export visibility (internal vs private) > resolves PublicService() constructor across files
  • Swift export visibility (internal vs private) > resolves internalHelper() across files (internal = module-scoped)
  • Swift if let / guard let binding resolution > detects User and Repo classes
  • Swift if let / guard let binding resolution > resolves user.save() inside if-let to User#save
  • Swift if let / guard let binding resolution > resolves repo.save() inside guard-let to Repo#save
  • Swift if let / guard let binding resolution > user.save() in if-let does NOT resolve to Repo#save
  • Swift await / try expression unwrapping > resolves user.save() via await fetchUser() return type
  • Swift await / try expression unwrapping > resolves repo.save() via try parseRepo() return type
  • Swift await / try expression unwrapping > detects fetchUser and parseRepo as functions
  • Swift for-in loop element type inference > detects User and Repo classes
  • Swift for-in loop element type inference > creates implicit import edges between files
  • Swift field-type resolution > detects classes and their properties
  • Swift field-type resolution > emits HAS_PROPERTY edges from class to field
  • Swift field-type resolution > resolves field-chain call user.address.save() → Address#save
  • Swift field-type resolution > emits ACCESSES edges for field reads in chains
  • Swift field-type resolution > populates field metadata (visibility, declaredType) on Property nodes
  • Swift call-result binding > resolves call-result-bound method call user.save() → User#save
  • Swift call-result binding > getUser() is present as a defined function
  • Swift call-result binding > emits processUser -> getUser CALLS edge for let-assigned free function call

Code Coverage

Tests

Metric Coverage Covered Base Delta Status
Statements 70.75% 13022/18405 70.48% 📈 +0.3 🟢 ██████████████░░░░░░
Branches 60.05% 8604/14327 59.8% 📈 +0.2 🟢 ████████████░░░░░░░░
Functions 75.51% 1144/1515 75.25% 📈 +0.3 🟢 ███████████████░░░░░
Lines 72.93% 11844/16240 72.65% 📈 +0.3 🟢 ██████████████░░░░░░

📋 View full run · Generated by CI

…ram support

- Add destructor_declaration, operator_declaration, and
  conversion_operator_declaration to methodNodeTypes
- Custom extractName for operators (e.g., "operator +", "implicit operator double")
- Fix extractReturnType for operator declarations (use type field, not returns)
- Add in modifier to parameter extraction (alongside out/ref)
- Add 4 new tests: destructor, operator+, implicit conversion, in parameter
…tation

- Add test for ref parameter modifier (was only testing out)
- Document that protected internal / private protected resolve to first modifier
…ate protected)

- Add 'protected internal' and 'private protected' to FieldVisibility union
- Detect compound modifiers in both C# method and field extractors via
  collectModifierTexts helper scanning adjacent modifier nodes
- Add 2 tests for compound visibility detection
@magyargergo

Copy link
Copy Markdown
Collaborator Author

C# MethodExtractor Coverage Report

Complete C# Method-Like Construct Coverage

Construct Status
Regular methods (all modifiers) ✅ Supported
Constructors (instance + static) ✅ Supported
Destructors / finalizers (~Foo()) ✅ Supported
Operator overloads (operator +, operator ==, etc.) ✅ Supported
Conversion operators (implicit operator double) ✅ Supported
Expression-bodied members (=> expr) ✅ Supported
Abstract / sealed / static / virtual / override ✅ Supported
Interface default methods (C# 8+) ✅ Supported
Static abstract interface members (C# 11) ✅ Supported
Generic methods (T GetValue<T>()) ✅ Supported
Async methods (async Task<T>) ✅ Supported
Struct / Record / Interface / Class methods ✅ Supported

Parameter Handling

Feature Status
params arrays (variadic) isVariadic: true
out modifier ✅ Type prefixed ("out int")
ref modifier ✅ Type prefixed ("ref int")
in modifier (C# 7.2) ✅ Type prefixed ("in double")
Optional parameters with defaults isOptional: true
[Attribute] annotations ✅ Extracted as @AttributeName

Visibility

Modifier Status
public
private (default)
protected
internal
protected internal (compound)
private protected (compound)

Intentionally Out of Scope

Construct Reason
Property accessors (get/set/init) Covered by field extraction
Indexers (this[int i]) Closer to properties than methods
Local functions Nested inside method bodies, not type members
Lambda expressions Anonymous, no name field

Test Coverage

62 tests total (37 existing Java/Kotlin + 25 new C#) — all passing.
TypeScript type check clean. Pre-commit hooks (eslint, prettier, tsc) all pass.

Files Changed

File Change
method-extractors/configs/csharp.ts New — C# MethodExtractionConfig
languages/csharp.ts Wire createMethodExtractor(csharpMethodConfig)
field-types.ts Add 'protected internal' and 'private protected' to FieldVisibility
field-extractors/configs/csharp.ts Compound visibility detection for field extractor consistency
test/unit/method-extraction.test.ts 25 new C# test cases

@magyargergo

Copy link
Copy Markdown
Collaborator Author

Pipeline Integration Verification

Traced the end-to-end integration from language provider through parse-worker to confirm the C# method extractor is fully wired into the ingestion pipeline.

Chain of execution

csharp.ts (language provider)
  └─ methodExtractor: createMethodExtractor(csharpMethodConfig)
         │
         ▼
parse-worker.ts (Phase 3+4: Parse + Resolve)
  └─ For each node where nodeLabel === 'Method' | 'Constructor' | 'Function':
       │
       ├─ 1. Check provider.methodExtractor exists           (line 1711)
       ├─ 2. findEnclosingClassNode(definitionNode)
       │      └─ Walks up AST looking for CLASS_CONTAINER_TYPES
       │         └─ Contains: class_declaration, interface_declaration,
       │            struct_declaration, record_declaration    (all C# types)
       ├─ 3. getMethodInfo(classNode, provider, context)     (line 1714)
       │      └─ Calls provider.methodExtractor.extract()
       │      └─ Caches results in methodInfoCache keyed by name:line
       ├─ 4. Enriches ParsedNode properties:
       │      ├─ parameterCount, parameterTypes, requiredParameterCount
       │      ├─ returnType
       │      ├─ visibility (incl. protected internal, private protected)
       │      ├─ isStatic
       │      ├─ isAbstract    ← from MethodExtractor
       │      ├─ isFinal       ← from MethodExtractor
       │      └─ annotations   ← from MethodExtractor
       ├─ 5. Falls back to extractMethodSignature when no
       │      MethodExtractor is available                   (line 1742)
       └─ 6. Properties written to both result.nodes
              AND result.symbols → stored in graph           (lines 1784–1847)

Verification points

Check Result
csharpProvider.methodExtractor set createMethodExtractor(csharpMethodConfig) in languages/csharp.ts:127
CLASS_CONTAINER_TYPES includes C# types class_declaration, struct_declaration, interface_declaration, record_declaration all present in ast-helpers.ts:107
getMethodInfo calls provider.methodExtractor.extract() parse-worker.ts:417
methodInfoCache keyed by name:line (handles overloads) parse-worker.ts:422
All MethodInfo fields propagated to ParsedNode.properties parameterCount, parameterTypes, returnType, visibility, isStatic, isAbstract, isFinal, annotations all spread into node properties at lines 1805–1815
Same fields propagated to ParsedSymbol ✅ Lines 1835–1846
Cache cleared between files methodInfoCache.clear() in clearCaches() at line 330

Conclusion

No additional wiring needed. The existing parse-worker infrastructure is generic — any language provider with a methodExtractor field is automatically picked up. The C# config follows the exact same code path as Java and Kotlin.

@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think while identifying C# languages gaps. Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert in static analysis tools and review this change from that perpective and review the architectural fit. Also check out previous comments and reason with their requests.

@github-actions

github-actions Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 5m 39s —— View job


C# MethodExtractor — Senior Compiler Front-End Review

Tasks

  • Gather context (PR body, comments, changed files list)
  • Read the C# method extractor config (csharp.ts)
  • Read the test file for coverage gaps
  • Read the base extractor framework (method-extractors/generic.ts)
  • Read the language provider wiring (languages/csharp.ts)
  • Read field-types.ts and field extractor changes
  • Deep-dive: AST correctness, C# grammar edge cases, architecture
  • Verify previous comment claims (pipeline integration, coverage table)
  • Write full review

Overview

Solid, well-structured implementation that follows the established JVM pattern faithfully. The config-driven factory approach is a good architectural choice. Below is a deep-dive from a compiler front-end / static-analysis perspective, organized by severity.


Verified Previous Comments

Pipeline integration trace (from magyargergo's second comment) is correct and accurate:

  • csharpProvider.methodExtractor is properly set at languages/csharp.ts:127
  • CLASS_CONTAINER_TYPES in ast-helpers.ts:107–126 includes all four C# container types (class_declaration, interface_declaration, struct_declaration, record_declaration)
  • Cache clearing in clearCaches() at workers/parse-worker.ts:330 is confirmed

Coverage table (from magyargergo's first comment) is largely accurate but has gaps — see below.


Bugs / Correctness Issues

1. Primary Constructors (C# 12) — Silently Missed

class Point(int x, int y) { } and record Person(string Name, int Age) { } produce no constructor_declaration AST node in tree-sitter-c-sharp. The parameter list is a direct child of class_declaration/record_declaration itself. The method extractor only scans the declaration_list body for constructor_declaration nodes, so primary constructors are never extracted.

The tree-sitter query file already captures this at tree-sitter-queries.ts:576–577:

(class_declaration name: (identifier) @name (parameter_list) @definition.constructor)
(record_declaration name: (identifier) @name (parameter_list) @definition.constructor)

...but the method extractor config has no corresponding handler. The coverage table in the PR comment claims "Constructors (instance + static)" are supported — primary constructors are a counterexample.

Fix this →

2. Destructor Name Ambiguity

~Resource() is extracted with name: 'Resource' — same as the constructor. The test at line 948 confirms this:

const dtor = result!.methods.find((m) => m.name === 'Resource');

From a static analysis perspective, a destructor and a constructor are semantically distinct and should be distinguishable by name. The de-facto convention is to either prefix with ~ or use a distinct name like ~Resource. The current behavior means that if a class has both a constructor and a destructor, they both appear as name: 'Resource' — distinguishable only by line number via the methodInfoCache key.

Fix this →

3. collectModifierTexts is Duplicated

Identical implementation exists in both:

  • method-extractors/configs/csharp.ts:20–29
  • field-extractors/configs/csharp.ts:13–22

This should live in field-extractors/configs/helpers.ts and be re-exported. As-is, any future fix to modifier detection must be applied in two places.

Fix this →


Missing C# Language Semantics

4. virtual and override Modifiers Not Modeled

C# has a three-way distinction: virtual (open for override), override (currently overriding), sealed override (terminates override chain). The current schema maps sealedisFinal which is correct, but virtual and override are silently dropped. For call-graph analysis, knowing whether a call site goes through a virtual dispatch slot matters.

This is a design gap in MethodInfo (no isVirtual/isOverride field), not just a C# config issue — but it would be worth documenting as a known limitation.

5. Generic Method Type Parameters Stripped

public T GetValue<T>() { } is extracted with name: 'GetValue'. The type parameter list <T> is a separate type_parameter_list field on the method_declaration node and is ignored. This is acceptable for simple cases but means that GetValue<T>() and a hypothetical GetValue() overload are indistinguishable by name alone. The methodInfoCache uses name:line as the key so collisions only happen across files, but it's worth documenting.

6. partial Method Declarations

partial void OnChanged();        // declaration, no body
partial void OnChanged() { }    // implementation, has body

Both will be extracted. The declaration variant has no body and isn't in an interface, so isAbstract returns false — semantically misleading. A partial method declaration IS conceptually abstract (it may be discarded at compile time if there's no implementation). This is an obscure edge case but worth a test to document the behavior.

7. async Not Captured

C# async methods differ from sync ones in their return type semantics (Task<T> vs T) and their execution model. The MethodInfo schema has no isAsync field, so async Task<T> GetAsync() and Task<T> GetNotAsync() are identical in the extracted metadata. Not a bug in this PR (schema limitation), but given C#'s heavy async usage this is worth noting as a known gap.


params Handling — Grammar-Version Coupling

The code at csharp.ts:59–93 contains a careful comment:

"In tree-sitter-c-sharp, the params variadic keyword is NOT wrapped inside a parameter node."

This is a tree-sitter-c-sharp grammar implementation detail, not a C# language specification. The behavior was verified against the installed grammar version and the tests pass. However, this is fragile: if the grammar is ever updated to wrap params inside a standard parameter node with a modifier child, the special path (lines 61–93) would silently not match, and params parameters would be extracted with isVariadic: false and no error.

Recommendation: Add a comment with the exact tree-sitter-c-sharp grammar version this was verified against, and add a compile-time or test-time assertion that protects this assumption. At minimum, add a test that would fail visibly if the grammar changes behavior.


Architectural Observations

STATIC_OWNER_TYPES in generic.ts Leaks JVM Semantics

// generic.ts:20
const STATIC_OWNER_TYPES = new Set(['companion_object', 'object_declaration']);

This is checked on every call to buildMethod for ALL language configs, including C#. C# container types (class_declaration, struct_declaration, etc.) will never match, so there's no bug — but this is a Kotlin-specific concern embedded in the shared factory. Consider moving this to a staticOwnerTypes optional config field, or documenting that this is intentional JVM contamination.

MethodVisibility = FieldVisibility Allows Impossible Values

method-types.ts:8: export type MethodVisibility = FieldVisibility

This means a C# MethodExtractionConfig.extractVisibility() is typed to return 'fileprivate' or 'open' (Swift) or 'package' (Java), which are nonsensical for C#. A narrower union type per language config would be more correct, though the current implementation never produces invalid values in practice.

bodyNodeTypes Includes Only declaration_list

C# uses declaration_list for all type bodies (class/struct/interface/record), and this is correct. The addNestedBodies function in generic.ts would also recurse into nested declaration_list nodes (e.g., nested classes). This means methods of nested classes would be attributed to the outer class's owner — verify this is intentional or that nested types are pre-filtered by the caller.


Test Coverage Gaps

Scenario Covered Notes
Primary constructor class Point(int x, int y) Silently not extracted
Destructor name disambiguation ⚠️ Tested but extracts ambiguous name
Expression-bodied method (=> expr) No test at all
virtual method Not modeled
override method Not modeled
Generic method T GetValue<T>() Name strips type params
partial method (declaration vs impl) Behavior undefined
async method Not modeled
Nested class — outer vs inner attribution Worth explicit test
params grammar version change resilience Fragile assumption

Summary

The core implementation is correct and well-tested for the declared scope. The JVM pattern was followed faithfully, the wiring is verified, and the compound visibility handling is a thoughtful addition. The main actionable items are:

  1. Fix: collectModifierTexts duplication → move to helpers.ts
  2. Fix: Destructor name should be ~Resource, not Resource
  3. Document/test: Primary constructor gap (C# 12)
  4. Document: params grammar version dependency
  5. Document: virtual, override, async, partial, generic type params as known limitations in a code comment

The coverage table in the PR body is aspirational in a few areas (primary constructors, some modifier semantics) — adjusting it to reflect the actual coverage would keep the documentation honest.
· Branch

…ields

Address all known limitations from review:

- Primary constructor support (C# 12): add extractPrimaryConstructor to
  MethodExtractionConfig and extractPrimaryFields to FieldExtractionConfig.
  Record params become public readonly properties; class params become
  private captured fields.
- Add isVirtual, isOverride, isAsync optional fields to MethodInfo,
  MethodExtractionConfig, NodeProperties, and parse-worker propagation.
- Detect virtual/override/async modifiers in C# method config.
- Move collectModifierTexts to shared helpers.ts (deduplicate).
- Fix destructor name to ~ClassName (disambiguates from constructor).
- Add expression-bodied method test.
- 118 tests total across method + field extraction suites, all passing.
@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think while identifying C# languages gaps. Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert in static analysis tools and review this change from that perpective and review the architectural fit. Also check out previous comments and reason with their requests.

@github-actions

github-actions Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 4m 56s —— View job


C# MethodExtractor — Senior Compiler Front-End Review

Tasks

  • Gather context (PR body, comments, changed files list)
  • Read current state of all changed files (post-fix commits)
  • Verify previous review findings against current code
  • Deep-dive: AST correctness, C# grammar edge cases, architecture
  • Write full review

Context: Previous Review vs. Current State

The previous Claude review (in the comments) identified several issues. I read the current source after all 5 commits. Here is the verified status of each finding, plus new findings from this deep-dive.


✅ All Previous Findings Addressed

Previous Issue Status Evidence
Primary constructors (C# 12) silently missed Fixed extractPrimaryConstructor in csharp.ts:266-310; hooked in generic.ts:67-70; two tests at lines 1052–1093
Destructor name Resource instead of ~Resource Fixed extractName branches on destructor_declaration → returns `~${name}` at csharp.ts:184-186; test at line 948
collectModifierTexts duplicated Fixed Now in helpers.ts:157-166, imported by both configs — no duplication
virtual / override not modeled Fixed MethodInfo has optional isVirtual?, isOverride?, isAsync? fields; config implements all three; tests at 1096–1148
async not captured Fixed (same as above)
Expression-bodied members untested Fixed Test at lines 1034-1049; no special handling needed because tree-sitter-c-sharp still emits a method_declaration node

The response rate on the previous review is excellent — every actionable item was acted on.


New Findings

1. Bug: Primary Constructor Annotations Are Contaminated by Class-Level Attributes

extractPrimaryConstructor at csharp.ts:306 calls extractCSharpAnnotations(ownerNode), where ownerNode is the class_declaration or record_declaration. This picks up class-level [Attribute] decorators and incorrectly assigns them as the constructor's annotations.

[Serializable]
public class Point(int x, int y) { }
// → primary constructor gets annotations: ['@Serializable']
//   but @Serializable belongs to the class, not the constructor

In C#, there is no syntax to place an attribute directly on a primary constructor (unlike parameters, which use [param: Attr]). So the correct value for annotations on a primary constructor is always [].

The primary constructor tests at lines 1052–1093 don't assert on annotations, so this slips through.

Fix this →


2. Consistency Gap: record_struct_declaration Absent from Method and Field Extractor typeDeclarationNodes

export-detection.ts:76 already recognizes record_struct_declaration (and record_class_declaration at line 77), but these are absent from:

  • method-extractors/configs/csharp.ts:167-172typeDeclarationNodes
  • field-extractors/configs/csharp.ts:20-25typeDeclarationNodes
  • utils/ast-helpers.ts:107-128CLASS_CONTAINER_TYPES
  • tree-sitter-queries.ts:554-610 — no (record_struct_declaration ...) capture pattern

Impact: record struct types (C# 10, e.g. record struct Point(int X, int Y)) are known to the export checker but produce no type/method/field nodes — silently incomplete ingestion. The gap predates this PR (it's in the query layer), but this PR is the natural moment to document or fix it since it adds the C# method layer.

Note: field-extractors/configs/csharp.ts:100 also has a hard check ownerNode.type === 'record_declaration' for primary-field visibility inference — even if record_struct_declaration were added to the type nodes list, primary fields on record struct would incorrectly get private instead of public visibility.

Fix this →


3. params Path: Grammar Version Not Pinned

The comment at csharp.ts:30-40 carefully documents the tree-sitter-c-sharp grammar deviation for params, but doesn't pin the specific grammar version. The declared dependency in package.json is "tree-sitter-c-sharp": "^0.23.1" — the floating ^ means any 0.x.y minor update could silently change the behavior of this path. If the grammar ever wraps params inside a standard parameter node (as a named modifier child), the special path at lines 59-93 would simply not fire, producing isVariadic: false with no error.

Recommendation: Add // verified against tree-sitter-c-sharp ^0.23.1 to the comment block, and consider pinning the grammar version or adding a guard test that would fail visibly if the assumption breaks.


4. partial Method Behavior Undocumented

partial void OnChanged();   // declaration-only, no body
partial void OnChanged() { }  // implementation

Both variants are method_declaration nodes. The declaration-only form has no body field. Since isAbstract only returns true for the abstract keyword or bodyless interface methods (not partial), it returns false here — which is semantically ambiguous (a declaration-only partial method is discarded by the compiler if no implementation exists).

This isn't a high-priority bug, but a skipped test with a comment like // partial method declarations produce isAbstract: false — documented behavior would make the intent explicit and prevent future "fix" attempts that break intentional behavior.


5. Generic Method Type Parameters Stripped — Undocumented

public T GetValue<T>()name: 'GetValue' (type parameter list <T> silently dropped). This is acceptable for the current use case (call-graph analysis primarily uses names), but two overloads GetValue<T>() and GetValue() would be indistinguishable by name. No comment or test documents this intentional omission.


Architectural Observations (Remaining from Previous Review)

STATIC_OWNER_TYPES JVM Contamination

generic.ts:20: const STATIC_OWNER_TYPES = new Set(['companion_object', 'object_declaration']) is evaluated unconditionally for all language configs including C#. C# container types never match, so there's no bug, but this is Kotlin-specific logic embedded in the shared factory. A comment acknowledging this would clarify intent.

MethodVisibility = FieldVisibility

method-types.ts:8: The alias allows MethodVisibility to carry Swift (fileprivate, open) or Java (package) values that are nonsensical in C#. Not a runtime bug since the C# config never produces these values, but a per-language narrowing type (e.g., type CSharpVisibility = 'public' | 'private' | 'protected' | 'internal' | 'protected internal' | 'private protected') would make it impossible to write a C# extractor that returns a Swift visibility by accident.


Updated Coverage Table

Scenario Status Notes
Primary constructor class Point(int x, int y) ✅ Extracted Via extractPrimaryConstructor
Primary constructor annotations ⚠️ Bug Class-level attributes bleed into ctor annotations
Destructor ~Resource naming ✅ Fixed ~ prefix in extractName
virtual / override / async ✅ Modeled Optional fields on MethodInfo
Expression-bodied => expr ✅ Covered Same AST node type, works naturally
collectModifierTexts deduplication ✅ Fixed In helpers.ts
record struct (C# 10) ❌ Missing Not in queries, extractors, or CLASS_CONTAINER_TYPES
partial method declaration ⚠️ Undocumented isAbstract: false, no test documents this
Generic method T GetValue<T>() ⚠️ Undocumented Type params stripped, no comment
params grammar version resilience ⚠️ Fragile Version not pinned in comment

Summary

The implementation is solid. The previous review's six actionable items were all addressed cleanly. The three items that warrant action now:

  1. Fix (low-effort): Empty annotations: [] for primary constructors — class attributes should not bleed onto the constructor
  2. Fix or document (medium): record_struct_declaration — either add it to the extractor chain and tree-sitter queries, or add a // record struct not yet supported comment in typeDeclarationNodes cross-referencing export-detection.ts:76
  3. Comment (low-effort): Pin grammar version in the params comment block, and add skipped/documented tests for partial and generic type params
    · Branch

- Fix primary constructor annotations: use [] instead of extracting
  class-level attributes (C# has no syntax for ctor-specific attributes)
- Add record_struct_declaration to typeDeclarationNodes in both method
  and field extractors, CLASS_CONTAINER_TYPES, and isRecord visibility check
- Pin tree-sitter-c-sharp version (^0.23.1) in params comment
@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think while identifying C# languages gaps. Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert in static analysis tools and review this change from that perpective and review the architectural fit. Also check out previous comments and reason with their requests.

@github-actions

github-actions Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 3m 45s —— View job


C# MethodExtractor — Round 3 Deep Review

Tasks

  • Gather context (PR body, all prior comments, changed files)
  • Read current state of all changed files
  • Verify second review's findings against current code
  • Deep-dive: AST correctness, grammar edge cases, architecture
  • Identify new gaps not caught in prior reviews
  • Write full review

Verified Status of Round 2 Findings

Round 2 Issue Status Evidence
Primary constructor annotations contaminated ✅ Fixed annotations: [] hardcoded at csharp.ts:307
record_struct_declaration missing ⚠️ Partially fixed Added to extractor configs + CLASS_CONTAINER_TYPES, but query layer still missing — see Finding 1 below
params grammar version not pinned ✅ Fixed Comment at csharp.ts:30–40 now reads "verified against ^0.23.1"
partial method undocumented ❌ Not addressed No test, no comment documenting isAbstract: false behavior
Generic type params stripped undocumented ❌ Not addressed Still no comment or test

Finding 1 — record_struct_declaration Fix is Incomplete (Pipeline Gap Remains)

The round 2 fix correctly added record_struct_declaration to:

  • method-extractors/configs/csharp.ts:172typeDeclarationNodes
  • field-extractors/configs/csharp.ts:25typeDeclarationNodes
  • field-extractors/configs/csharp.ts:102isRecord check ✅
  • utils/ast-helpers.ts:113CLASS_CONTAINER_TYPES

But two critical pieces remain unfixed:

1a. tree-sitter-queries.ts has no capture pattern for record_struct_declaration.

The query file only has:

(record_declaration name: (identifier) @name) @definition.record

There is no equivalent (record_struct_declaration ...) pattern. This means record struct Point { } types are never captured as type definition nodes by the parse-worker — the method extractor will never receive a record_struct_declaration node to process. The fix in the extractor configs is dead code until a query exists. The same is true for the primary constructor query at line 577 — only record_declaration is covered, not record_struct_declaration.

1b. CONTAINER_TYPE_TO_LABEL in ast-helpers.ts:131 has no entry for record_struct_declaration.

export const CONTAINER_TYPE_TO_LABEL: Record<string, string> = {
  record_declaration: 'Record',
  // record_struct_declaration is absent → falls through to || 'Class' fallback at line 275

If a record_struct_declaration node somehow reaches findEnclosingTypeNodeId, it would produce a 'Class'-labelled container — semantically incorrect for a value-type record struct.

1c. No test for isTypeDeclaration with record_struct_declaration, and no test for method or primary constructor extraction from record struct.

Fix this →


Finding 2 — record_class_declaration Entirely Absent from Pipeline (Pre-existing)

export-detection.ts:77 already recognises record_class_declaration (the explicit record class Foo {} C# 10 syntax). It appears nowhere else in the codebase:

  • Not in tree-sitter-queries.ts (no capture pattern)
  • Not in method-extractors/configs/csharp.ts typeDeclarationNodes
  • Not in field-extractors/configs/csharp.ts typeDeclarationNodes
  • Not in CLASS_CONTAINER_TYPES
  • Not in CONTAINER_TYPE_TO_LABEL

This predates the PR, but since this PR is the natural place to audit C# type coverage, documenting it is relevant. If the grammar emits record_class_declaration as a distinct node from record_declaration, methods and fields on record class Foo {} types are silently not extracted. Worth a comment cross-referencing the export-detection entry.


Finding 3 — sealed override Compound Modifier Test Is Incomplete

The test at method-extraction.test.ts:736–746 covers sealed override and asserts isFinal: true. However it does not also assert isOverride: true, which buildMethod would set via the isOverride?.(node) spread at generic.ts:170.

public sealed override string ToString() { return ""; }
// Expected: isFinal: true AND isOverride: true

The current test is:

expect(result!.methods[0].isFinal).toBe(true);
// isOverride is not checked

This gap means that if isOverride detection were accidentally broken specifically for the sealed override case, the test would not catch it.

Fix this →


Finding 4 — partial Method Declaration Behavior (Undocumented, Round 2 Carry-over)

partial void OnChanged();   // declaration-only — no body, not in interface
partial void OnChanged() { }  // implementation

Both are method_declaration nodes. The declaration-only form has no body field. Since isAbstract only fires for the abstract keyword or bodyless interface methods, a partial method declaration returns isAbstract: false — which is semantically ambiguous (the compiler discards it at compile time if no implementation exists).

A documented skip test would make the intentional behavior explicit:

it.skip('partial method declaration returns isAbstract: false — documented behavior: ' +
  'partial is not modeled as abstract; callers relying on this should check for partial modifier', () => {});

Finding 5 — Generic Method Type Parameters Stripped (Undocumented, Round 2 Carry-over)

public T GetValue<T>()name: 'GetValue'. The <T> is a type_parameter_list field on the method_declaration node and is silently dropped. Two overloads GetValue<T>() and GetValue() would be indistinguishable by name in the extracted graph. No comment in extractName acknowledges this.

A one-line comment explaining the intentional omission would prevent future "fix" attempts that break other things.


Architectural Observations (Outstanding from Previous Review)

STATIC_OWNER_TYPES JVM contamination (generic.ts:20): companion_object and object_declaration checked unconditionally for all languages. C# types never match, so no bug — but a comment acknowledging this is Kotlin-specific logic in a shared factory would clarify intent for future language authors.

MethodVisibility = FieldVisibility (method-types.ts:8): The alias allows values like 'fileprivate' or 'open' (Swift) or 'package' (Java) to type-check as valid MethodVisibility for C# extractors. Not a runtime issue, but a per-language narrowing type would make it impossible to accidentally return a Swift visibility from a C# extractor.


Updated Coverage Table

Scenario Status Notes
Primary constructor (C# 12) — class/record ✅ Fixed extractPrimaryConstructor
Primary constructor annotations ✅ Fixed Hardcoded []
Destructor ~Resource naming ✅ Fixed ~ prefix in extractName
virtual / override / async ✅ Fixed Optional fields on MethodInfo
Expression-bodied => expr ✅ Covered Same AST node type
collectModifierTexts deduplication ✅ Fixed In helpers.ts
params grammar version comment ✅ Fixed "verified against ^0.23.1"
sealed override compound test ⚠️ Incomplete isFinal tested; isOverride not verified in same case
record_struct_declaration extractors ✅ Fixed In method/field configs and CLASS_CONTAINER_TYPES
record_struct_declaration query ❌ Missing No tree-sitter capture pattern — dead code
record_struct_declaration label mapping ❌ Missing Falls to 'Class' in CONTAINER_TYPE_TO_LABEL
record_struct_declaration tests ❌ Missing No isTypeDeclaration, no method, no primary ctor test
record_class_declaration (pre-existing) ❌ Undocumented In export-detection only; rest of pipeline blind to it
partial method declaration ⚠️ Undocumented isAbstract: false, no test documents this
Generic method T GetValue<T>() ⚠️ Undocumented Type params stripped, no comment

Summary

The implementation is solid and the three actionable items from round 2 have been addressed cleanly. The main remaining gap is record_struct_declaration — the extractor configs were updated, but without a tree-sitter query to capture record struct as a definition node, the fix is effectively unreachable in production. The label mapping omission compounds this.

Prioritized action items:

  1. Must fix: Add (record_struct_declaration ...) capture pattern to tree-sitter-queries.ts + add record_struct_declaration → 'Struct' to CONTAINER_TYPE_TO_LABEL
  2. Should fix: sealed override test should assert both isFinal and isOverride
  3. Nice to have: Documented skip tests for partial and generic type params; comment on record_class_declaration gap
    · Branch

…rride test

- Add record_struct_declaration capture patterns to tree-sitter-queries.ts
  (type definition + primary constructor)
- Add record_struct_declaration → 'Struct' in CONTAINER_TYPE_TO_LABEL
- Assert isOverride: true alongside isFinal in sealed override test
@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think while identifying C# languages gaps. Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert in static analysis tools and review this change from that perpective and review the architectural fit. Also check out previous comments and reason with their requests.

@github-actions

github-actions Bot commented Mar 29, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 4m 9s —— View job


C# MethodExtractor — Round 4 Deep Review

Tasks

  • Gather context (PR body, all prior comments)
  • Read current state of all changed files
  • Verify Round 3 findings against current code
  • Deep-dive: new gaps, AST correctness, grammar edge cases, architecture
  • Write full review

Verified Status of Round 3 Findings

Round 3 Issue Status Evidence
record_struct_declaration — query missing ✅ Fixed tree-sitter-queries.ts:561, 579
record_struct_declarationCONTAINER_TYPE_TO_LABEL missing ✅ Fixed ast-helpers.ts:145'Struct'
record_struct_declaration — extractor tests ❌ Not addressed No isTypeDeclaration, no method, no primary ctor test for record struct
sealed overrideisOverride not asserted ✅ Fixed method-extraction.test.ts:745–746 now asserts both isFinal and isOverride
partial method behavior undocumented ❌ Not addressed Still no test or comment
Generic method type params stripped undocumented ❌ Not addressed Still no comment in extractName
record_class_declaration gap ❌ Not addressed Still only in export-detection.ts:77, absent everywhere else

Finding 1 — Label Mismatch: record_struct_declaration Gets 'Record' Node but 'Struct' HAS_METHOD Lookup

This is a new bug introduced by the Round 3 fix that was not caught in the previous review.

The query at tree-sitter-queries.ts:561 assigns @definition.record to record_struct_declaration:

(record_struct_declaration name: (identifier) @name) @definition.record

getLabelFromCaptures (ast-helpers.ts:203) maps definition.record → label 'Record'.

But CONTAINER_TYPE_TO_LABEL['record_struct_declaration'] (ast-helpers.ts:145) = 'Struct'.

The consequence:

  1. A record struct Point type node is stored in the graph with ID generateId('Record', '<file>:Point')
  2. When a method inside Point is processed, findEnclosingClassId walks up the AST to record_struct_declaration, reads CONTAINER_TYPE_TO_LABEL'Struct', and produces generateId('Struct', '<file>:Point')
  3. These two IDs never match → HAS_METHOD edges for record struct types are never created

The fix is to change the query capture tag to @definition.struct so both paths agree on 'Struct':

- (record_struct_declaration name: (identifier) @name) @definition.record
+ (record_struct_declaration name: (identifier) @name) @definition.struct

@definition.struct is already in DEFINITION_CAPTURE_KEYS (ast-helpers.ts:19) and getLabelFromCaptures (ast-helpers.ts:190) maps it to 'Struct', consistent with the CONTAINER_TYPE_TO_LABEL entry.

Fix this →


Finding 2 — No Tests for record_struct_declaration

Three tests are missing that would have caught Finding 1:

// 1. isTypeDeclaration should recognize record_struct_declaration
it('recognizes record_struct_declaration', () => {
  const tree = parseCSharp('public record struct Point { }');
  expect(extractor.isTypeDeclaration(tree.rootNode.child(0)!)).toBe(true);
});

// 2. Method extraction from record struct
it('extracts methods from record struct', () => {
  const tree = parseCSharp(`
    public record struct Measurement(double Value) {
      public string Format() => Value.ToString();
    }
  `);
  const node = tree.rootNode.child(0)!;
  const result = extractor.extract(node, csharpCtx);
  expect(result!.ownerName).toBe('Measurement');
  expect(result!.methods.find(m => m.name === 'Format')).toBeDefined();
});

// 3. Primary constructor from record struct
it('extracts primary constructor from record_struct_declaration', () => {
  const tree = parseCSharp('public record struct Point(int X, int Y);');
  const node = tree.rootNode.child(0)!;
  const result = extractor.extract(node, csharpCtx);
  const ctor = result!.methods.find(m => m.name === 'Point');
  expect(ctor).toBeDefined();
  expect(ctor!.parameters).toHaveLength(2);
});

Fix this →


Finding 3 — record_class_declaration in export-detection.ts:77 Is Likely Dead Code

In tree-sitter-c-sharp, both record Person(...) and record class Person(...) produce a record_declaration AST node — the optional class keyword is absorbed by the grammar rule and no separate record_class_declaration node type exists. The entry at export-detection.ts:77 therefore never matches any real node and has no corresponding entry in any other part of the pipeline.

This is pre-existing, but given that C# support is being actively extended in this PR, a one-line comment would prevent future confusion:

// 'record_class_declaration' is listed here defensively; tree-sitter-c-sharp
// absorbs the optional 'class' keyword into 'record_declaration'. No separate
// node type is emitted in practice.
'record_class_declaration',

Remaining Carry-Over Items (Not Yet Addressed)

These were identified in earlier reviews and remain unresolved:

partial method declaration behavior — declaration-only partial void OnChanged(); returns isAbstract: false. Since partial declarations are semantically discarded by the compiler if no implementation exists, this is misleading. A documented skip test would make the intentional behavior explicit:

it.skip('partial method declaration: isAbstract is false — documented limitation: ' +
  'partial keyword is not modeled; declaration-only partial methods are not distinguished ' +
  'from implemented ones', () => {});

Generic method type parameters strippedpublic T GetValue<T>() extracts name: 'GetValue'. A one-line comment in extractName explaining this is intentional would prevent future attempts to "fix" it.


Architecture Observations (Persistent from Prior Reviews)

STATIC_OWNER_TYPES JVM contamination (generic.ts:20): companion_object and object_declaration are checked unconditionally for all language configs including C#. These will never match C# container types. A comment acknowledging this is intentional JVM-specific logic would clarify the design for future language authors adding configs.

MethodVisibility = FieldVisibility (method-types.ts:8): The alias permits 'fileprivate' (Swift), 'open' (Swift), and 'package' (Java) as legal MethodVisibility values in any C# extractor. A narrower per-language type (e.g., type CSharpVisibility = 'public' | 'private' | 'protected' | 'internal' | 'protected internal' | 'private protected') would make it impossible to accidentally return a Swift visibility from a C# config.


Updated Coverage Table

Scenario Status Notes
Primary constructors (class/record) extractPrimaryConstructor at csharp.ts:267
Primary constructor annotations Hardcoded [] at csharp.ts:307
Destructor ~Resource naming ~ prefix at csharp.ts:185–187
virtual / override / async Optional fields in method-types.ts
Expression-bodied => expr Same AST node
collectModifierTexts deduplication In helpers.ts:157
params grammar version comment "verified against ^0.23.1" at csharp.ts:30
sealed override test completeness Both isFinal and isOverride asserted at line 745–746
record_struct_declaration extractor configs In typeDeclarationNodes and CLASS_CONTAINER_TYPES
record_struct_declaration tree-sitter query tree-sitter-queries.ts:561, 579
record_struct_declaration — label mismatch Bug @definition.record'Record', but CONTAINER_TYPE_TO_LABEL'Struct'; HAS_METHOD edges broken
record_struct_declaration tests No isTypeDeclaration, method, or primary ctor test
record_class_declaration ⚠️ Dead code In export-detection.ts:77 only; no real tree-sitter node
partial method declaration ⚠️ Undocumented isAbstract: false, no documented test
Generic method type params stripped ⚠️ Undocumented No comment in extractName

Summary

One new bug to fix before merge:

  1. Change @definition.record@definition.struct in tree-sitter-queries.ts:561 for record_struct_declaration — the current mismatch with CONTAINER_TYPE_TO_LABEL silently breaks HAS_METHOD edge creation for all record struct types.

Two quick additions:
2. Add three record struct tests (would have caught bug #1)
3. Add documented skip tests or comments for partial and generic type params

The rest of the implementation is solid and all six items from the Round 1 review plus the three from Round 2 have been cleanly addressed.
· Branch

…nted limitation tests

- Fix record_struct_declaration query tag: @Definition.struct (not @definition.record)
  to match CONTAINER_TYPE_TO_LABEL and prevent broken HAS_METHOD edges
- Add 3 record struct tests: isTypeDeclaration, method extraction, primary constructor
- Add documented limitation tests: partial method (isAbstract: false), generic type
  parameter stripping (name excludes <T>)
@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think while identifying C# languages gaps. Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert in static analysis tools and review this change from that perpective and review the architectural fit. Also check out previous comments and reason with their requests.

@github-actions

github-actions Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 4m 24s —— View job


C# MethodExtractor — Round 5 Deep Review

Tasks

  • Gather context (PR body, all prior comments)
  • Read current state of all changed files
  • Verify Round 4 findings against current code
  • Deep-dive: new gaps, AST correctness, grammar edge cases, architecture
  • Write full review

Round 4 Findings — Verified Status

Issue Status Evidence
record_struct_declaration — query label mismatch ✅ Fixed tree-sitter-queries.ts:561 now uses @definition.struct, consistent with CONTAINER_TYPE_TO_LABEL['record_struct_declaration'] = 'Struct' at ast-helpers.ts:145
record_struct_declaration — three tests ✅ Fixed method-extraction.test.ts:1151–1180: isTypeDeclaration, method extraction, primary constructor
sealed overrideisOverride also asserted ✅ Fixed method-extraction.test.ts:745–746 asserts both isFinal and isOverride
partial method behavior undocumented ✅ Fixed Active test (not skip) at lines 1186–1198 documenting isAbstract: false
Generic type params stripped undocumented ✅ Fixed Active test at lines 1203–1213 documenting name stripping
record_class_declaration dead code ❌ No comment added Still only in export-detection.ts:77; no comment

All five actionable items from Round 4 are addressed. The sole carry-over is the record_class_declaration dead-code comment.


Finding 1 — extractCSharpAnnotations Doesn't Filter Attribute Targets

extractCSharpAnnotations (csharp.ts:146–159) collects every attribute inside every attribute_list child. In C#, attribute lists can carry a target specifier that redirects the attribute to a different declaration element:

[return: NotNull]          // targets the return value, not the method
[method: MethodImpl(MethodImplOptions.AggressiveInlining)]  // method target
public string GetName() { }

In tree-sitter-c-sharp, an attribute_list with a target has an attribute_target_specifier named child followed by the attribute node(s). The current code iterates all named attribute children unconditionally:

for (let j = 0; j < child.namedChildCount; j++) {
  const attr = child.namedChild(j);
  if (!attr || attr.type !== 'attribute') continue;   // ← 'attribute_target_specifier' is skipped
  const nameNode = attr.childForFieldName('name');
  if (nameNode) annotations.push('@' + nameNode.text); // @NotNull incorrectly attributed to method
}

The attribute_target_specifier node is silently skipped because its type is not 'attribute', but the attribute node that follows it in the same attribute_list is still collected. The result: [return: NotNull] produces annotations: ['@NotNull'] on the method — semantically incorrect. The @NotNull belongs to the return type.

Practical impact: P/Invoke signatures are a common case:

[return: MarshalAs(UnmanagedType.Bool)]
public static extern bool IsWindow(IntPtr hWnd);

This method would carry annotations: ['@MarshalAs'] rather than [], polluting the annotation list.

Fix: Skip attribute_list nodes that contain an attribute_target_specifier (or at minimum, skip those whose target is not method or absent):

function extractCSharpAnnotations(node: SyntaxNode): string[] {
  const annotations: string[] = [];
  for (let i = 0; i < node.namedChildCount; i++) {
    const child = node.namedChild(i);
    if (!child || child.type !== 'attribute_list') continue;

    // Skip attribute lists with a target specifier (e.g., [return: ...], [param: ...])
    const hasTarget = Array.from({ length: child.namedChildCount }, (_, j) => child.namedChild(j))
      .some(c => c?.type === 'attribute_target_specifier');
    if (hasTarget) continue;

    for (let j = 0; j < child.namedChildCount; j++) {
      const attr = child.namedChild(j);
      if (!attr || attr.type !== 'attribute') continue;
      const nameNode = attr.childForFieldName('name');
      if (nameNode) annotations.push('@' + nameNode.text);
    }
  }
  return annotations;
}

Fix this →


Finding 2 — Static Constructor Name Collision (Undocumented)

C# allows a static constructor: static Foo() { }. The current extractor produces:

Field Value
name 'Foo'
isStatic true
returnType null
parameters []
visibility 'private' (default — static ctors have no visibility modifier)

An instance constructor public Foo(int x) produces name: 'Foo' with isStatic: false. Both live in the same methods array distinguished only by isStatic and line. This is acceptable for call-graph analysis (static constructors are rarely called explicitly), but there is no test documenting this behavior. If a future refactor changed the naming convention for static constructors (e.g. prefixing with static), the breakage would be silent.

A one-line documented test would anchor the behavior:

it('static constructor is extracted as isStatic: true, same name as class', () => {
  const tree = parseCSharp(`
    public class Config { static Config() { } }
  `);
  const result = extractor.extract(tree.rootNode.child(0)!, csharpCtx);
  const ctor = result!.methods.find(m => m.name === 'Config');
  expect(ctor!.isStatic).toBe(true);
  expect(ctor!.parameters).toHaveLength(0);
});

Fix this →


Finding 3 — partial Method Pair Produces Two Entries (Undocumented)

The "documented limitations" test at method-extraction.test.ts:1186 tests a declaration-only partial void OnChanged() correctly. However, when both the declaration and the implementation coexist in the same declaration_list:

public partial class Foo {
  partial void OnChanged();       // ← declaration
  partial void OnChanged() { }   // ← implementation
}

Both method_declaration nodes are traversed in extractMethodsFromBody. The result would be two MethodInfo entries both named OnChanged at different source lines. For call-graph purposes this is harmless (the method cache uses name:line), but for consumers that enumerate methods by name, the duplication is unexpected.

A test documenting that the pair produces two entries — or a comment in the existing test — would prevent confusion:

// note: if both declaration and implementation of a partial method appear in
// the same declaration_list, two MethodInfo entries are produced (one per node).
// Deduplication is the caller's responsibility.

Finding 4 — record_class_declaration Dead Code (No Comment, Carry-Over)

export-detection.ts:77 contains 'record_class_declaration' in the type detection set. This appears nowhere else in the codebase (not in queries, not in extractors, not in CLASS_CONTAINER_TYPES). In tree-sitter-c-sharp, both record Foo {} and record class Foo {} produce a record_declaration AST node — the optional class keyword is absorbed by the grammar. The record_class_declaration entry in export-detection.ts never matches a real tree-sitter node and is effectively dead code.

A single comment would prevent future engineers from trying to "fix" this by adding the missing support:

// 'record_class_declaration' is listed defensively. In tree-sitter-c-sharp, the
// optional 'class' keyword in 'record class Foo {}' is absorbed into a regular
// 'record_declaration' node — no separate 'record_class_declaration' node is
// emitted by the grammar in practice.
'record_class_declaration',

Architectural Observations (Persistent)

These were raised in prior reviews and remain unaddressed. Both are low-severity, but documenting them in code comments would benefit future language authors:

STATIC_OWNER_TYPES JVM contamination (generic.ts:20): The constant new Set(['companion_object', 'object_declaration']) is evaluated on every buildMethod call for all language configs, including C#. These types never appear in C# ASTs, so there is no runtime impact. A comment would clarify intent:

// JVM-specific: companion_object / object_declaration members (Kotlin) are static by convention.
// Other language configs never produce these owner node types.
const STATIC_OWNER_TYPES = new Set(['companion_object', 'object_declaration']);

MethodVisibility = FieldVisibility (method-types.ts:8): The alias allows 'fileprivate' (Swift), 'open' (Swift), and 'package' (Java) to be valid MethodVisibility values in the C# extractor's type signature. The C# config never produces these values, so there is no runtime impact. A narrower per-language visibility type (type CSharpMethodVisibility = 'public' | 'private' | 'protected' | 'internal' | 'protected internal' | 'private protected') would make the type system enforce C#-validity, but this is a larger refactor given the shared type alias pattern.


Coverage Table — Final State

Scenario Status Notes
Regular methods (all modifiers) Covered
Constructors (instance) Covered
Static constructors ⚠️ Undocumented Extracts correctly but no test; name collision with instance ctor documented only by isStatic
Destructors (~Resource) ~ prefix in extractName
Operator overloads Covered
Conversion operators Covered
Expression-bodied members Same AST node
Abstract / sealed / static / virtual / override / async All modeled and tested
Primary constructors (C# 12) extractPrimaryConstructor
Primary constructor annotations Hardcoded []
Generic methods (type params stripped) ✅ Documented Test at line 1203
partial method (declaration-only) ✅ Documented Test at line 1186
partial method (declaration + impl pair) ⚠️ Undocumented Two entries produced, undocumented
params variadic Grammar version pinned in comment
out / ref / in parameters Covered
Optional parameters Covered
Attribute annotations ⚠️ Bug [return: Attr] and [method: Attr] targets bleed into method annotations
Compound visibility protected internal, private protected
record_struct_declaration Query, extractors, label mapping, tests all fixed
record_class_declaration ⚠️ Dead code No comment explaining it never matches a real node

Summary

After four rounds of feedback, the implementation is in excellent shape. The response rate on each review has been exemplary — all six actionable items from Round 1, all three from Round 2, and all five from Round 4 have been addressed.

One genuine bug remains:

  1. Fix: extractCSharpAnnotations incorrectly collects [return: Attr]-targeted attributes as method annotations — filter attribute_list nodes with an attribute_target_specifier child

Two low-effort additions would complete the coverage documentation:
2. Test: Static constructor behavior (name collision with instance ctor, isStatic: true)
3. Comment: partial method declaration+implementation pair produces two entries; record_class_declaration is dead code in export-detection.ts
· Branch

…r node type

tree-sitter-c-sharp 0.23.1 parses 'record struct' as record_declaration
(absorbs the 'struct' keyword as an unnamed child token). The non-existent
record_struct_declaration in queries caused TSQueryErrorNodeType, breaking
ALL C# file processing.

Remove from: tree-sitter-queries.ts, typeDeclarationNodes in both
extractors, CLASS_CONTAINER_TYPES, and CONTAINER_TYPE_TO_LABEL.
Record struct types are already handled via record_declaration.
…ic ctor test

- Add isPartial optional field to MethodInfo, MethodExtractionConfig,
  NodeProperties, and parse-worker propagation pipeline
- Detect partial modifier in C# config — marks both declaration-only
  and implemented partial methods
- Filter targeted attribute lists (e.g. [return: MarshalAs(...)]) in
  extractCSharpAnnotations — only untargeted attributes collected
- Add static constructor test (isStatic: true, same name as class)
- Add 3 partial method tests: declaration-only, with body, coexisting pair
- Document record_struct/record_class as defensive dead code in
  export-detection.ts (grammar absorbs keywords into record_declaration)
@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think while identifying C# languages gaps. Perfom this analysis on this pull request and verify those findings. Act as a senior compiler font-end engineer and expert in static analysis tools and review this change from that perpective and review the architectural fit. Also check out previous comments and reason with their requests.. You must additionally verify what version of csharp tree-sitter we use and verify when you claim a new language features must be covered.

@github-actions

github-actions Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 7m 10s —— View job


C# MethodExtractor — Round 6 Deep Review

Tasks

  • Gather context (PR body, all prior comments)
  • Verify tree-sitter-c-sharp version and validate feature claims against grammar
  • Read current state of all changed files
  • Verify Round 5 findings against current code
  • Deep-dive: new gaps, AST correctness, grammar edge cases, architecture
  • Write full review

Grammar Version Baseline

Installed version: "tree-sitter-c-sharp": "^0.23.1" (gitnexus/package.json:72). The ^ permits any 0.23.x patch release. For the claims below, the analysis is anchored to what the grammar actually emits for C# constructs — not what feature names suggest a node type should be called.


Round 5 Findings — Verified Status

Issue Status Evidence
extractCSharpAnnotations — targeted attributes bleed in ✅ Fixed csharp.ts:152–160: skips attribute_list nodes containing attribute_target_specifier; test at method-extraction.test.ts:872–886
Static constructor undocumented ✅ Fixed Test at method-extraction.test.ts:907–920
partial method pair produces two entries undocumented ✅ Fixed Documented test at lines 1244–1262 with explicit comment
Generic type params stripped undocumented ✅ Fixed Documented test at lines 1264–1277 with comment
record_class_declaration dead code comment ✅ Fixed export-detection.ts:76–78 now reads: "tree-sitter-c-sharp absorbs 'record struct' and 'record class' into record_declaration — these two node types are listed defensively but never emitted by the grammar in practice (verified against ^0.23.1)"
isPartial support ✅ Added csharp.ts:276–278; tested at lines 1213–1262

All six actionable items from Round 5 have been addressed. The response rate across all six rounds has been exemplary.


Grammar Verification: The record_struct_declaration Story

Commit e45931d ("remove record_struct_declaration — not a real tree-sitter node type") was based on empirical verification — the correct methodology. The comment now in export-detection.ts:76–78 correctly documents why: tree-sitter-c-sharp ^0.23.1 emits record_declaration for both record Foo {} and record struct Foo {} syntax variants.

The record struct tests at method-extraction.test.ts:1182–1211 still pass because tree-sitter-c-sharp produces a record_declaration node for record struct source code, and record_declaration IS in typeDeclarationNodes. The extractor correctly handles record struct types — it just doesn't need a separate record_struct_declaration case to do so.


Finding 1 — record struct Test Title Creates Future Confusion

method-extraction.test.ts:1183:

it('recognizes record_struct_declaration', () => {
  const tree = parseCSharp('public record struct Point { }');
  expect(extractor.isTypeDeclaration(tree.rootNode.child(0)!)).toBe(true);
});

The test title says record_struct_declaration but, per the verified grammar behaviour, parseCSharp('public record struct Point { }') produces a record_declaration node — not a record_struct_declaration node. The test passes because record_declaration is in typeDeclarationNodes. A future developer reading this test in isolation could reasonably conclude that record_struct_declaration is a real grammar node that the extractor explicitly handles, directly contradicting commit e45931d and the comment in export-detection.ts.

The test description and a comment inside it should clarify the actual grammar behaviour:

// tree-sitter-c-sharp ^0.23.1 emits record_declaration for both
// 'record Foo {}' and 'record struct Foo {}' — there is no separate
// record_struct_declaration node. This test verifies that record struct
// types are recognised via the record_declaration path.
it('recognizes record struct as record_declaration (tree-sitter-c-sharp ^0.23.1)', () => {

Fix this →


Finding 2 — Extension Method this Parameter Not Handled

In C#, the first parameter of a static extension method carries a this modifier:

public static bool IsNullOrEmpty(this string s) { }
public static T FirstOrDefault<T>(this IEnumerable<T> source) { }

In tree-sitter-c-sharp ^0.23.1, this is a named modifier child inside the parameter node — the same structural pattern as out, ref, and in. The current modifier loop at csharp.ts:106–113:

const modText = c.text.trim();
if (modText === 'out' || modText === 'ref' || modText === 'in') {
  typeName = typeName ? `${modText} ${typeName}` : modText;
  break;
}

this is not in the condition. The extracted parameter for this string s is { type: 'string', name: 's' } — the this marker is silently dropped.

Call-graph impact: Extension method calls are syntactically indistinguishable from instance method calls at the call site (s.IsNullOrEmpty() vs. obj.Method()). Resolving extension method calls requires knowing that a static method has a this-qualified first parameter. With this dropped, IsNullOrEmpty(this string s) looks identical in the graph to a regular IsNullOrEmpty(string s) static method — a CALLS edge from s.IsNullOrEmpty() cannot be correctly attributed.

The fix is a one-line addition to the modifier check, mirroring the existing in handling:

if (modText === 'out' || modText === 'ref' || modText === 'in' || modText === 'this') {

This produces { type: 'this string', name: 's' } — consistent with how out int and ref int are represented.

Fix this →


Finding 3 — ^0.23.1 Semver Range Does Not Pin the Exact Verified Version

The comment at csharp.ts:30 says "verified against ^0.23.1", but ^0.23.1 is a semver range, not a pinned version. In CI or a fresh npm install, any 0.23.x patch release may be installed. The params special-path at lines 59–93 depends on a specific grammar-level behaviour (bare unnamed params token outside parameter nodes). If tree-sitter-c-sharp 0.23.5 (hypothetically) wraps params in a standard parameter modifier, the path silently misfires.

The comment should state the specific resolved version rather than the semver range:

// In tree-sitter-c-sharp 0.23.1 (verified; see package-lock.json),
// the `params` variadic keyword is NOT wrapped inside a `parameter` node.

Alternatively, the package.json entry could be pinned to "0.23.1" (exact) instead of "^0.23.1" for the grammar dependency, given that grammar-specific workarounds are present in the code.


Architectural Observations (Persistent)

These have appeared in prior rounds and remain unaddressed. Both are low-severity:

STATIC_OWNER_TYPES JVM contamination (generic.ts:20): new Set(['companion_object', 'object_declaration']) is evaluated unconditionally for all language configs. C# container types never match, no runtime impact, but a comment would clarify intent for future language authors.

MethodVisibility = FieldVisibility (method-types.ts:8): The alias permits 'fileprivate' and 'open' (Swift) and 'package' (Java) as valid MethodVisibility values in the C# extractor's type signature. Not a runtime issue since the C# config never returns these values, but a per-language narrowing type would prevent the mistake by construction.


Final Coverage Table

Scenario Status Notes
Regular methods (all modifiers)
Constructors (instance)
Static constructors Documented test: same name, isStatic: true
Destructors (~Resource) ~ prefix in extractName
Operator overloads
Conversion operators
Expression-bodied => expr Same AST node
abstract / sealed / static / virtual / override / async All modeled and tested
isPartial Added in fb1e007; 3 tests
Primary constructors (C# 12 class/record) extractPrimaryConstructor
Primary constructor annotations Hardcoded []
Generic methods (type params stripped) ✅ Documented Test at line 1267
partial method declaration-only ✅ Documented isAbstract: false documented at line 1226
partial declaration + impl pair ✅ Documented Two-entry behaviour documented at line 1244
params variadic Grammar quirk documented
out / ref / in parameters Prefixed type strings
this parameter (extension methods) ❌ Missing this modifier silently dropped; type becomes bare type string
Targeted attribute lists [return: Attr] ✅ Fixed Filtered in extractCSharpAnnotations
Compound visibility protected internal, private protected
record struct via record_declaration ✅ Correct Grammar-verified; no separate node type in ^0.23.1
record struct test title accuracy ⚠️ Misleading Title says record_struct_declaration; grammar emits record_declaration
record_class_declaration dead code ✅ Documented Comment in export-detection.ts:76–78
params semver precision ⚠️ Minor "verified against ^0.23.1" should state exact resolved version

Summary

After six rounds of review, the implementation is in excellent shape. All five items from Round 5 have been addressed, and the record_struct_declaration story was correctly resolved with empirical grammar verification rather than assumption.

Actionable before merge:

  1. Fix (medium) — Handle this parameter modifier for extension methods (csharp.ts:110); add test
  2. Comment (trivial) — Update record struct test title at line 1183 to reflect actual grammar behaviour

Nice to have:
3. Pin exact tree-sitter-c-sharp version in the params comment block


· Branch

@xkonjin xkonjin left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C# Method Extraction Review

Overall this is solid work — comprehensive coverage of C# constructs with good test coverage. A few items to consider:

1. Language-specific pattern matching bug

extractParametersFromList() has a subtle issue with the params variadic handling. The current loop can skip valid parameters when it advances i incorrectly after finding a params argument:

if (nameText) {
  params.push({...});
}
i++;  // This increments but the outer while also increments
continue;

Consider restructuring to avoid double-incrementing i, or use a for-loop with manual control.

2. Missing validation for empty param names

In extractCSharpParameters(), if nameNode exists but has empty text, you still push a parameter. Consider validating nameNode.text.trim() is non-empty.

3. Test gap: null/undefined handling

The primary constructor tests don't cover edge cases where parameter_list exists but contains no valid parameters, or where parameters have malformed syntax. Worth adding a test for:

public class Bad(int, string);  // Invalid C# but parser might produce nodes

4. Visibility precedence in primary constructors

In extractPrimaryConstructor(), the compound visibility logic is duplicated from extractVisibility(). Consider extracting this to a shared helper to avoid drift.

5. Return type for conversion operators

The test extracts implicit conversion operator asserts conv!.parameters but doesn't verify returnType. Consider adding that assertion.

Minor nits

  • The comment about tree-sitter-c-sharp grammar version in export-detection.ts is helpful — consider adding similar version-gating comments to csharp.ts for the primary constructor handling
  • Several tests use !. non-null assertions after expect().toBeDefined() — fine for tests, but the production code should have runtime guards

Security/Reliability

No concerns. The code doesn't execute user input, only parses it. The AST traversal is bounded by node count.


Verdict: Approve with minor suggestions. The architecture is clean and the test coverage is thorough.

… fixes

- Handle this modifier on extension method parameters (type prefixed
  as 'this string', consistent with out/ref/in handling)
- Deduplicate visibility logic in extractPrimaryConstructor — reuse
  csharpMethodConfig.extractVisibility instead of inline compound check
- Fix record struct test title to reflect actual grammar behavior
- Add conversion operator returnType assertion
- Add extension method this parameter test
@magyargergo

Copy link
Copy Markdown
Collaborator Author

@claude ultra-think and review this pull request as a senior context engineer, senior compiler front-end engineer, and expert in static analysis tools.

Perform a deep, evidence-based review of the implementation, not just the PR description. Inspect the full diff, surrounding files, linked issue/context, and all prior review comments, then reason carefully about whether this change actually satisfies the requested behavior and fits the existing architecture.

Review priorities:

  1. Validate end-to-end correctness, not just local code quality.
  2. Review the architectural fit with the current GitNexus web ingestion flow and URL parsing model.
  3. Check whether the implementation is robust, maintainable, and aligned with the repository’s design direction.
  4. Carefully read previous review comments and verify whether their requests were truly addressed, partially addressed, or only superficially addressed.

You must investigate specifically:

  • whether branch-specific GitHub URLs are parsed correctly
  • whether non-default branch support actually works end to end
  • whether default-branch behavior remains unchanged
  • whether the UI, validation, and error handling are consistent with the new feature
  • whether the implementation introduces fragile parsing or hidden assumptions
  • whether test coverage is sufficient for the new behavior

Be especially strict about URL parsing edge cases:

  • /tree/
  • branch names containing slashes
  • trailing slashes
  • .git suffixes
  • repo URLs with extra path segments
  • invalid but near-valid GitHub URLs
  • encoded branch names
  • branch names that could be misparsed as path segments

Also verify whether the code is overfitting to one GitHub URL format instead of implementing a durable parsing strategy.

In addition:

  • check existing tests and identify important missing unit, integration, or e2e coverage
  • inspect related parsing and ingestion code outside the direct diff where needed
  • call out any mismatch between the feature intent and what was actually implemented
  • review the wording of placeholders, helper text, and error messages for product correctness and discoverability

Important:

  • Do not give generic praise
  • Do not invent problems
  • Only raise findings you can support from the code
  • Distinguish clearly between confirmed defects and softer architectural suggestions
  • Prioritize the highest-signal findings first

You must additionally verify what version of the C# tree-sitter grammar this repository currently uses, and if you mention any missing C# language feature coverage you must verify that claim against the actual grammar version in use before raising it. Do not speculate about unsupported C# constructs without checking the exact tree-sitter-c-sharp version and the repository’s current C# ingestion/parsing pipeline.

Output format:

Verdict

State whether this is:

  • mergeable as is
  • mergeable with minor follow-ups
  • or needs changes

Confirmed findings

For each finding use:

  • Severity: blocker | major | minor | nit
  • File:
  • Problem:
  • Why it matters:
  • Recommended fix:

Architectural assessment

Briefly assess whether the solution fits the existing design and whether there is a cleaner or more durable approach.

Previous comment verification

List prior reviewer requests and say whether each one is:

  • fully addressed
  • partially addressed
  • not addressed

Missing tests

List only the highest-value missing tests.

Final review comment

Write one concise, paste-ready GitHub review comment in a strong senior-engineer tone that focuses on correctness, architecture, and follow-up actions.

@github-actions

github-actions Bot commented Mar 30, 2026

Copy link
Copy Markdown
Contributor

Claude finished @magyargergo's task in 5m 5s —— View job


C# MethodExtractor — Round 7 Deep Review

Tasks

  • Gather PR context and prior review history
  • Read current state of all changed files
  • Verify tree-sitter-c-sharp grammar version
  • Verify Round 6 findings against current code
  • Deep-dive: new gaps, grammar edge cases, architecture
  • Verify xkonjin reviewer requests
  • Write full review

Grammar Version Baseline

tree-sitter-c-sharp: ^0.23.1 (package.json:72). All findings below are anchored to what this grammar version actually emits — not speculative.


Round 6 Findings — Verified Status

Issue Status Evidence
record struct test title misleading ✅ Fixed method-extraction.test.ts:1203: title now reads "recognizes record struct via record_declaration" with inline comment
this parameter for extension methods silently dropped ✅ Fixed csharp.ts:110 adds 'this' to modifier check; test at lines 1041–1055
params semver comment imprecise ⚠️ Partially Comment still reads "verified against ^0.23.1" — range, not exact resolved version

All three Round 6 actionable items are addressed or acknowledged. The semver nit is cosmetic.


Verdict

Mergeable as is. All confirmed bugs across six rounds have been fixed. No new blockers or majors found below. The remaining findings are documentation nits or long-standing architectural observations that predate this PR.


Confirmed Findings

1. Severity: nit

  • File: gitnexus/src/core/ingestion/method-extractors/configs/csharp.ts:314
  • Problem: extractPrimaryConstructor sets line: ownerNode.startPosition.row + 1 — the line of the class/record declaration, not the parameter list. For class Point(int x, int y) { ... }, the line number assigned to the synthetic constructor entry is the same as the class declaration line.
  • Why it matters: The methodInfoCache key in parse-worker.ts is name:line. A primary constructor and an explicit constructor placed on the same line in the same file (contrived but possible) would collide in the cache.
  • Recommended fix: Use paramList.startPosition.row + 1 instead. paramList is already in scope at csharp.ts:300 when the parameter list is found.

Fix this →

2. Severity: nit

  • File: gitnexus/src/core/ingestion/method-extractors/configs/csharp.ts:127
  • Problem: name: nameNode.text — no validation that nameNode.text.trim() is non-empty. If tree-sitter produces a zero-length identifier node (possible under error recovery for malformed input), an empty-string name would be pushed into the params array and propagated to the graph.
  • Why it matters: Consumers that key on parameter name (e.g., methodInfoCache lookups, signature display) would silently receive an empty name. Low probability with real C# code; relevant when indexing repos with syntax errors.
  • Recommended fix: Add if (!nameNode.text.trim()) { i++; continue; } before the push, or change to name: nameNode.text || '<anonymous>' as a sentinel.

xkonjin Reviewer — Verification

xkonjin Finding Verdict Notes
Double-increment bug in extractParametersFromList Not a bug Reviewer identified the i++ at line 92 but missed the continue at line 93 which prevents the outer i++ at line 135 from executing. The loop advances correctly by exactly one position past the name identifier.
Missing validation for empty param names Valid nit nameNode.text is not validated. See Finding 2 above.
Test gap: malformed primary constructor params Low priority public class Bad(int, string) is invalid C# syntax — tree-sitter produces ERROR nodes. extractParametersFromList handles this gracefully by skipping non-parameter nodes; result is 0 params with no error. The behavior is correct even without a dedicated test.
Visibility logic duplicated in extractPrimaryConstructor Not duplicated extractPrimaryConstructor at line 308 calls csharpMethodConfig.extractVisibility(ownerNode) — it delegates to the config's own method, not a copy-paste. Either xkonjin reviewed an older snapshot or the fix landed in commit de4615c.
Return type for conversion operators not tested Already present method-extraction.test.ts:1019 asserts conv!.returnType === 'double' for the implicit conversion operator. Possibly present before xkonjin's review.

Persistent Architectural Observations (Pre-existing, No Action Required for This PR)

MethodVisibility = FieldVisibility (method-types.ts:8): The alias allows 'fileprivate' (Swift) and 'open' (Swift) as structurally valid MethodVisibility values in the C# extractor's type signature. Not a runtime issue — the C# config never produces these values — but a C#-specific narrowing type would make the type system enforce this by construction. This is a pre-existing design choice that affects all language configs, not this PR's scope.

extractVisibility logic copy-shared between field-extractors/configs/csharp.ts:70–76 and method-extractors/configs/csharp.ts:235–241 — both are identical compound-visibility checks. The underlying helpers (collectModifierTexts, findVisibility) were correctly unified in helpers.ts, but the two-line compound check block is still copy-pasted. This is acceptable given the configs are separate objects; a shared extractCSharpVisibility(node) helper would be the cleaner long-term solution but is out of scope for this PR.


Missing Tests (Highest Value)

Scenario Priority Notes
Primary constructor line points to parameter list, not class declaration High (once nit 1 is fixed) Verify ctor.line === paramList.startPosition.row + 1
Extension method with multiple parameters including this Low Current test only covers single-param case
params as sole parameter (no preceding regular params) Low Exercises the bare-token path without a leading regular param

Final Review Comment

Round 7 verdict: mergeable as is. Six rounds of review produced an exemplary response rate — every confirmed bug was fixed before the next round. The current state has no blockers or majors.

Two items to close before or after merge:

  1. Nit (csharp.ts:314): extractPrimaryConstructor assigns line: ownerNode.startPosition.row + 1 (the class keyword line) instead of the parameter list line. In a file where a primary constructor and explicit constructor share the same line, the methodInfoCache key name:line would collide. Fix: use paramList.startPosition.row + 1paramList is already in scope.

  2. Nit (csharp.ts:127): name: nameNode.text is not validated for empty string. Under tree-sitter error recovery, a zero-length identifier node would produce an empty-name parameter in the graph. Low probability in practice; one-line guard would eliminate it.

xkonjin's double-increment concern is not a bug — the continue at line 93 prevents the outer i++ from executing, and the loop advances correctly. The other four xkonjin findings are either resolved in the current code, already tested, or correctly low-priority.

Architecture is clean: the JVM pattern was followed faithfully, collectModifierTexts is correctly shared via helpers.ts, and extractPrimaryConstructor's visibility correctly delegates to csharpMethodConfig.extractVisibility. No JVM-specific leakage observed beyond the pre-existing STATIC_OWNER_TYPES constant (which is already commented).


· Branch

…e guard

- Use paramList.startPosition instead of ownerNode.startPosition for
  primary constructor line number (avoids methodInfoCache key collision)
- Guard against empty param names from tree-sitter error recovery nodes
@magyargergo magyargergo merged commit c72890d into main Mar 30, 2026
13 checks passed
@magyargergo magyargergo deleted the feat/csharp-method-extractor branch March 30, 2026 07:41
icodebuster pushed a commit to icodebuster/GitNexus that referenced this pull request Mar 31, 2026
* main: (114 commits)
  feat(csharp): C# MethodExtractor config (abhigyanpatwari#582)
  docs: add gitnexus-shared build step before gitnexus-web (abhigyanpatwari#585)
  chore: add enterprise offering section to README, ignore local_docs/ (abhigyanpatwari#579)
  fix(eval): exclude litellm 1.82.7 and 1.82.8 due to compatibility issues (abhigyanpatwari#580)
  feat(java,kotlin): MethodExtractor abstraction with per-language configs (abhigyanpatwari#576)
  feat: added skip-agents-md cli flag (abhigyanpatwari#517)
  feat(wiki): Azure OpenAI support for wiki command (abhigyanpatwari#562)
  refactor: reduce explicit any types (abhigyanpatwari#566)
  feat(java): method references, worker overload disambiguation, interface dispatch (abhigyanpatwari#540)
  feat: configure eslint with unused import removal (abhigyanpatwari#564)
  feat: configure prettier with pre-commit hook (abhigyanpatwari#563)
  feat: unify web and cli ingestion pipeline (abhigyanpatwari#536)
  fix/opencode mcp gitnexus timeout (abhigyanpatwari#363)
  chore: bump version to 1.4.10, update CHANGELOG
  fix: resolve tree-sitter peer dependency conflicts (abhigyanpatwari#538)
  chore: bump version to 1.4.9, add CHANGELOG.md
  refactor: Phase 8 & 9 — Field Types and Return-Type Binding (abhigyanpatwari#494)
  feat: add COBOL language support with regex extraction pipeline (abhigyanpatwari#498)
  fix: close remaining Dart language support gaps (abhigyanpatwari#524)
  refactor: split global BUILT_IN_NAMES into per-language provider fields (abhigyanpatwari#523)
  ...

# Conflicts:
#	gitnexus/src/core/wiki/llm-client.ts
@abhigyanpatwari abhigyanpatwari mentioned this pull request Apr 1, 2026
3 tasks
motolese pushed a commit to motolese/datamoto-gitnexus that referenced this pull request Apr 23, 2026
* feat(csharp): add C# MethodExtractor config (abhigyanpatwari#573)

Add C# method extraction config mirroring the JVM pattern from PR abhigyanpatwari#576.
Wire csharpMethodConfig into the C# language provider and add 18 tests
covering classes, interfaces, abstract classes, structs, records,
constructors, params/out/ref/optional parameters, sealed methods,
attributes, and visibility modifiers.

* fix(csharp): add destructor, operator, conversion operator, and in-param support

- Add destructor_declaration, operator_declaration, and
  conversion_operator_declaration to methodNodeTypes
- Custom extractName for operators (e.g., "operator +", "implicit operator double")
- Fix extractReturnType for operator declarations (use type field, not returns)
- Add in modifier to parameter extraction (alongside out/ref)
- Add 4 new tests: destructor, operator+, implicit conversion, in parameter

* fix(csharp): add ref param test and document compound visibility limitation

- Add test for ref parameter modifier (was only testing out)
- Document that protected internal / private protected resolve to first modifier

* feat(csharp): support compound visibilities (protected internal, private protected)

- Add 'protected internal' and 'private protected' to FieldVisibility union
- Detect compound modifiers in both C# method and field extractors via
  collectModifierTexts helper scanning adjacent modifier nodes
- Add 2 tests for compound visibility detection

* feat(csharp): primary constructors, virtual/override/async, primary fields

Address all known limitations from review:

- Primary constructor support (C# 12): add extractPrimaryConstructor to
  MethodExtractionConfig and extractPrimaryFields to FieldExtractionConfig.
  Record params become public readonly properties; class params become
  private captured fields.
- Add isVirtual, isOverride, isAsync optional fields to MethodInfo,
  MethodExtractionConfig, NodeProperties, and parse-worker propagation.
- Detect virtual/override/async modifiers in C# method config.
- Move collectModifierTexts to shared helpers.ts (deduplicate).
- Fix destructor name to ~ClassName (disambiguates from constructor).
- Add expression-bodied method test.
- 118 tests total across method + field extraction suites, all passing.

* fix(csharp): review round 2 — annotations, record_struct, grammar pin

- Fix primary constructor annotations: use [] instead of extracting
  class-level attributes (C# has no syntax for ctor-specific attributes)
- Add record_struct_declaration to typeDeclarationNodes in both method
  and field extractors, CLASS_CONTAINER_TYPES, and isRecord visibility check
- Pin tree-sitter-c-sharp version (^0.23.1) in params comment

* fix(csharp): complete record_struct query + label mapping, sealed override test

- Add record_struct_declaration capture patterns to tree-sitter-queries.ts
  (type definition + primary constructor)
- Add record_struct_declaration → 'Struct' in CONTAINER_TYPE_TO_LABEL
- Assert isOverride: true alongside isFinal in sealed override test

* fix(csharp): record_struct label mismatch, add record struct + documented limitation tests

- Fix record_struct_declaration query tag: @Definition.struct (not @definition.record)
  to match CONTAINER_TYPE_TO_LABEL and prevent broken HAS_METHOD edges
- Add 3 record struct tests: isTypeDeclaration, method extraction, primary constructor
- Add documented limitation tests: partial method (isAbstract: false), generic type
  parameter stripping (name excludes <T>)

* fix(csharp): remove record_struct_declaration — not a real tree-sitter node type

tree-sitter-c-sharp 0.23.1 parses 'record struct' as record_declaration
(absorbs the 'struct' keyword as an unnamed child token). The non-existent
record_struct_declaration in queries caused TSQueryErrorNodeType, breaking
ALL C# file processing.

Remove from: tree-sitter-queries.ts, typeDeclarationNodes in both
extractors, CLASS_CONTAINER_TYPES, and CONTAINER_TYPE_TO_LABEL.
Record struct types are already handled via record_declaration.

* feat(csharp): add isPartial support, filter targeted attributes, static ctor test

- Add isPartial optional field to MethodInfo, MethodExtractionConfig,
  NodeProperties, and parse-worker propagation pipeline
- Detect partial modifier in C# config — marks both declaration-only
  and implemented partial methods
- Filter targeted attribute lists (e.g. [return: MarshalAs(...)]) in
  extractCSharpAnnotations — only untargeted attributes collected
- Add static constructor test (isStatic: true, same name as class)
- Add 3 partial method tests: declaration-only, with body, coexisting pair
- Document record_struct/record_class as defensive dead code in
  export-detection.ts (grammar absorbs keywords into record_declaration)

* fix(csharp): this param for extension methods, dedup visibility, test fixes

- Handle this modifier on extension method parameters (type prefixed
  as 'this string', consistent with out/ref/in handling)
- Deduplicate visibility logic in extractPrimaryConstructor — reuse
  csharpMethodConfig.extractVisibility instead of inline compound check
- Fix record struct test title to reflect actual grammar behavior
- Add conversion operator returnType assertion
- Add extension method this parameter test

* fix(csharp): primary constructor line points to param list, empty name guard

- Use paramList.startPosition instead of ownerNode.startPosition for
  primary constructor line number (avoids methodInfoCache key collision)
- Guard against empty param names from tree-sitter error recovery nodes
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat: C# MethodExtractor config

2 participants