GitNexus/gitnexus/test/unit/parser-loader-abi.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

185 lines
6.4 KiB
TypeScript

import { describe, it, expect } from 'vitest';
import Parser from 'tree-sitter';
import {
listGrammarSources,
getLanguageGrammar,
} from '../../src/core/tree-sitter/parser-loader.js';
import { SupportedLanguages } from '../../src/config/supported-languages.js';
import { isOptionalGrammarRequired } from '../helpers/optional-grammar.js';
/**
* ABI load-smoke (#1922). For EVERY entry in `parser-loader.ts` SOURCES,
* `setLanguage` + parse a trivial snippet on a real `Parser`. This is the
* runtime counterpart to the static ABI assertion in
* `.github/scripts/check-tree-sitter-upgrade-readiness.py --assert-current`:
*
* - Required grammars MUST load and parse — an ABI-incompatible native
* binding (the #1242-class failure) fails here loudly.
* - Optional / vendored grammars (swift/dart/kotlin) must either load OR
* cleanly report unavailable — never hard-crash the process.
*
* Swift is prebuilt-only (no introspectable parser.c) so the static Python
* check can't assert its ABI; this smoke is where an ABI-incompatible Swift
* `.node` is caught. It is therefore included explicitly below.
*
* The (language, filePath, snippet) map is keyed by the raw SOURCES key so
* the `:tsx` variant is exercised distinctly from plain TypeScript.
*/
interface SmokeCase {
language: SupportedLanguages;
filePath?: string;
snippet: string;
rootType: string;
}
// Keyed by the exact SOURCES key (see parser-loader.ts) so every row —
// including `typescript:tsx` — has an explicit, asserted snippet.
const SMOKE_CASES: Record<string, SmokeCase> = {
[SupportedLanguages.JavaScript]: {
language: SupportedLanguages.JavaScript,
snippet: 'const x = 1;\n',
rootType: 'program',
},
[SupportedLanguages.TypeScript]: {
language: SupportedLanguages.TypeScript,
filePath: 'a.ts',
snippet: 'const x: number = 1;\n',
rootType: 'program',
},
[`${SupportedLanguages.TypeScript}:tsx`]: {
language: SupportedLanguages.TypeScript,
filePath: 'a.tsx',
snippet: 'const x = <div />;\n',
rootType: 'program',
},
[SupportedLanguages.Python]: {
language: SupportedLanguages.Python,
snippet: 'x = 1\n',
rootType: 'module',
},
[SupportedLanguages.Java]: {
language: SupportedLanguages.Java,
snippet: 'class A {}\n',
rootType: 'program',
},
[SupportedLanguages.CSharp]: {
language: SupportedLanguages.CSharp,
snippet: 'class A {}\n',
rootType: 'compilation_unit',
},
[SupportedLanguages.CPlusPlus]: {
language: SupportedLanguages.CPlusPlus,
snippet: 'int main() { return 0; }\n',
rootType: 'translation_unit',
},
[SupportedLanguages.ObjectiveC]: {
language: SupportedLanguages.ObjectiveC,
snippet:
'@interface ObjcSmoke\n- (void)run;\n@end\n@implementation ObjcSmoke\n- (void)run {}\n@end\n',
rootType: 'translation_unit',
},
[SupportedLanguages.Go]: {
language: SupportedLanguages.Go,
snippet: 'package main\nfunc main() {}\n',
rootType: 'source_file',
},
[SupportedLanguages.Rust]: {
language: SupportedLanguages.Rust,
snippet: 'fn main() {}\n',
rootType: 'source_file',
},
[SupportedLanguages.PHP]: {
language: SupportedLanguages.PHP,
snippet: '<?php $x = 1;\n',
rootType: 'program',
},
[SupportedLanguages.Ruby]: {
language: SupportedLanguages.Ruby,
snippet: 'x = 1\n',
rootType: 'program',
},
[SupportedLanguages.Vue]: {
language: SupportedLanguages.Vue,
snippet: 'const x = 1;\n',
rootType: 'program',
},
[SupportedLanguages.C]: {
language: SupportedLanguages.C,
snippet: 'int main(void) { return 0; }\n',
rootType: 'translation_unit',
},
[SupportedLanguages.Swift]: {
language: SupportedLanguages.Swift,
snippet: 'class Foo { func bar() {} }\n',
rootType: 'source_file',
},
[SupportedLanguages.Dart]: {
language: SupportedLanguages.Dart,
snippet: 'void main() {}\n',
rootType: 'program',
},
[SupportedLanguages.Kotlin]: {
language: SupportedLanguages.Kotlin,
snippet: 'fun main() {}\n',
rootType: 'source_file',
},
[SupportedLanguages.Zig]: {
language: SupportedLanguages.Zig,
snippet: 'pub fn main() void {}\n',
rootType: 'source_file',
},
};
describe('parser-loader ABI load-smoke (#1922)', () => {
const sources = listGrammarSources();
it('has a smoke case for every registered grammar SOURCES entry', () => {
const missing = sources.map((s) => s.key).filter((key) => !(key in SMOKE_CASES));
expect(missing, `add a SMOKE_CASES entry for: ${missing.join(', ')}`).toEqual([]);
});
// Explicit guard: Swift must be in the matrix so an ABI-incompatible
// prebuilt .node is caught here (the static Python check can't introspect
// a binary-only vendor).
it('includes Swift in the smoke matrix', () => {
expect(sources.some((s) => s.key === SupportedLanguages.Swift)).toBe(true);
});
it('includes Objective-C in the smoke matrix', () => {
expect(sources.some((s) => s.key === SupportedLanguages.ObjectiveC)).toBe(true);
});
for (const { key, optional } of sources) {
const testCase = SMOKE_CASES[key];
if (!testCase) continue; // covered by the "every entry" assertion above
it(`${optional ? 'optionally ' : ''}loads + parses ${key}`, () => {
let grammar: unknown;
try {
grammar = getLanguageGrammar(testCase.language, testCase.filePath);
} catch (err) {
// GITNEXUS_REQUIRE_<LANG>=1 revokes the optional exemption: a job that
// sets it runs on a platform the grammar publishes a prebuild for, so a
// load failure there is a real regression, not an absent binding.
if (optional && !isOptionalGrammarRequired(key)) {
// Optional/vendored grammar absent on this platform — the loader
// reported it cleanly (the only acceptable failure mode). Never a
// hard crash; the throw above proves a clean JS-level error.
expect(err).toBeInstanceOf(Error);
return;
}
throw err;
}
// A grammar that loads MUST parse + walk without crashing. Touching
// node.type is what surfaces an ABI mismatch (the #1242 unmarshalNode
// crash) rather than a benign load.
const parser = new Parser();
parser.setLanguage(grammar as Parameters<Parser['setLanguage']>[0]);
const tree = parser.parse(testCase.snippet);
expect(typeof tree.rootNode.type).toBe('string');
expect(tree.rootNode.type).toBe(testCase.rootType);
});
}
});