GitNexus/gitnexus/test/unit/preprocess-source-parity.test.ts
mengkaka 4154b63131
feat(indexing): add Objective-C semantic indexing support (#3179)
* docs: add Objective-C fork provider notes

* feat(objective-c): add deterministic provider and grammar

* feat(objective-c): finalize provider MVP

* fix(objective-c): harden provider integration

* fix(objective-c): normalize bare macro markers

* docs(objective-c): integrate provider documentation

* fix(objective-c): harden resolution and header classification

* fix(objective-c): complete provider follow-ups

* fix: address Objective-C review follow-ups

* chore: format Objective-C grammar sources

* fix(objective-c): harden review follow-ups

* Address PR review feedback (#3179)

Keep Objective-C chunking and macro recovery aligned with the grammar, and stop Community MEMBER_OF edges from leaking into symbol context.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address follow-up review on ObjC chunking and language fallback.

Keep preprocessor directive text from changing file-scope brace depth, group real ivar nodes, skip header modifiers, and restore Rakefile/Gemfile detection through getLanguageFromFilename.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Parse Objective-C headers with the objc grammar in embeddings.

ensureAndParse and structural extraction now use the same content classifier as ingest, including method snippets from .h files, so Protocol/Category/Class chunks are not re-parsed as C++.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Keep file-scope macro elision off C line splices and @interface/@protocol/@implementation bodies, and attach ivar attributes to the following instance variable when chunking.

Co-authored-by: Cursor <cursoragent@cursor.com>

* chore(bench): rebaseline Objective-C CSV emit

* feat(objective-c): add workspace resolution and linear emit benches

Plain .h files are classified as C++, so the ObjC pass could not
resolve #import of those headers. Load a C/C#-style workspace once
per pass, and keep protocol-candidate USES linear.

Refs #3179

Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

- Compare LadybugDB labels() as a scalar when excluding Community MEMBER_OF edges.
- Walk superclass members, skip file-static C sibling defs, and ignore comments in ObjC header/macro scans.

Note: pre-existing failure in objective-c-provider integration (worker-pool ready timeout) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit Objective-C declaration captures so compilation-unit siblings can share
header/implementation bindings, and keep class vs protocol visibility groups
distinct.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

* Address PR review feedback (#3179)

Emit every comma-separated property/ivar declarator, and count @interface
after a multiline block comment closes so in-declaration macros stay intact.

Note: pre-existing failure in worker-pool startup (GITNEXUS_WORKER_READY_TIMEOUT_MS) not addressed by this PR.
Co-authored-by: Cursor <cursoragent@cursor.com>

---------

Co-authored-by: ximengkai <ximengkai@soyoung.com>
Co-authored-by: Gergő Magyar <gergomagyar@icloud.com>
Co-authored-by: Gergo Magyar <gergomagyar0@gmail.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-09 09:40:21 +00:00

116 lines
4 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'),
},
[SupportedLanguages.ObjectiveC]: {
filePath: 'Marker.m',
source: [
'RCT_EXTERN_C_BEGIN',
'typedef struct {',
' int value;',
'} GNMarker;',
'RCT_EXTERN_C_END',
'@protocol GNMarkerProtocol',
'- (void)run;',
'@end',
'',
].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);
});
},
);
});
});