diff --git a/gitnexus/src/core/ingestion/resolve-references.ts b/gitnexus/src/core/ingestion/resolve-references.ts index e95659d8e..b34bff49d 100644 --- a/gitnexus/src/core/ingestion/resolve-references.ts +++ b/gitnexus/src/core/ingestion/resolve-references.ts @@ -220,7 +220,25 @@ function lookupForSite( ...(site.explicitReceiver !== undefined ? { explicitReceiver: site.explicitReceiver } : {}), }; const fieldHits = fieldRegistry.lookup(site.name, site.inScope, fieldOpts); - if (fieldHits.length > 0) return fieldHits; + // A BARE IDENTIFIER is not a member access. With no receiver there is no + // object whose `Property` this could be, so a hit on one is a false edge: + // in JS/TS/Python/Ruby a field read needs `this.` / `self.` / `@`, and the + // bare name means the nearest lexical binding instead. + // + // Observed: `class Box { baseUrl = '...'; pick() { const baseUrl = ...; + // return baseUrl; } }` linked the block-local read to `Box.baseUrl`, + // duplicating the legitimate `this.baseUrl` edge. That predates the + // TypeScript captures added here — JavaScript has emitted bare-identifier + // reads since A2 and no class fixture exercised the shadow. + // + // Callables are deliberately still reachable: `cb = save` naming a + // top-level function is a real bare-name reference, which is why the + // method/class fallbacks below are untouched. + const receiverlessFieldHits = + site.explicitReceiver === undefined + ? fieldHits.filter((hit) => hit.def?.type !== 'Property') + : fieldHits; + if (receiverlessFieldHits.length > 0) return receiverlessFieldHits; const methodHits = methodRegistry.lookup(site.name, site.inScope); if (methodHits.length > 0) return methodHits; return classRegistry.lookup(site.name, site.inScope); diff --git a/gitnexus/src/core/lbug/schema.ts b/gitnexus/src/core/lbug/schema.ts index 4d6b490de..c6dfb4d07 100644 --- a/gitnexus/src/core/lbug/schema.ts +++ b/gitnexus/src/core/lbug/schema.ts @@ -403,7 +403,7 @@ const ATTACHMENT_TARGET_LABELS: readonly NodeTableName[] = [ * * What survives here is characteristic, not arbitrary. Almost all of it is a * TARGET no rule reaches — `CodeElement`, `Impl`, `Namespace`, `Template`, - * `TypeAlias`, `Typedef`, `Union`, `Static`, `Section`, `Folder` are in neither + * `Typedef`, `Union`, `Static`, `Section`, `Folder` are in neither * `SCOPE_BRIDGE_TARGET_LABELS` nor {@link ATTACHMENT_TARGET_LABELS} — plus the * `Impl|*` and `Template|*` member rows (Rust `impl`/`trait` bodies, C++ * templates), the two `Route|Process` / `Tool|Process` entry points whose @@ -437,7 +437,6 @@ export const STRUCTURAL_PAIR_DDL = ` FROM File TO Folder, FROM File TO \`Union\`, FROM File TO \`Namespace\`, FROM File TO \`Impl\`, - FROM File TO \`TypeAlias\`, FROM File TO \`Static\`, FROM File TO \`Template\`, FROM File TO Section, @@ -445,20 +444,17 @@ export const STRUCTURAL_PAIR_DDL = ` FROM File TO Folder, FROM Folder TO File, FROM Function TO \`Template\`, FROM Function TO \`Namespace\`, - FROM Function TO \`TypeAlias\`, FROM Function TO \`Impl\`, FROM Function TO \`Typedef\`, FROM Function TO \`Union\`, FROM Function TO CodeElement, FROM Class TO \`Template\`, - FROM Class TO \`TypeAlias\`, FROM Class TO \`Impl\`, FROM Class TO \`Union\`, FROM Class TO \`Namespace\`, FROM Class TO \`Typedef\`, FROM Class TO CodeElement, FROM Method TO \`Template\`, - FROM Method TO \`TypeAlias\`, FROM Method TO \`Namespace\`, FROM Method TO \`Impl\`, FROM Method TO CodeElement, @@ -480,8 +476,6 @@ export const STRUCTURAL_PAIR_DDL = ` FROM File TO Folder, FROM CodeElement TO \`Property\`, FROM Section TO Section, FROM Interface TO CodeElement, - FROM Interface TO \`TypeAlias\`, - FROM \`Enum\` TO \`TypeAlias\`, FROM \`Namespace\` TO \`Struct\`, FROM \`Impl\` TO Method, FROM \`Impl\` TO Function, @@ -490,15 +484,10 @@ export const STRUCTURAL_PAIR_DDL = ` FROM File TO Folder, FROM \`Impl\` TO \`Trait\`, FROM \`Impl\` TO \`Struct\`, FROM \`Impl\` TO \`Impl\`, - FROM \`TypeAlias\` TO \`Trait\`, - FROM \`TypeAlias\` TO Class, - FROM \`TypeAlias\` TO \`Property\`, - FROM \`TypeAlias\` TO Method, FROM \`Record\` TO Method, FROM \`Record\` TO \`Constructor\`, FROM \`Record\` TO \`Property\`, FROM \`Constructor\` TO \`Template\`, - FROM \`Constructor\` TO \`TypeAlias\`, FROM \`Constructor\` TO \`Impl\`, FROM \`Constructor\` TO \`Namespace\`, FROM \`Constructor\` TO \`Typedef\`, diff --git a/gitnexus/test/fixtures/pipeline-golden/mini-repo/expected-graph.json b/gitnexus/test/fixtures/pipeline-golden/mini-repo/expected-graph.json index 91ee4fa05..4a5d5b408 100644 --- a/gitnexus/test/fixtures/pipeline-golden/mini-repo/expected-graph.json +++ b/gitnexus/test/fixtures/pipeline-golden/mini-repo/expected-graph.json @@ -3,7 +3,7 @@ "fixture": "mini-repo", "totalFileCount": 7, "symbols": 41, - "relationships": 80, + "relationships": 85, "processes": 4, "byType": { "Class": 1, @@ -25,7 +25,8 @@ "HAS_PROPERTY": 8, "IMPORTS": 12, "MEMBER_OF": 12, - "STEP_IN_PROCESS": 12 + "STEP_IN_PROCESS": 12, + "USES": 5 }, - "edgeDigest": "d617ad94554d760eb0497bf5365b7b0a2e21842ab5b16568def721b954aa1594" + "edgeDigest": "c55bd8307a5fccbfd23d5aa93f241e26ef0f45c1a9e57691f2bf4f6602e8e4ab" } diff --git a/gitnexus/test/integration/structural-pair-coverage.test.ts b/gitnexus/test/integration/structural-pair-coverage.test.ts index 9fad66ff8..ffa76cee0 100644 --- a/gitnexus/test/integration/structural-pair-coverage.test.ts +++ b/gitnexus/test/integration/structural-pair-coverage.test.ts @@ -117,21 +117,28 @@ const NON_BRIDGE_CORPUS = [ // died on any repo containing `type X = { ... }`. Every resolver test still // passed, because they build an in-memory graph and never write to the DB — // this suite is the only place that difference shows up. - // `TypeAlias|Property` is the LOAD-BEARING sentinel: `TypeAlias` is on the - // eleven-table list this suite exists for, so no cross-product generates it. - // `Interface|Property` was listed alongside it and is tautological — both - // labels are in the SCOPE_BRIDGE cross-product, so that pair is emitted by - // construction and the sentinel cannot fail. Dropped rather than left to - // read as coverage. + // `TypeAlias` USED to be off the generated grid, which is why round 1 hand- + // declared its pairs. It is now in `LINKABLE_LABELS` (the def→graph-node + // bridge needs it), which makes it a SCOPE_BRIDGE source and target, so the + // cross-product generates these pairs and the hand declarations were + // removed as redundant. + // + // The sentinel is still load-bearing, for a different reason than before: + // it now depends on `TypeAlias` being in `LINKABLE_LABELS`. Take it out and + // the pair stops being generated AND the hand declaration is gone, so this + // fails — which is exactly the state that also silently breaks alias + // consumer edges. `Interface|Property` was dropped from this entry because + // it is tautological in the ordinary way: both labels were always in the + // cross-product, so nothing about it could ever fail. fixture: 'typescript-alias-fields', emitter: 'object-type alias HAS_PROPERTY', sentinels: ['TypeAlias|Property'], }, { - // `TypeAlias|Method` is declared in `schema.ts` but no fixture contained a - // method-shaped alias member, so nothing proved it was the right pair for - // what is actually emitted — a declared pair that no emitter exercises is - // indistinguishable from a missing one until an analyze aborts on it. + // Nothing in the corpus contained a method-shaped alias member, so nothing + // proved `TypeAlias|Method` was the right pair for what is actually + // emitted — a pair no emitter exercises is indistinguishable from a missing + // one until an analyze aborts on a real repo. fixture: 'typescript-alias-methods', emitter: 'object-type alias HAS_METHOD', sentinels: ['TypeAlias|Method'],