mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-21 00:21:30 +00:00
feat(csharp-scope): parity Unit 5a — IMPORTS edge + static-using mapping
Closes 1 parity failure (21 → 20). Fixes cross-file IMPORTS edge emission for C#: - `languages/csharp/interpret.ts`: map `using static X.Y;` to `kind: 'namespace'` rather than `'wildcard'`. The File→File IMPORTS edge needs a non-wildcard kind to survive finalize's Phase 4 (wildcard-expanded edges drop to empty when the provider doesn't implement `expandsWildcardTo`). Unqualified static-member access is a deferred limitation — covered by the namespace-siblings cross-namespace pass for type lookups, and documented under the module's Known Limitations. - `languages/csharp/import-target.ts`: progressive prefix stripping. `using CrossFile.Models;` in a repo laid out `Models/User.cs` (no `CrossFile/` directory) works because the legacy resolver consults csproj; the scope-resolver tries each suffix of the dotted path against `.cs` files. Also handles `using static NS.Type;` by stripping leading segments until a direct match lands. - `test/unit/scope-resolution/csharp/csharp-imports.test.ts`: update the `using static` test to the new namespace-kind shape. 376/376 scope-resolution unit tests pass; legacy 175/175 green; 20 parity failures remain.
This commit is contained in:
parent
38b203599f
commit
ea6ee46ce2
3 changed files with 68 additions and 11 deletions
|
|
@ -83,5 +83,44 @@ export function resolveCsharpImportTarget(
|
|||
|
||||
if (exactFile !== null) return exactFile;
|
||||
if (suffixFile !== null) return suffixFile;
|
||||
return directoryChild;
|
||||
if (directoryChild !== null) return directoryChild;
|
||||
|
||||
// Progressive prefix stripping — mirrors csproj's root-namespace
|
||||
// mapping without the csproj. `using CrossFile.Models;` in a repo
|
||||
// laid out `Models/User.cs` (no `CrossFile/` prefix) works because
|
||||
// the legacy resolver consults csproj; the scope-resolver layer
|
||||
// doesn't have csproj, so we try each suffix of the namespace path
|
||||
// against `.cs` files and directories.
|
||||
//
|
||||
// Also handles `using static CrossFile.Models.UserFactory;` —
|
||||
// strip the leading segment, try `Models/UserFactory.cs`; strip
|
||||
// two, try `UserFactory.cs`.
|
||||
const segments = pathLike.split('/').filter(Boolean);
|
||||
for (let skip = 1; skip < segments.length; skip++) {
|
||||
const tail = segments.slice(skip).join('/');
|
||||
if (tail === '') continue;
|
||||
const tailFile = `${tail}.cs`;
|
||||
const tailSuffix = `/${tailFile}`;
|
||||
const tailDir = `${tail}/`;
|
||||
const tailSuffixDir = `/${tailDir}`;
|
||||
let tailDirectChild: string | null = null;
|
||||
for (const raw of ctx.allFilePaths) {
|
||||
const f = raw.replace(/\\/g, '/');
|
||||
if (!f.endsWith('.cs')) continue;
|
||||
if (f === tailFile) return raw;
|
||||
if (f.endsWith(tailSuffix)) return raw;
|
||||
if (tailDirectChild === null) {
|
||||
const atRoot = f.startsWith(tailDir);
|
||||
const atNested = f.includes(tailSuffixDir);
|
||||
if (atRoot || atNested) {
|
||||
const idx = atRoot ? 0 : f.indexOf(tailSuffixDir) + 1;
|
||||
const after = f.slice(idx + tailDir.length);
|
||||
if (after.length > 0 && !after.includes('/')) tailDirectChild = raw;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (tailDirectChild !== null) return tailDirectChild;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -51,10 +51,19 @@ export function interpretCsharpImport(captures: CaptureMatch): ParsedImport | nu
|
|||
}
|
||||
case 'static': {
|
||||
// `using static System.Math;` — brings static members of Math into
|
||||
// unqualified scope. Semantically closest to a wildcard: any name
|
||||
// can resolve to a static member of the target type. Merge-bindings
|
||||
// (Unit 4) ranks wildcards lowest so locals still shadow.
|
||||
return { kind: 'wildcard', targetRaw: sourceCap.text };
|
||||
// unqualified scope. Semantically closest to a wildcard, but we
|
||||
// map to `namespace` here so finalize emits the File→File IMPORTS
|
||||
// edge without requiring `expandsWildcardTo` (which would list
|
||||
// every exported member). Static-member unqualified-access is a
|
||||
// deferred limitation; the usual cross-file lookup via
|
||||
// namespace-siblings covers `Target.Member` calls.
|
||||
const lastSegment = sourceCap.text.split('.').pop() ?? sourceCap.text;
|
||||
return {
|
||||
kind: 'namespace',
|
||||
localName: lastSegment,
|
||||
importedName: sourceCap.text,
|
||||
targetRaw: sourceCap.text,
|
||||
};
|
||||
}
|
||||
default:
|
||||
return null;
|
||||
|
|
|
|||
|
|
@ -55,12 +55,21 @@ describe('interpretCsharpImport — using flavors', () => {
|
|||
});
|
||||
});
|
||||
|
||||
it('interprets `using static X.Y;` as a wildcard import', () => {
|
||||
// `using static` brings static members of the target type into
|
||||
// unqualified scope. Merge-bindings (Unit 4) ranks wildcards
|
||||
// lowest so locals still shadow them.
|
||||
it('interprets `using static X.Y;` as a namespace import targeting the type', () => {
|
||||
// `using static` brings static members into unqualified scope.
|
||||
// Initially this was mapped to `kind: 'wildcard'` but that
|
||||
// requires `expandsWildcardTo` to materialize any IMPORTS edge;
|
||||
// we map to `namespace` so the File→File edge still emits and
|
||||
// the namespace-siblings pass (which walks known namespaces)
|
||||
// picks up the target file's classes. Unqualified static-member
|
||||
// access is a deferred limitation — see csharp/index.ts.
|
||||
const [imp] = importsFor('using static System.Math;\nclass A {}');
|
||||
expect(imp).toEqual({ kind: 'wildcard', targetRaw: 'System.Math' });
|
||||
expect(imp).toEqual({
|
||||
kind: 'namespace',
|
||||
localName: 'Math',
|
||||
importedName: 'System.Math',
|
||||
targetRaw: 'System.Math',
|
||||
});
|
||||
});
|
||||
|
||||
it('strips `global::` qualifier — `using global::X.Y;` → namespace X.Y', () => {
|
||||
|
|
@ -91,7 +100,7 @@ describe('interpretCsharpImport — using flavors', () => {
|
|||
`;
|
||||
const imps = importsFor(src);
|
||||
expect(imps).toHaveLength(4);
|
||||
expect(imps.map((p) => p.kind)).toEqual(['namespace', 'namespace', 'alias', 'wildcard']);
|
||||
expect(imps.map((p) => p.kind)).toEqual(['namespace', 'namespace', 'alias', 'namespace']);
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue