mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
test(scope-resolution): guard closure identity invariants (#2748)
This commit is contained in:
parent
bc76ba2f25
commit
bee3e82ab2
3 changed files with 122 additions and 0 deletions
|
|
@ -596,6 +596,21 @@ describeIfWorkerBuilt('a closure binding as a call SOURCE (#2699 part B)', () =>
|
|||
expect(targets).toEqual(['rel:CALLS:Function:A.kt:handler->Function:A.kt:target']);
|
||||
});
|
||||
|
||||
it('Kotlin: a call at block level is not attributed to a nested named function (#2736)', async () => {
|
||||
// A Block-kind scope may own a nested function without being that
|
||||
// function's body. The start-position alignment check is what keeps the
|
||||
// block-level call attributed to `outer`, rather than swallowing it into
|
||||
// the uncalled `nested`.
|
||||
const targets = await callEdgeIdsFor(
|
||||
'Block.kt',
|
||||
'fun target(): Int = 1\n\nfun outer(): Int {\n' +
|
||||
' if (true) {\n fun nested(): Int = 0\n return target()\n }\n' +
|
||||
' return 0\n}\n',
|
||||
);
|
||||
|
||||
expect(targets).toEqual(['rel:CALLS:Function:Block.kt:outer->Function:Block.kt:target']);
|
||||
});
|
||||
|
||||
it('PHP: a call inside the closure IS attributed to the binding (#2699 S1)', async () => {
|
||||
// FLIPPED by #2699 S1. php/query.ts now carries the closure-binding
|
||||
// declaration rule with javascript/query.ts's anchor discipline
|
||||
|
|
|
|||
|
|
@ -25,11 +25,23 @@
|
|||
* pass. Every assertion here is therefore on the EDGE, whose source and target
|
||||
* are produced by the two different phases: it can only pass if both agree.
|
||||
*/
|
||||
import {
|
||||
buildDefIndex,
|
||||
buildScopeTree,
|
||||
type Reference,
|
||||
type Scope,
|
||||
type ScopeId,
|
||||
type SymbolDefinition,
|
||||
} from 'gitnexus-shared';
|
||||
import { describe, expect, it, vi } from 'vitest';
|
||||
import fs from 'node:fs';
|
||||
import os from 'node:os';
|
||||
import path from 'node:path';
|
||||
import { createKnowledgeGraph } from '../../src/core/graph/graph.js';
|
||||
import type { ScopeResolutionIndexes } from '../../src/core/ingestion/model/scope-resolution-indexes.js';
|
||||
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
||||
import { buildGraphNodeLookup } from '../../src/core/ingestion/scope-resolution/graph-bridge/node-lookup.js';
|
||||
import { emitReferencesViaLookup } from '../../src/core/ingestion/scope-resolution/graph-bridge/references-to-edges.js';
|
||||
import { DIST_WORKER_URL, distWorkerExists } from '../helpers/worker-parse.js';
|
||||
|
||||
vi.setConfig({ testTimeout: 90_000 });
|
||||
|
|
@ -352,3 +364,94 @@ describeIfWorkerBuilt('function-local VALUES carry their own identity (#2699 A1)
|
|||
expect(ids).toEqual(['Const:v.ts:handler', 'Const:v.ts:run.handler@3:2']);
|
||||
});
|
||||
});
|
||||
|
||||
describe('function-local value identity resolves through to emitted edges (#2736)', () => {
|
||||
it('targets the position-qualified local rather than its file-level twin', () => {
|
||||
const filePath = 'v.ts';
|
||||
const caller: SymbolDefinition = {
|
||||
nodeId: 'def:v.ts#3:0:Function:run',
|
||||
filePath,
|
||||
type: 'Function',
|
||||
qualifiedName: 'run',
|
||||
};
|
||||
const topLevel: SymbolDefinition = {
|
||||
nodeId: 'def:v.ts#1:0:Const:handler',
|
||||
filePath,
|
||||
type: 'Const',
|
||||
qualifiedName: 'handler',
|
||||
};
|
||||
const local: SymbolDefinition = {
|
||||
nodeId: 'def:v.ts#4:2:Const:run.handler',
|
||||
filePath,
|
||||
type: 'Const',
|
||||
qualifiedName: 'run.handler',
|
||||
};
|
||||
const moduleScope: Scope = {
|
||||
id: 'scope:v.ts#1:0-100:0:Module',
|
||||
parent: null,
|
||||
kind: 'Module',
|
||||
range: { startLine: 1, startCol: 0, endLine: 100, endCol: 0 },
|
||||
filePath,
|
||||
bindings: new Map(),
|
||||
ownedDefs: [caller, topLevel],
|
||||
imports: [],
|
||||
typeBindings: new Map(),
|
||||
};
|
||||
const functionScope: Scope = {
|
||||
id: 'scope:v.ts#3:0-6:1:Function',
|
||||
parent: moduleScope.id,
|
||||
kind: 'Function',
|
||||
range: { startLine: 3, startCol: 0, endLine: 6, endCol: 1 },
|
||||
filePath,
|
||||
bindings: new Map(),
|
||||
ownedDefs: [local],
|
||||
imports: [],
|
||||
typeBindings: new Map(),
|
||||
};
|
||||
const indexes = {
|
||||
scopeTree: buildScopeTree([moduleScope, functionScope]),
|
||||
defs: buildDefIndex([caller, topLevel, local]),
|
||||
} as unknown as ScopeResolutionIndexes;
|
||||
|
||||
const graph = createKnowledgeGraph();
|
||||
graph.addNode({
|
||||
id: 'Function:v.ts:run',
|
||||
label: 'Function',
|
||||
properties: { name: 'run', qualifiedName: 'run', filePath, startLine: 2 },
|
||||
});
|
||||
graph.addNode({
|
||||
id: 'Const:v.ts:handler',
|
||||
label: 'Const',
|
||||
properties: { name: 'handler', qualifiedName: 'handler', filePath, startLine: 0 },
|
||||
});
|
||||
graph.addNode({
|
||||
id: 'Const:v.ts:run.handler@3:2',
|
||||
label: 'Const',
|
||||
properties: { name: 'handler', qualifiedName: 'run.handler', filePath, startLine: 3 },
|
||||
});
|
||||
|
||||
const reference: Reference = {
|
||||
fromScope: functionScope.id,
|
||||
toDef: local.nodeId,
|
||||
atRange: { startLine: 5, startCol: 9, endLine: 5, endCol: 16 },
|
||||
kind: 'read',
|
||||
confidence: 1,
|
||||
evidence: [],
|
||||
};
|
||||
const emitted = emitReferencesViaLookup(
|
||||
graph,
|
||||
indexes,
|
||||
{ bySourceScope: new Map<ScopeId, readonly Reference[]>([[functionScope.id, [reference]]]) },
|
||||
buildGraphNodeLookup(graph),
|
||||
);
|
||||
|
||||
expect(emitted).toEqual({ emitted: 1, skipped: 0 });
|
||||
expect(graph.relationships).toEqual([
|
||||
expect.objectContaining({
|
||||
sourceId: 'Function:v.ts:run',
|
||||
targetId: 'Const:v.ts:run.handler@3:2',
|
||||
type: 'ACCESSES',
|
||||
}),
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -101,6 +101,10 @@ describe('fileContentHash', () => {
|
|||
});
|
||||
|
||||
describe('PARSE_CACHE_VERSION', () => {
|
||||
it('pins SCHEMA_BUMP to 31 so concurrent bumps cannot silently collide (#2736)', () => {
|
||||
expect(Number(PARSE_CACHE_VERSION.split('+', 1)[0])).toBe(31);
|
||||
});
|
||||
|
||||
it('embeds the gitnexus package version (so upgrades invalidate the cache)', () => {
|
||||
// Looks like "1+1.6.4" — schema bump prefix + actual gitnexus version
|
||||
expect(PARSE_CACHE_VERSION).toMatch(/^\d+\+\d+\.\d+\.\d+/);
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue