mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-08-28 05:25:25 +00:00
Some checks failed
CodeQL / Analyze (javascript-typescript) (push) Waiting to run
CodeQL / Analyze (python) (push) Waiting to run
Gitleaks / gitleaks (push) Waiting to run
Publish / Classify release event (push) Waiting to run
Publish / RC guard (marker + release-PR skip) (push) Blocked by required conditions
Publish / ci (push) Blocked by required conditions
Publish / Publish to npm (push) Blocked by required conditions
Publish / Build & Push RC Docker images (push) Blocked by required conditions
Scorecard / Scorecard analysis (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-cli) (push) Waiting to run
Trivy Image Scan / Trivy (gitnexus-web) (push) Waiting to run
Devcontainer Smoke / Config-transform unit tests (push) Has been cancelled
Devcontainer Smoke / Build devcontainer image (push) Has been cancelled
Skill copy sync / shipped skills drift guard (push) Has been cancelled
189 lines
7.3 KiB
TypeScript
189 lines
7.3 KiB
TypeScript
/**
|
|
* #2687 follow-up — a closure bound to a name emits ONE `Function` node in
|
|
* every language, not `Variable` in some and `Property` in others.
|
|
*
|
|
* `const f = () => {}` already produced a `Function` in TS/JS (that is what the
|
|
* #2687 twin fix preserved), but the same construct produced a `Variable` in
|
|
* Go/Python/Dart/C++ and a `Property` in Kotlin/Swift. The graph schema states
|
|
* "Function: Functions and arrow functions", and every syntactic tagger the
|
|
* convention was checked against (tree-sitter tags, universal-ctags) labels the
|
|
* binding a function — so the callable label is the consistent one.
|
|
*
|
|
* Each language's value capture still matches the same declaration node, so
|
|
* these rely on the #2687 pre-scan collapsing the pair; a regression there
|
|
* would surface here as a twin rather than a wrong label.
|
|
*
|
|
* The label alone does not make `f()` resolve — free-call resolution runs off
|
|
* the per-language scope-resolution queries. Go, Python and C++ now also carry a
|
|
* `@declaration.function` capture anchored on the inner closure literal, so
|
|
* calls resolve there too (asserted in the second describe). Kotlin, Swift and
|
|
* Dart still lack a `@scope.function` whose range matches the closure literal —
|
|
* Kotlin deliberately scopes `lambda_literal` as a BLOCK (#1757) — and an
|
|
* unaligned declaration anchor mis-attributes callers, so those three keep the
|
|
* label fix only.
|
|
*/
|
|
import { describe, expect, it } from 'vitest';
|
|
import fs from 'node:fs';
|
|
import os from 'node:os';
|
|
import path from 'node:path';
|
|
import { runPipelineFromRepo } from '../../src/core/ingestion/pipeline.js';
|
|
import { DIST_WORKER_URL, distWorkerExists } from '../helpers/worker-parse.js';
|
|
import { parseFilesWithWorkers } from '../helpers/worker-parse.js';
|
|
|
|
const labelsFor = async (path: string, content: string, name: string): Promise<string[]> => {
|
|
const { graph } = await parseFilesWithWorkers([{ path, content }]);
|
|
return graph.nodes
|
|
.filter((node) => node.properties.name === name)
|
|
.map((node) => node.label)
|
|
.sort();
|
|
};
|
|
|
|
describe('closure bindings emit a single Function node in every language', () => {
|
|
it('Go: var f = func(){}', async () => {
|
|
expect(
|
|
await labelsFor(
|
|
'src/handler.go',
|
|
'package main\n\nvar Handler = func(x int) int { return x }\n',
|
|
'Handler',
|
|
),
|
|
).toEqual(['Function']);
|
|
});
|
|
|
|
it('Python: f = lambda x: x', async () => {
|
|
expect(await labelsFor('src/handler.py', 'handler = lambda x: x\n', 'handler')).toEqual([
|
|
'Function',
|
|
]);
|
|
});
|
|
|
|
it('Kotlin: val f = { x -> x }', async () => {
|
|
expect(await labelsFor('src/Handler.kt', 'val handler = { x: Int -> x }\n', 'handler')).toEqual(
|
|
['Function'],
|
|
);
|
|
});
|
|
|
|
it('Swift: let f = { ... }', async () => {
|
|
expect(
|
|
await labelsFor(
|
|
'src/Handler.swift',
|
|
'let handler = { (x: Int) -> Int in return x }\n',
|
|
'handler',
|
|
),
|
|
).toEqual(['Function']);
|
|
});
|
|
|
|
it('C++: auto f = [](int x){ ... }', async () => {
|
|
expect(
|
|
await labelsFor('src/handler.cpp', 'auto handler = [](int x) { return x; };\n', 'handler'),
|
|
).toEqual(['Function']);
|
|
});
|
|
|
|
it('Dart: var f = (int x) => x', async () => {
|
|
expect(await labelsFor('src/handler.dart', 'var handler = (int x) => x;\n', 'handler')).toEqual(
|
|
['Function'],
|
|
);
|
|
});
|
|
|
|
// The suppression must key on an actual closure value, never on the
|
|
// declaration keyword — otherwise ordinary constants would vanish. One `it`
|
|
// per language: each spins its own worker pool and four in a single test
|
|
// exceeds the default timeout.
|
|
|
|
it('Go: leaves a genuine const alone', async () => {
|
|
expect(
|
|
await labelsFor('src/consts.go', 'package main\n\nconst MaxSize = 10\n', 'MaxSize'),
|
|
).toEqual(['Const']);
|
|
});
|
|
|
|
it('Python: leaves a genuine assignment alone', async () => {
|
|
expect(await labelsFor('src/consts.py', 'MAX_SIZE = 10\n', 'MAX_SIZE')).toEqual(['Variable']);
|
|
});
|
|
|
|
it('Kotlin: leaves a genuine property alone', async () => {
|
|
expect(await labelsFor('src/Consts.kt', 'val maxSize = 10\n', 'maxSize')).toEqual(['Property']);
|
|
});
|
|
|
|
it('C++: leaves a genuine variable alone', async () => {
|
|
expect(await labelsFor('src/consts.cpp', 'auto maxSize = 10;\n', 'maxSize')).toEqual([
|
|
'Variable',
|
|
]);
|
|
});
|
|
|
|
it('Python: an annotated attribute stays a Property, not a Variable', async () => {
|
|
// Regression guard. Python matches BOTH `@definition.property` (annotated)
|
|
// and `@definition.variable` (bare assignment) on the same statement at the
|
|
// same byte offset. Ranking `Property` level with the value labels made the
|
|
// winner depend on match order, which silently turned every typed attribute
|
|
// — including dataclass fields — into a file-level `Variable`.
|
|
expect(await labelsFor('src/model.py', 'class C:\n name: str = "x"\n', 'name')).toEqual([
|
|
'Property',
|
|
]);
|
|
});
|
|
|
|
it('Python: an annotated attribute keeps its owning HAS_PROPERTY edge', async () => {
|
|
// The label regression above also detached the attribute from its class:
|
|
// the node became `Variable:<file>:name` reached by `File -DEFINES->`
|
|
// instead of `Property:<file>:C.name` reached by `Class -HAS_PROPERTY->`.
|
|
const { graph } = await parseFilesWithWorkers([
|
|
{ path: 'src/owned.py', content: 'class C:\n name: str = "x"\n' },
|
|
]);
|
|
|
|
expect(
|
|
graph.relationships
|
|
.filter((rel) => rel.type === 'HAS_PROPERTY')
|
|
.map((rel) => `${rel.sourceId} -> ${rel.targetId}`),
|
|
).toEqual(['Class:src/owned.py:C -> Property:src/owned.py:C.name']);
|
|
});
|
|
});
|
|
|
|
const describeIfWorkerBuilt = distWorkerExists() ? describe : describe.skip;
|
|
|
|
/** Call targets resolved in a one-file repo, for the closure-call assertions. */
|
|
const callTargetsFor = async (filename: string, source: string): Promise<string[]> => {
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'gn-closure-calls-'));
|
|
try {
|
|
fs.writeFileSync(path.join(dir, filename), source, 'utf-8');
|
|
const result = await runPipelineFromRepo(dir, () => {}, {
|
|
workerPoolSize: 1,
|
|
workerUrlForTest: DIST_WORKER_URL,
|
|
});
|
|
return result.graph.relationships
|
|
.filter((rel) => rel.type === 'CALLS')
|
|
.map((rel) => rel.targetId)
|
|
.sort();
|
|
} finally {
|
|
fs.rmSync(dir, { recursive: true, force: true });
|
|
}
|
|
};
|
|
|
|
describeIfWorkerBuilt('calls to a closure binding resolve to its Function node', () => {
|
|
// The label change alone is not enough: each language also needs a
|
|
// `@declaration.function` anchored on the inner closure literal, so the def is
|
|
// owned by the closure's own scope and free-call resolution can find it.
|
|
|
|
it('Go: Handler(1) resolves', async () => {
|
|
const targets = await callTargetsFor(
|
|
'main.go',
|
|
'package main\n\nvar Handler = func(x int) int { return x }\n\nfunc Caller() int { return Handler(1) }\n',
|
|
);
|
|
|
|
expect(targets).toContain('Function:main.go:Handler');
|
|
});
|
|
|
|
it('Python: handler(1) resolves', async () => {
|
|
const targets = await callTargetsFor(
|
|
'app.py',
|
|
'handler = lambda x: x\n\ndef caller():\n return handler(1)\n',
|
|
);
|
|
|
|
expect(targets).toContain('Function:app.py:handler');
|
|
});
|
|
|
|
it('C++: handler(1) resolves', async () => {
|
|
const targets = await callTargetsFor(
|
|
'main.cpp',
|
|
'auto handler = [](int x) { return x; };\n\nint caller() { return handler(1); }\n',
|
|
);
|
|
|
|
expect(targets).toContain('Function:main.cpp:handler');
|
|
});
|
|
});
|