mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-10-03 02:21:44 +00:00
* fix(csharp): bind qualified constructor names, capture : base/: this, fix generic strip Mirrors the Java #1928 parsing-layer fixes for the C# scope-resolution path — the same three defect classes exist verbatim in C#: - Qualified / qualified-generic / alias-qualified constructor calls (`new Ns.Foo()`, `new A.B.Foo()`, `new Ns.Box<int>()`, `new MyAlias::Foo()`, `new global::Foo()`) bound only `@reference.call.constructor.qualified` with no `@reference.name`, so the central extractor fell back to the whole-expression anchor and the reference name became the raw `new Ns.Foo()` text (never resolved). Derive the simple-name tail via the existing `terminalTypeNameNode` helper (handles qualified_name, generic tail, and alias_qualified_name), and add a query arm for the top-level `alias_qualified_name` shape that was not captured at all. - `: base(...)` / `: this(...)` explicit constructor initializers, modeled by tree-sitter as `constructor_initializer` and never matched by the scope query, dropped the chained-constructor CALLS edges. Synthesize them: `this` → enclosing type name; `base` → the base type's bare name (first base-list entry, which C# requires to be the base class). Arity attached for overload disambiguation. - `interpretCsharpTypeBinding`'s qualifier strip used `lastIndexOf('.')` over the whole string, cutting inside a qualified generic type ARGUMENT (`Dictionary<string, Ns.User>` → `User>`). Make stripQualifier generic-aware: reduce only the segment before the first `<`, re-attaching the generic suffix — multi-arg generics stay intact so the `.Values`/`.Keys` collection-accessor unwrap keeps working. Tests: capture-level unit tests for every constructor shape (incl. alias-qualified, double-match guard) and `: base`/`: this` (incl. struct/record/mixed-base); interpretCsharpTypeBinding unit tests (the corruption case + nullable/nested/ unknown-generic edges); end-to-end resolver tests with new fixtures. The csharp-captures golden was regenerated — drift is purely additive (only the new fixtures; zero existing-fixture digests changed). Co-authored-by: Cursor <cursoragent@cursor.com> * fix(csharp): enhance constructor resolution and namespace qualification - Implemented qualified constructor name binding to resolve collisions between types in different namespaces. - Added support for `: base(...)` and `: this(...)` constructor initializers to ensure correct edge emission in the scope resolution. - Improved generic argument stripping to prevent incorrect parsing of qualified types. - Introduced tests for new features, including handling of interface-only base classes and qualified constructor calls. This update addresses issues related to constructor resolution and namespace qualification, ensuring accurate type references in C# code. Tests have been added to validate these changes. * fix(csharp): implement namespace prefix tagging for file-level type definitions - Updated the C# ingestion process to tag file-level type definitions with their enclosing namespace path using a new `namespacePrefix` field, without altering the `qualifiedName`. - Enhanced the scope resolver to utilize the `namespacePrefix` for resolving same-tail collisions in constructor calls, improving accuracy in type resolution. - Added unit tests to validate the new functionality, ensuring that namespace prefixes are correctly applied to both block-scoped and file-scoped types, while leaving namespace-free types untagged. This change addresses issues related to namespace qualification and constructor resolution in C# code, facilitating better handling of type references. * refactor(scope-resolution): share isOverloadableCallable via util Extract the ctor/function/method overload predicate into callable-labels.ts so graph-bridge registration and lookup stay aligned without duplicated private copies in ids.ts and node-lookup.ts. Co-authored-by: Cursor <cursoragent@cursor.com> --------- Co-authored-by: Cursor <cursoragent@cursor.com> Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
100 lines
3.9 KiB
TypeScript
100 lines
3.9 KiB
TypeScript
/**
|
|
* C# parsing-layer coverage gaps mirroring the Java #1928 findings — end-to-end.
|
|
*
|
|
* - F35: qualified / qualified-generic constructor calls (`new Ns.Foo()`,
|
|
* `new Ns.Box<int>()`) resolve to the target constructor/class instead
|
|
* of dropping the edge on a corrupted `Ns.Foo` reference name.
|
|
* - F38: `: base(...)` / `: this(...)` constructor initializers emit CALLS
|
|
* edges to the base / sibling constructor.
|
|
*/
|
|
import { describe, it, expect, beforeAll } from 'vitest';
|
|
import path from 'path';
|
|
import { FIXTURES, getRelationships, runPipelineFromRepo, type PipelineResult } from './helpers.js';
|
|
|
|
describe('C# qualified constructor resolution (F35, mirror of Java #1928)', () => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(
|
|
path.join(FIXTURES, 'csharp-qualified-constructor'),
|
|
() => {},
|
|
);
|
|
}, 60000);
|
|
|
|
it('resolves `new Models.Widget()` to the Widget type', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
const widget = calls.find((c) => c.source === 'Make' && c.target === 'Widget');
|
|
expect(widget).toBeDefined();
|
|
expect(['Class', 'Constructor']).toContain(widget!.targetLabel);
|
|
});
|
|
|
|
it('resolves `new Models.Box<int>()` to the Box type', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
const box = calls.find((c) => c.source === 'Make' && c.target === 'Box');
|
|
expect(box).toBeDefined();
|
|
expect(['Class', 'Constructor']).toContain(box!.targetLabel);
|
|
});
|
|
|
|
it('never emits a CALLS edge to a corrupted qualified/raw name', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
expect(calls.some((c) => c.target.includes('.') || c.target.includes('new '))).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('C# explicit constructor initializer resolution (F38, mirror of Java #1928)', () => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(path.join(FIXTURES, 'csharp-explicit-ctor-init'), () => {});
|
|
}, 60000);
|
|
|
|
it('resolves `: base(1)` to the Base type', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
const baseCall = calls.find((c) => c.target === 'Base');
|
|
expect(baseCall).toBeDefined();
|
|
expect(baseCall!.source).toBe('Child');
|
|
expect(['Class', 'Constructor']).toContain(baseCall!.targetLabel);
|
|
});
|
|
|
|
it('resolves `: this()` to a DISTINCT sibling Child constructor (no self-loop)', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
const thisCall = calls.find((c) => c.target === 'Child' && c.source === 'Child');
|
|
expect(thisCall).toBeDefined();
|
|
expect(thisCall!.targetLabel).toBe('Constructor');
|
|
expect(thisCall!.rel.sourceId).not.toBe(thisCall!.rel.targetId);
|
|
expect(thisCall!.rel.sourceId).toMatch(/Child\.Child#1/);
|
|
expect(thisCall!.rel.targetId).toMatch(/Child\.Child#0/);
|
|
});
|
|
});
|
|
|
|
describe('C# interface-only `: base()` must not target an interface (#2046)', () => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(path.join(FIXTURES, 'csharp-interface-only-base'), () => {});
|
|
}, 60000);
|
|
|
|
it('emits no CALLS edge to IFoo from `: base()` on `class C : IFoo`', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
expect(calls.some((c) => c.target === 'IFoo')).toBe(false);
|
|
expect(calls.some((c) => c.targetLabel === 'Interface')).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe('C# qualified constructor resolves by qualifier, not same-tail local (#2046)', () => {
|
|
let result: PipelineResult;
|
|
|
|
beforeAll(async () => {
|
|
result = await runPipelineFromRepo(
|
|
path.join(FIXTURES, 'csharp-namespace-qualified-ctor'),
|
|
() => {},
|
|
);
|
|
}, 60000);
|
|
|
|
it('resolves `new B.Foo()` to the Foo in namespace B, not the colliding A.Foo', () => {
|
|
const calls = getRelationships(result, 'CALLS');
|
|
const hit = calls.find((c) => c.source === 'Make' && c.target === 'Foo');
|
|
expect(hit).toBeDefined();
|
|
expect(hit!.targetFilePath).toBe('B/Foo.cs');
|
|
});
|
|
});
|