From cb8a2680312cf4b72d94db72304f074075cce3c0 Mon Sep 17 00:00:00 2001 From: Gergo Magyar Date: Tue, 21 Apr 2026 19:06:03 +0100 Subject: [PATCH] =?UTF-8?q?feat(csharp-scope):=20parity=20Unit=203a=20?= =?UTF-8?q?=E2=80=94=20cross-namespace=20`using`=20binding?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes 2 parity failures (27 → 25). Extends the namespace-siblings pass to resolve `using X;` directives against known namespace buckets: for each `using` that targets a namespace declared somewhere in the workspace, inject that namespace's classes into the importer's module scope with origin='namespace'. This is the scope-resolution analog of legacy's csproj-driven directory↔namespace mapping. Without it, `new User()` in `Services/UserService.cs` (namespace MyApp.Services) can't see the User class in `Models/User.cs` (namespace MyApp.Models) even with `using MyApp.Models;` — the scope-resolver layer doesn't have csproj metadata to translate the dotted namespace path into a directory lookup. Legacy 175/175 green; 25 parity failures remain. --- .../languages/csharp/namespace-siblings.ts | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts b/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts index 4795fb878..376c2fcf2 100644 --- a/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts +++ b/gitnexus/src/core/ingestion/languages/csharp/namespace-siblings.ts @@ -126,6 +126,41 @@ export function populateCsharpNamespaceSiblings( // pattern (see `propagateImportedReturnTypes` which does the same // for module-scope typeBindings). const finalized = indexes.bindings as Map>; + + // Cross-namespace imports: for each file's `using X;` directive, + // if `X` matches a known namespace bucket, inject that bucket's + // classes into the importer's module scope. This is what makes + // `new User()` in `namespace App;` resolve to `User` declared in + // a sibling file with `namespace Models;` when the importer says + // `using Models;`. Legacy uses csproj directory↔namespace mapping; + // the scope-resolver layer uses the declared namespace directly. + for (const parsed of parsedFiles) { + const moduleScope = parsed.scopes.find((s) => s.kind === 'Module'); + if (moduleScope === undefined) continue; + for (const imp of parsed.parsedImports) { + if (imp.kind !== 'namespace') continue; + const targetNs = imp.targetRaw; + if (targetNs === null || targetNs === '') continue; + const bucket = buckets.get(targetNs); + if (bucket === undefined) continue; + for (const def of bucket.classDefs) { + if (def.filePath === parsed.filePath) continue; + const q = def.qualifiedName ?? ''; + const simpleName = q.includes('.') ? q.slice(q.lastIndexOf('.') + 1) : q; + if (simpleName === '') continue; + let scopeBindings = finalized.get(moduleScope.id); + if (scopeBindings === undefined) { + scopeBindings = new Map(); + finalized.set(moduleScope.id, scopeBindings); + } + const existing = scopeBindings.get(simpleName) ?? []; + if (existing.some((b) => b.def.nodeId === def.nodeId)) continue; + existing.push({ def, origin: 'namespace' }); + scopeBindings.set(simpleName, existing); + } + } + } + for (const [, bucket] of buckets) { // De-dup by (nodeId, filePath) across multiple declarations (e.g. // partial classes declaring the same name in two files — we take