GitNexus/gitnexus/test/unit/preprocess-source-parity.test.ts
azizur100389 4f16bd8023
fix(impact): report scope extraction omissions (#3071)
* fix(impact): surface scope extraction omissions

* fix(impact): preserve complete index fixtures

* fix(impact): preserve scope completeness evidence

* test(analyze): model successful scope extraction in harnesses

---------

Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
2026-08-29 08:38:20 +01:00

102 lines
3.7 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { SupportedLanguages } from 'gitnexus-shared';
import { providers, getProvider } from '../../src/core/ingestion/languages/index.js';
import { extractParsedFile } from '../../src/core/ingestion/scope-extractor-bridge.js';
import { isLanguageAvailable } from '../../src/core/tree-sitter/parser-loader.js';
import { ensureAndParse } from '../../src/core/embeddings/ast-utils.js';
import type { LanguageProvider } from '../../src/core/ingestion/language-provider.js';
/**
* Every provider that defines `preprocessSource` must produce the same
* `ParsedFile` whether it is handed raw source or already-preprocessed source.
*
* The parse worker applies the hook, but `emitScopeCaptures` re-parses on a
* parse-cache miss and the embedding pipeline parses independently — so unless
* those paths see the same transform the halves of the pipeline analyze
* different programs and the graph depends on whether the run was warm (#2771).
*
* Fixtures are keyed by language and cross-checked against the registry, so a
* new provider adopting the hook fails here until it adds one.
*/
const FIXTURES: Partial<Record<SupportedLanguages, { filePath: string; source: string }>> = {
[SupportedLanguages.Swift]: {
filePath: 'Fixture.swift',
source: [
'class Outer {',
' enum A { case x }',
' #if os(iOS)',
' enum B { case y }',
' #endif',
'}',
'',
].join('\n'),
},
[SupportedLanguages.CPlusPlus]: {
filePath: 'Actor.cpp',
source: [
'UCLASS()',
'class MYGAME_API AGameActor : public AActor {',
' GENERATED_BODY()',
'public:',
' UPROPERTY(EditAnywhere) int Health;',
' UFUNCTION(BlueprintCallable) void Tick(float DeltaTime) { Health = 1; }',
'};',
'',
].join('\n'),
},
[SupportedLanguages.Dart]: {
filePath: 'meters.dart',
source: ['extension type Meters(int value) {', ' int get raw => value;', '}', ''].join('\n'),
},
};
const languagesWithHook = Object.entries(providers)
.filter(([, provider]) => provider.preprocessSource !== undefined)
.map(([language]) => language)
.sort();
describe('LanguageProvider.preprocessSource parity', () => {
it('does not propagate an exception thrown by the warning callback', () => {
const provider = {
emitScopeCaptures: () => {
throw new Error('provider failed');
},
} as unknown as LanguageProvider;
expect(() =>
extractParsedFile(provider, 'const value = 1;', 'broken.ts', () => {
throw new Error('warning transport closed');
}),
).not.toThrow();
});
it('has a fixture for every provider defining the hook', () => {
expect(Object.keys(FIXTURES).sort()).toEqual(languagesWithHook);
});
describe.each(languagesWithHook)('%s', (language) => {
const provider = getProvider(language as SupportedLanguages);
const { filePath, source } = FIXTURES[language as SupportedLanguages]!;
describe.skipIf(!isLanguageAvailable(language as SupportedLanguages))(
'with the grammar',
() => {
it('extracts the same ParsedFile from raw and preprocessed source', () => {
const preprocessed = provider.preprocessSource!(source, filePath);
expect(preprocessed).not.toBe(source);
expect(preprocessed).toHaveLength(source.length);
expect(extractParsedFile(provider, source, filePath, () => {})).toEqual(
extractParsedFile(provider, preprocessed, filePath, () => {}),
);
});
it('parses the preprocessed text on the embedding path too', async () => {
const tree = await ensureAndParse(source, filePath);
expect(tree.rootNode.hasError).toBe(false);
});
},
);
});
});