mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-21 00:21:30 +00:00
feat(csharp-scope): Unit 3 — simple hooks (binding/import/receiver)
Adds simple-hooks.ts mirroring Python's pattern: - `csharpBindingScopeFor` — delegates to innermost (block scope is already captured by @scope.block in the query). - `csharpImportOwningScope` — binds `using` inside a namespace to that namespace's scope so imports don't leak into sibling namespaces. File-level using delegates to module. Function-body using (not legal C# but possible from malformed input) attaches to the function. - `csharpReceiverBinding` — looks up `this` / `base` in the function scope's type bindings; returns null for statics, free functions, and non-Function scopes. `this` / `base` synthesis itself is deferred to a follow-up (matches Python's receiver-binding.ts pattern). 9 new tests pin delegation semantics. 47/47 C# scope-resolution unit tests pass; tsc clean.
This commit is contained in:
parent
a3a2c65cc7
commit
dd7e553d07
2 changed files with 172 additions and 0 deletions
74
gitnexus/src/core/ingestion/languages/csharp/simple-hooks.ts
Normal file
74
gitnexus/src/core/ingestion/languages/csharp/simple-hooks.ts
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/**
|
||||
* Trivial / no-op-ish hooks for the C# provider. Kept together because
|
||||
* each is a few lines and they share a common theme: they make the
|
||||
* provider's choice explicit rather than relying on "absence == default"
|
||||
* so reviewers don't have to re-derive the analysis.
|
||||
*/
|
||||
|
||||
import type {
|
||||
CaptureMatch,
|
||||
ParsedImport,
|
||||
Scope,
|
||||
ScopeId,
|
||||
ScopeTree,
|
||||
TypeRef,
|
||||
} from 'gitnexus-shared';
|
||||
|
||||
// ─── bindingScopeFor ──────────────────────────────────────────────────────
|
||||
|
||||
/** C# has block scope, but the central extractor's "innermost enclosing
|
||||
* scope" default already handles it correctly: class-body declarations
|
||||
* attach to the innermost Class scope, method-body declarations attach
|
||||
* to the innermost Function scope, and namespace-body declarations
|
||||
* attach to the innermost Namespace scope (which the scope query emits
|
||||
* for both `namespace X { }` and `namespace X;` forms).
|
||||
*
|
||||
* Returns `null` to delegate. */
|
||||
export function csharpBindingScopeFor(
|
||||
_decl: CaptureMatch,
|
||||
_innermost: Scope,
|
||||
_tree: ScopeTree,
|
||||
): ScopeId | null {
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── importOwningScope ────────────────────────────────────────────────────
|
||||
|
||||
/** `using` inside `namespace X { }` binds to that namespace's scope (its
|
||||
* types are visible only within that namespace's members). File-level
|
||||
* `using` delegates to the module default. Class-body `using` is not
|
||||
* legal C# — defensively handle it by attaching to the class if it
|
||||
* ever slips through.
|
||||
*
|
||||
* `global using X;` (C# 10+) at the compilation_unit level is treated
|
||||
* as a file-scoped using for Unit 2's purposes; cross-file propagation
|
||||
* will be addressed if Unit 7's parity gate flags it. */
|
||||
export function csharpImportOwningScope(
|
||||
_imp: ParsedImport,
|
||||
innermost: Scope,
|
||||
_tree: ScopeTree,
|
||||
): ScopeId | null {
|
||||
if (innermost.kind === 'Namespace' || innermost.kind === 'Class' || innermost.kind === 'Function')
|
||||
return innermost.id;
|
||||
return null;
|
||||
}
|
||||
|
||||
// ─── receiverBinding ──────────────────────────────────────────────────────
|
||||
|
||||
/** Look up `this` or `base` in the function scope's type bindings.
|
||||
* Returns `null` for free functions (no `this`), static methods (no
|
||||
* `this` binding synthesized), and non-Function scopes.
|
||||
*
|
||||
* `this` and `base` are synthesized as type bindings on instance
|
||||
* methods during capture emission (receiver-binding.ts, planned for a
|
||||
* follow-up unit). Until that synthesis lands this hook returns `null`
|
||||
* for every instance method, which matches the legacy fallback
|
||||
* behavior — the central extractor then walks the enclosing class
|
||||
* scope to recover the receiver type.
|
||||
*
|
||||
* Matches `pythonReceiverBinding`'s shape so the two provider wirings
|
||||
* stay symmetric. */
|
||||
export function csharpReceiverBinding(functionScope: Scope): TypeRef | null {
|
||||
if (functionScope.kind !== 'Function') return null;
|
||||
return functionScope.typeBindings.get('this') ?? functionScope.typeBindings.get('base') ?? null;
|
||||
}
|
||||
|
|
@ -0,0 +1,98 @@
|
|||
/**
|
||||
* Unit 3 coverage for C# simple hooks.
|
||||
*
|
||||
* Exercises the small-surface hooks that mirror Python's simple-hooks:
|
||||
* `bindingScopeFor`, `importOwningScope`, `receiverBinding`. Each hook
|
||||
* is tiny, but the tests pin the delegation semantics so refactors
|
||||
* don't silently re-route bindings.
|
||||
*
|
||||
* `isSuperReceiver` lives on the ScopeResolver contract (Unit 6) rather
|
||||
* than the LanguageProvider, so it isn't exercised here.
|
||||
*/
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import {
|
||||
csharpBindingScopeFor,
|
||||
csharpImportOwningScope,
|
||||
csharpReceiverBinding,
|
||||
} from '../../../../src/core/ingestion/languages/csharp/simple-hooks.js';
|
||||
import type { CaptureMatch, ParsedImport, Scope, ScopeTree, TypeRef } from 'gitnexus-shared';
|
||||
|
||||
function fakeScope(
|
||||
kind: Scope['kind'],
|
||||
id = 's1',
|
||||
typeBindings = new Map<string, TypeRef>(),
|
||||
): Scope {
|
||||
return {
|
||||
id,
|
||||
kind,
|
||||
parentId: null,
|
||||
childrenIds: [],
|
||||
bindings: new Map(),
|
||||
typeBindings,
|
||||
} as unknown as Scope;
|
||||
}
|
||||
|
||||
const fakeTree = {} as ScopeTree;
|
||||
const fakeCapture = {} as CaptureMatch;
|
||||
const fakeImport: ParsedImport = {
|
||||
kind: 'namespace',
|
||||
localName: 'System',
|
||||
importedName: 'System',
|
||||
targetRaw: 'System',
|
||||
};
|
||||
|
||||
describe('csharpBindingScopeFor', () => {
|
||||
it('delegates to innermost for method-body declarations', () => {
|
||||
const fn = fakeScope('Function');
|
||||
expect(csharpBindingScopeFor(fakeCapture, fn, fakeTree)).toBe(null);
|
||||
});
|
||||
|
||||
it('delegates to innermost for namespace-body class declarations', () => {
|
||||
const ns = fakeScope('Namespace');
|
||||
expect(csharpBindingScopeFor(fakeCapture, ns, fakeTree)).toBe(null);
|
||||
});
|
||||
});
|
||||
|
||||
describe('csharpImportOwningScope', () => {
|
||||
it('binds `using` inside a namespace to the namespace scope', () => {
|
||||
const ns = fakeScope('Namespace', 'ns-1');
|
||||
expect(csharpImportOwningScope(fakeImport, ns, fakeTree)).toBe('ns-1');
|
||||
});
|
||||
|
||||
it('delegates file-level `using` to the module default', () => {
|
||||
const mod = fakeScope('Module');
|
||||
expect(csharpImportOwningScope(fakeImport, mod, fakeTree)).toBe(null);
|
||||
});
|
||||
|
||||
it('attaches `using` inside a function scope to that function', () => {
|
||||
// Not legal C# at the source level, but defensive — Unit 7 parity
|
||||
// gate flags any regression.
|
||||
const fn = fakeScope('Function', 'fn-1');
|
||||
expect(csharpImportOwningScope(fakeImport, fn, fakeTree)).toBe('fn-1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('csharpReceiverBinding', () => {
|
||||
it('returns the `this` type binding for an instance method scope', () => {
|
||||
const binding: TypeRef = { rawName: 'User', source: 'self' } as unknown as TypeRef;
|
||||
const fn = fakeScope('Function', 'm-1', new Map([['this', binding]]));
|
||||
expect(csharpReceiverBinding(fn)).toBe(binding);
|
||||
});
|
||||
|
||||
it('falls back to `base` when `this` is absent', () => {
|
||||
const binding: TypeRef = { rawName: 'Parent', source: 'self' } as unknown as TypeRef;
|
||||
const fn = fakeScope('Function', 'm-1', new Map([['base', binding]]));
|
||||
expect(csharpReceiverBinding(fn)).toBe(binding);
|
||||
});
|
||||
|
||||
it('returns null for a static method (no synthesized `this`/`base`)', () => {
|
||||
const fn = fakeScope('Function', 'm-1');
|
||||
expect(csharpReceiverBinding(fn)).toBe(null);
|
||||
});
|
||||
|
||||
it('returns null for non-Function scopes', () => {
|
||||
expect(csharpReceiverBinding(fakeScope('Class'))).toBe(null);
|
||||
expect(csharpReceiverBinding(fakeScope('Module'))).toBe(null);
|
||||
});
|
||||
});
|
||||
Loading…
Add table
Reference in a new issue