diff --git a/gitnexus-shared/src/index.ts b/gitnexus-shared/src/index.ts index d4efe8a89..ea66c3855 100644 --- a/gitnexus-shared/src/index.ts +++ b/gitnexus-shared/src/index.ts @@ -134,7 +134,11 @@ export type { // Scope tree spine + position lookup (RFC §2.2 + §3.1; Ring 2 SHARED #912) export { makeScopeId, clearScopeIdInternPool } from './scope-resolution/scope-id.js'; export type { ScopeIdInput } from './scope-resolution/scope-id.js'; -export { buildScopeTree, ScopeTreeInvariantError } from './scope-resolution/scope-tree.js'; +export { + buildScopeTree, + canParentScope, + ScopeTreeInvariantError, +} from './scope-resolution/scope-tree.js'; export type { ScopeTree } from './scope-resolution/scope-tree.js'; export { buildPositionIndex } from './scope-resolution/position-index.js'; export type { PositionIndex } from './scope-resolution/position-index.js'; diff --git a/gitnexus-shared/src/scope-resolution/scope-tree.ts b/gitnexus-shared/src/scope-resolution/scope-tree.ts index 96aa222ec..5011f2a36 100644 --- a/gitnexus-shared/src/scope-resolution/scope-tree.ts +++ b/gitnexus-shared/src/scope-resolution/scope-tree.ts @@ -260,7 +260,7 @@ function rangesEqual(a: Range, b: Range): boolean { * helper in `scope-extractor.ts` so `pass1BuildScopes` and the validator * agree on what a well-formed parent edge looks like. */ -function canParentScope( +export function canParentScope( outer: Range, inner: Range, outerKind: Scope['kind'], diff --git a/gitnexus/src/core/ingestion/scope-extractor.ts b/gitnexus/src/core/ingestion/scope-extractor.ts index 829db89da..063b8a62d 100644 --- a/gitnexus/src/core/ingestion/scope-extractor.ts +++ b/gitnexus/src/core/ingestion/scope-extractor.ts @@ -74,7 +74,7 @@ import type { SymbolDefinition, TypeRef, } from 'gitnexus-shared'; -import { buildPositionIndex, buildScopeTree, makeScopeId } from 'gitnexus-shared'; +import { buildPositionIndex, buildScopeTree, canParentScope, makeScopeId } from 'gitnexus-shared'; import type { LanguageProvider } from './language-provider.js'; // ─── Narrow hook surface the extractor actually uses ─────────────────────── @@ -924,56 +924,6 @@ function rangesEqual(a: Range, b: Range): boolean { ); } -function rangeStrictlyContains(outer: Range, inner: Range): boolean { - if ( - outer.startLine === inner.startLine && - outer.startCol === inner.startCol && - outer.endLine === inner.endLine && - outer.endCol === inner.endCol - ) { - return false; - } - const startsBefore = - outer.startLine < inner.startLine || - (outer.startLine === inner.startLine && outer.startCol <= inner.startCol); - const endsAfter = - outer.endLine > inner.endLine || - (outer.endLine === inner.endLine && outer.endCol >= inner.endCol); - return startsBefore && endsAfter; -} - -/** - * Whether `outer` (kind `outerKind`) is a valid parent for `inner` (kind - * `innerKind`) by tree-sitter range geometry. - * - * Strict containment is the general rule. The single carve-out is the - * `Module`/non-Module pair whose ranges are exactly equal — this happens - * naturally when tree-sitter reports identical byte spans for the - * `compilation_unit` and the file's single top-level scope (e.g. a C# file - * consisting of nothing but `namespace X { ... }` with no leading or - * trailing trivia outside the namespace's `{}` body). The `Module` - * is the universal outer of any file-level scope by language semantics, so - * coincident ranges should not break the parent chain. The `rangesEqual` - * Module carve-out is direction-asymmetric: only Module-as-outer parents - * a same-range non-Module, never the reverse, so the relationship stays - * acyclic and `buildScopeTree`'s parent-must-share-filePath / no-cycle - * invariants continue to hold. - * - * `rangeStrictlyContains` keeps its strict semantics so position-index - * lookups, hook-side range comparisons, and other call sites are - * unchanged. - */ -function canParentScope( - outer: Range, - inner: Range, - outerKind: ScopeKind, - innerKind: ScopeKind, -): boolean { - if (rangeStrictlyContains(outer, inner)) return true; - if (outerKind === 'Module' && innerKind !== 'Module' && rangesEqual(outer, inner)) return true; - return false; -} - /** * Capture names that are never anchors — they are sub-tags nested inside a * larger anchor (e.g., the receiver expression inside a `@reference.call`