Преглед изворни кода

feat(impact): C# namespace extraction (qualify type names)

C# class qns were bare simple names, so same-named types in different namespaces
collapsed (a domain entity `ApplicationCore.Entities.CatalogBrand` and a DTO
`BlazorShared.Models.CatalogBrand` were indistinguishable). Added `namespace`
capture via the shared packageTypes mechanism (like Java/PHP): both block
`namespace Foo { … }` and file-scoped `namespace Foo;` forms, so types get
namespace-qualified ids (`Foo::Type`).

Safe — name-based resolution (C#'s default) is unaffected (the `name` is
unchanged; only `qualifiedName` gains the namespace). cs-mediatr 90.7% → 94.6%
(namespaces disambiguate collisions), cs-polly neutral (80.7%), ASP.NET
81.2% → 81.9%. 250 namespace nodes on eShopOnWeb. Full suite 1177; 1 test.

This is the prerequisite for Razor `@using` disambiguation (next): now that the
entity and DTO have distinct qns, a `.razor`'s `@using BlazorShared.Models` can
pick the DTO.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Colby McHenry пре 2 недеља
родитељ
комит
dc7d033106
3 измењених фајлова са 28 додато и 0 уклоњено
  1. 1 0
      CHANGELOG.md
  2. 15 0
      __tests__/extraction.test.ts
  3. 12 0
      src/extraction/languages/csharp.ts

+ 1 - 0
CHANGELOG.md

@@ -11,6 +11,7 @@ and adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
 ### New Features
 
+- C# types are now tracked by their namespace-qualified name. Same-named types in different namespaces — a domain entity and a DTO both called `CatalogBrand`, say — are told apart instead of collapsing into one arbitrary match, so a reference resolves to the right one and impact no longer conflates them. (C#)
 - ASP.NET Razor (`.cshtml`) and Blazor (`.razor`) markup are now parsed for code relationships. A `@model` / `@inherits` / `@inject` directive links the view to the C# view-model, base type, or service it names; a Blazor `<MyComponent/>` tag (plus `@typeof(...)` and generic `TItem="..."` arguments) links to the component class; and the C# inside `@code { }` / `@functions { }` / `@{ }` blocks is analyzed too, so services and types used in component logic are linked. A view-model, component, or service referenced only from markup is no longer reported as having no dependents, and editing it surfaces the views that use it. (ASP.NET, Blazor)
 - `codegraph status --json` now also reports the running CLI `version`, the index directory (`indexPath`), and a `lastIndexed` timestamp (ISO-8601, or null when nothing's indexed yet), so CI and scripts can pin the CLI version and check index freshness from a single command. A matching `CodeGraph.getLastIndexedAt()` library method exposes the same freshness check without shelling out. Thanks @12122J and @eddieran. (#329)
 

+ 15 - 0
__tests__/extraction.test.ts

@@ -4006,6 +4006,21 @@ describe('Razor / Blazor markup extraction', () => {
     expect(htmlNodes.length, 'no node for HTML elements').toBe(0);
   });
 
+  it('C# namespaces qualify type names so same-named types are distinct', async () => {
+    fs.writeFileSync(path.join(tempDir, 'entity.cs'), `namespace App.Entities { public class CatalogBrand { } }`);
+    fs.writeFileSync(path.join(tempDir, 'dto.cs'), `namespace App.Models { public class CatalogBrand { } }`);
+
+    cg = CodeGraph.initSync(tempDir);
+    await cg.indexAll();
+
+    const brands = cg.getNodesByKind('class').filter((n) => n.name === 'CatalogBrand');
+    expect(brands.length, 'both CatalogBrand classes indexed').toBe(2);
+    const qns = brands.map((b) => b.qualifiedName).sort();
+    expect(qns[0]).not.toBe(qns[1]); // distinct qualified names (namespace-scoped)
+    expect(qns.some((q) => q.includes('Entities') && q.endsWith('CatalogBrand'))).toBe(true);
+    expect(qns.some((q) => q.includes('Models') && q.endsWith('CatalogBrand'))).toBe(true);
+  });
+
   it('delegates Blazor @code block C# to cover types used in component logic', async () => {
     fs.writeFileSync(
       path.join(tempDir, 'CatalogService.cs'),

+ 12 - 0
src/extraction/languages/csharp.ts

@@ -15,6 +15,18 @@ export const csharpExtractor: LanguageExtractor = {
   enumTypes: ['enum_declaration'],
   enumMemberTypes: ['enum_member_declaration'],
   typeAliasTypes: [],
+  // Namespaces qualify type names so same-named types in different namespaces are
+  // distinguishable (e.g. `ApplicationCore.Entities.CatalogBrand` vs
+  // `BlazorShared.Models.CatalogBrand`). Both block (`namespace Foo { … }`, which
+  // nests its types) and file-scoped (`namespace Foo;`) forms — extractFilePackage
+  // pushes the namespace onto the scope so nested/top-level types pick it up.
+  packageTypes: ['namespace_declaration', 'file_scoped_namespace_declaration'],
+  extractPackage: (node: SyntaxNode, source: string) => {
+    const name =
+      node.childForFieldName('name') ??
+      node.namedChildren.find((c: SyntaxNode) => c.type === 'qualified_name' || c.type === 'identifier');
+    return name ? getNodeText(name, source) : null;
+  },
   importTypes: ['using_directive'],
   callTypes: ['invocation_expression'],
   variableTypes: ['local_declaration_statement'],