mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-11 22:53:04 +00:00
* 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>
126 lines
5.4 KiB
TypeScript
126 lines
5.4 KiB
TypeScript
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
|
|
const { createParserForLanguage, getLanguageFromFilename, parseSourceSafeSpy } = vi.hoisted(() => ({
|
|
createParserForLanguage: vi.fn(),
|
|
getLanguageFromFilename: vi.fn((filePath: string) =>
|
|
filePath.endsWith('.py') ? 'python' : 'typescript',
|
|
),
|
|
parseSourceSafeSpy: vi.fn(),
|
|
}));
|
|
|
|
vi.mock('../../src/core/tree-sitter/parser-loader.js', () => ({
|
|
createParserForLanguage,
|
|
isLanguageAvailable: vi.fn().mockReturnValue(true),
|
|
resolveLanguageKey: vi.fn((language: string, filePath?: string) =>
|
|
language === 'typescript' && filePath?.endsWith('.tsx') ? 'typescript:tsx' : language,
|
|
),
|
|
}));
|
|
|
|
vi.mock('../../src/core/tree-sitter/safe-parse.js', async () => {
|
|
const { buildSafeParseMock } = await import('../helpers/parse-source-safe-mock.js');
|
|
return buildSafeParseMock(parseSourceSafeSpy);
|
|
});
|
|
|
|
// Partial mock: `ast-utils` now resolves the LanguageProvider registry to apply
|
|
// `preprocessSource`, and that graph needs the real shared exports (#2771).
|
|
vi.mock('gitnexus-shared', async (importOriginal) => ({
|
|
...(await importOriginal<typeof import('gitnexus-shared')>()),
|
|
getLanguageFromFilename,
|
|
}));
|
|
|
|
describe('ensureAndParse', () => {
|
|
beforeEach(() => {
|
|
vi.resetModules();
|
|
createParserForLanguage.mockReset();
|
|
getLanguageFromFilename.mockClear();
|
|
});
|
|
|
|
it('reuses the parser for the same grammar key across interleaved languages', async () => {
|
|
const tsParse = vi
|
|
.fn()
|
|
.mockReturnValueOnce({ lang: 'ts', content: 'first' })
|
|
.mockReturnValueOnce({ lang: 'ts', content: 'second' });
|
|
const pyParse = vi.fn().mockReturnValue({ lang: 'py', content: 'middle' });
|
|
|
|
createParserForLanguage.mockImplementation(async (language: string, filePath?: string) => {
|
|
if (language === 'typescript') return { parse: tsParse, key: filePath };
|
|
if (language === 'python') return { parse: pyParse, key: filePath };
|
|
throw new Error(`unexpected language ${language}`);
|
|
});
|
|
|
|
const { ensureAndParse } = await import('../../src/core/embeddings/ast-utils.js');
|
|
|
|
const tsFirst = await ensureAndParse('const one = 1;', 'first.ts');
|
|
const pyMiddle = await ensureAndParse('value = 1', 'middle.py');
|
|
const tsSecond = await ensureAndParse('const two = 2;', 'second.ts');
|
|
|
|
expect(tsFirst).toEqual({ lang: 'ts', content: 'first' });
|
|
expect(pyMiddle).toEqual({ lang: 'py', content: 'middle' });
|
|
expect(tsSecond).toEqual({ lang: 'ts', content: 'second' });
|
|
expect(createParserForLanguage).toHaveBeenCalledTimes(2);
|
|
expect(tsParse).toHaveBeenCalledTimes(2);
|
|
expect(pyParse).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
it('uses separate parser instances for .ts and .tsx', async () => {
|
|
const tsParse = vi.fn().mockReturnValue({ lang: 'ts' });
|
|
const tsxParse = vi.fn().mockReturnValue({ lang: 'tsx' });
|
|
|
|
createParserForLanguage.mockImplementation(async (_language: string, filePath?: string) => {
|
|
if (filePath?.endsWith('.tsx')) return { parse: tsxParse };
|
|
return { parse: tsParse };
|
|
});
|
|
|
|
const { ensureAndParse } = await import('../../src/core/embeddings/ast-utils.js');
|
|
|
|
await ensureAndParse('const value = 1;', 'plain.ts');
|
|
await ensureAndParse('export const View = <div />;', 'view.tsx');
|
|
await ensureAndParse('const other = 2;', 'other.ts');
|
|
|
|
expect(createParserForLanguage).toHaveBeenCalledTimes(2);
|
|
expect(tsParse).toHaveBeenCalledTimes(2);
|
|
expect(tsxParse).toHaveBeenCalledTimes(1);
|
|
});
|
|
|
|
// Windows SIGSEGV regression: ensureAndParse must route through parseSourceSafe
|
|
// so >32 767-char inputs do not crash the process. Direct parser.parse(content)
|
|
// on strings that size SIGSEGVs on Windows; the spy assertion is what catches
|
|
// a bypass since parser.parse(40 000 chars) succeeds on Linux/macOS.
|
|
it('routes >32 767-char input through parseSourceSafe', async () => {
|
|
parseSourceSafeSpy.mockClear();
|
|
|
|
const fakeParse = vi.fn().mockReturnValue({ rootNode: { type: 'module' } });
|
|
createParserForLanguage.mockResolvedValue({ parse: fakeParse });
|
|
|
|
const { ensureAndParse } = await import('../../src/core/embeddings/ast-utils.js');
|
|
|
|
const largeInput = 'const x = 1;\n'.repeat(4000); // ~52 000 chars
|
|
expect(largeInput.length).toBeGreaterThan(40_000);
|
|
|
|
const result = await ensureAndParse(largeInput, 'big.ts');
|
|
|
|
expect(parseSourceSafeSpy).toHaveBeenCalled();
|
|
expect(result).not.toBeNull();
|
|
});
|
|
|
|
it('parses Objective-C .h declarations and method snippets with the objc grammar', async () => {
|
|
const objcParse = vi.fn().mockReturnValue({ lang: 'objc' });
|
|
const cppParse = vi.fn().mockReturnValue({ lang: 'cpp' });
|
|
createParserForLanguage.mockImplementation(async (language: string) => {
|
|
if (language === 'objective-c') return { parse: objcParse };
|
|
if (language === 'cpp') return { parse: cppParse };
|
|
throw new Error(`unexpected language ${language}`);
|
|
});
|
|
|
|
const { ensureAndParse } = await import('../../src/core/embeddings/ast-utils.js');
|
|
|
|
await ensureAndParse('@interface Worker\n- (void)run;\n@end\n', 'Worker.h');
|
|
await ensureAndParse('- (void)run;\n', 'Worker.h');
|
|
await ensureAndParse('class Widget { int value; };\n', 'widget.h');
|
|
|
|
expect(createParserForLanguage).toHaveBeenCalledWith('objective-c', 'Worker.h');
|
|
expect(createParserForLanguage).toHaveBeenCalledWith('cpp', 'widget.h');
|
|
expect(objcParse).toHaveBeenCalledTimes(2);
|
|
expect(cppParse).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|