GitNexus/gitnexus/test/unit/ingestion/non-value-definition-keys.test.ts
Gergő Magyar 89bbdcf566
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
fix(ingestion): stop double-indexing const X = () => {} as Function + edgeless Const twin (#2687) (#2691)
2026-07-25 16:56:17 +01:00

128 lines
4.6 KiB
TypeScript

/**
* #2687 — unit coverage for `buildNonValueDefinitionNameKeys`, the pre-scan that
* makes the parse-worker's duplicate suppression order-independent.
*
* The parse-worker consults these keys from its value-label branch, so what this
* pre-scan registers decides which `Const`/`Static`/`Variable` nodes get dropped.
* The two guards that matter most: keys are name-qualified (a multi-name
* declaration shares one definition node), and a match resolving to a value label
* registers nothing (so a match can never suppress itself).
*/
import { describe, expect, it } from 'vitest';
import type { LanguageProvider } from '../../../src/core/ingestion/language-provider.js';
import {
buildDefinitionPreScan,
type SyntaxNode,
} from '../../../src/core/ingestion/utils/ast-helpers.js';
/** Minimal stub — the pre-scan only reads `startIndex` and `text`. */
const node = (startIndex: number, text: string): SyntaxNode =>
({ startIndex, text }) as unknown as SyntaxNode;
const match = (captures: Record<string, SyntaxNode>) => ({
captures: Object.entries(captures).map(([name, syntaxNode]) => ({ name, node: syntaxNode })),
});
/** `getLabelFromCaptures` only reaches for `labelOverride`; nothing else. */
const PROVIDER = {} as unknown as LanguageProvider;
/** The non-value claim set — what `Const`/`Static`/`Variable` consult. */
const nonValueOf = (
matches: Parameters<typeof buildDefinitionPreScan>[0],
provider: LanguageProvider,
): ReadonlySet<string> => buildDefinitionPreScan(matches, provider).nonValue;
describe('buildDefinitionPreScan', () => {
it('registers a function capture under its startIndex and name', () => {
const keys = nonValueOf(
[match({ 'definition.function': node(0, 'const Bare = () => 1;'), name: node(6, 'Bare') })],
PROVIDER,
);
expect([...keys]).toEqual(['0:Bare']);
});
it('registers nothing for a value capture', () => {
const keys = nonValueOf(
[match({ 'definition.const': node(0, 'const CONFIG = {};'), name: node(6, 'CONFIG') })],
PROVIDER,
);
expect([...keys]).toEqual([]);
});
it('registers nothing for a match with no name capture', () => {
const keys = nonValueOf([match({ 'definition.function': node(0, '() => 1') })], PROVIDER);
expect([...keys]).toEqual([]);
});
it('registers nothing for a match with no definition capture', () => {
const keys = nonValueOf([match({ name: node(0, 'orphan') })], PROVIDER);
expect([...keys]).toEqual([]);
});
it('keys by name so a shared definition node does not over-suppress', () => {
// `const a = 1, b = () => {}` — both declarators share ONE definition node,
// so only `b`'s name may be claimed.
const declaration = node(0, 'const a = 1, b = () => {}');
const keys = nonValueOf(
[
match({ 'definition.const': declaration, name: node(6, 'a') }),
match({ 'definition.function': declaration, name: node(13, 'b') }),
],
PROVIDER,
);
expect([...keys]).toEqual(['0:b']);
});
it('registers nothing when a provider reclassifies a function capture to a value label', () => {
// Guards against self-suppression: if the pre-scan keyed off capture names
// rather than the resolved label, this match would register a key and then
// the main loop's value branch would drop its own node.
const provider = {
labelOverride: () => 'Const',
} as unknown as LanguageProvider;
const keys = nonValueOf(
[match({ 'definition.function': node(0, 'val x = {}'), name: node(4, 'x') })],
provider,
);
expect([...keys]).toEqual([]);
});
it('returns an empty set for no matches', () => {
expect([...nonValueOf([], PROVIDER)]).toEqual([]);
});
it('ranks a property claim as non-value but NOT callable', () => {
// The rank split is what keeps an annotated Python attribute ahead of its
// bare-assignment `Variable` twin while still letting a callable collapse a
// Kotlin/Swift closure property. A property in `callable` would make a
// property suppress itself.
const claims = buildDefinitionPreScan(
[match({ 'definition.property': node(0, 'name: str = "x"'), name: node(0, 'name') })],
PROVIDER,
);
expect({ nonValue: [...claims.nonValue], callable: [...claims.callable] }).toEqual({
nonValue: ['0:name'],
callable: [],
});
});
it('ranks a callable claim into both sets', () => {
const claims = buildDefinitionPreScan(
[match({ 'definition.function': node(0, 'val f = { }'), name: node(4, 'f') })],
PROVIDER,
);
expect({ nonValue: [...claims.nonValue], callable: [...claims.callable] }).toEqual({
nonValue: ['0:f'],
callable: ['0:f'],
});
});
});