Skip to content

fix(csharp-extractor): capture aliased usings and qualified new expressions in the call graph#441

Open
tirth8205 wants to merge 1 commit into
Egonex-AI:mainfrom
tirth8205:fix/csharp-extractor-edge-cases
Open

fix(csharp-extractor): capture aliased usings and qualified new expressions in the call graph#441
tirth8205 wants to merge 1 commit into
Egonex-AI:mainfrom
tirth8205:fix/csharp-extractor-edge-cases

Conversation

@tirth8205

Copy link
Copy Markdown
Contributor

Problem

Two C# extractor edge cases silently dropped real declarations:

  1. Aliased using directives with a simple-identifier target. extractUsingSource handled the alias form using Alias = Some.Namespace; by looking only for a qualified_name child after the =. When the alias target is a single, undotted name (e.g. using Alias = System;), tree-sitter emits the target as a plain identifier, not a qualified_name. The alias branch returned null, so the whole using directive was dropped from imports. Falling back to the existing findChild(node, "identifier") would also be wrong, because the first identifier child is the alias name Alias, not the target System.

  2. new of a qualified type. The object_creation_expression handler only looked for a direct identifier or generic_name child to name the constructed type. For a qualified type like new System.Text.StringBuilder(), tree-sitter places the type as a qualified_name child, so the object creation was dropped from the call graph entirely. (new Bar() and new List<int>() were captured correctly.)

Fix

  1. In the alias branch of extractUsingSource, when no qualified_name is found, fall back to the identifier that appears after the = token (tracking a seenEquals flag), so the target — not the alias name — is read.

  2. Add qualified_name to the type-node fallback chain in the object_creation_expression handler:

    const typeNode =
      findChild(node, "identifier") ??
      findChild(node, "generic_name") ??
      findChild(node, "qualified_name");

Testing

  • Added two tests to csharp-extractor.test.ts:
    • using Alias = System; now yields imports == [{ source: "System", specifiers: ["System"], lineNumber: 1 }].
    • new System.Text.StringBuilder() inside method M now produces call-graph entry { caller: "M", callee: "new System.Text.StringBuilder", lineNumber: 1 }.
  • Both tests fail before the fix (imports []; call graph []) and pass after.
  • The full core package vitest suite is green (694 tests, csharp-extractor up to 29). Lint (eslint) and typecheck (tsc --noEmit) on the changed files both exit 0.

🤖 Generated with Claude Code

…ressions in the call graph

Two C# extractor edge cases dropped real declarations:

1. Aliased using directives whose target is a single undotted name
   (`using Alias = System;`) emit the target as a plain `identifier`, not a
   `qualified_name`. The alias branch only looked for `qualified_name` and
   returned null, dropping the whole import. Now it falls back to the
   identifier that follows the `=` token (not the first identifier, which is
   the alias name itself).

2. `object_creation_expression` for a qualified type
   (`new System.Text.StringBuilder()`) places the type as a `qualified_name`
   child, which neither the `identifier` nor `generic_name` lookup matched, so
   the object creation was dropped from the call graph. Added `qualified_name`
   to the fallback chain.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@thejesh23 thejesh23 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

1. Aliased using drops the alias name entirely.
For using Alias = System; the import is recorded as { source: "System", specifiers: ["System"] }, so downstream consumers can't tell the symbol bound in scope is Alias. The alias is the only name actually resolvable in the file; emitting specifiers: ["Alias"] (or both) would match how import 'x' as y; should surface in PR #435's Dart extractor too.

2. Qualified generic new is untested and likely partially wrong.
new System.Collections.Generic.List<int>() isn't covered by either the existing new List<int>() test or the new qualified-type test. Depending on how tree-sitter-c-sharp shapes this (qualified_name wrapping a generic_name, vs. a distinct node), the callee text may include the type arguments or silently fall through the fallback chain. Worth one explicit case.

3. global using and file-scoped alias forms are unhandled.
The extractUsing switch only matches using_directive; C# 10's global using Alias = System; parses as global_using_directive (or as a using_directive under a global modifier child, depending on grammar version), so the new alias fix doesn't reach it. A one-line note or follow-up issue would be useful.

Nit: the manual seenEquals scan duplicates the work findChild(node, "=") already did; a single pass that records both presence-of-= and the post-= identifier would be tidier.

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.

2 participants