GitNexus/gitnexus/test/unit/objective-c-macro-marker-preprocess.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

195 lines
7.6 KiB
TypeScript

import { describe, expect, it } from 'vitest';
import { preprocessObjectiveCMacroMarkers } from '../../src/core/ingestion/languages/objective-c/macro-marker-preprocess.js';
describe('preprocessObjectiveCMacroMarkers', () => {
it('elides bare file-scope markers and preserves positions', () => {
const source = [
'#define RCT_EXTERN_C_BEGIN',
'#define RCT_EXTERN_C_END',
'RCT_EXTERN_C_BEGIN',
'typedef struct RCTMethodInfo {',
' const char *const jsName;',
'} RCTMethodInfo;',
'RCT_EXTERN_C_END',
'@protocol RCTBridgeModule <NSObject>',
'- (void)run;',
'@end',
'',
].join('\r\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'RCTBridgeModule.h');
expect(normalized).toHaveLength(source.length);
expect(normalized.split('\r\n')).toHaveLength(source.split('\r\n').length);
expect(normalized).toContain('#define RCT_EXTERN_C_BEGIN');
expect(normalized).toContain('@protocol RCTBridgeModule <NSObject>');
expect(normalized).toContain(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
expect(normalized).toContain(' '.repeat('RCT_EXTERN_C_END'.length));
expect(preprocessObjectiveCMacroMarkers(normalized, 'RCTBridgeModule.h')).toBe(normalized);
});
it('leaves non-marker syntax, strings, and comments untouched', () => {
const source = [
'void marker(void) {',
' RCT_EXTERN_C_BEGIN',
'}',
'RCT_EXTERN_C_END()',
'RCT_EXTERN_C_END;',
'#define RCT_EXTERN_C_END',
'#define RCT_MARKER_SEQUENCE \\',
'RCT_EXTERN_C_END',
'const char *value = "RCT_EXTERN_C_END";',
'// RCT_EXTERN_C_END',
'/*',
'RCT_EXTERN_C_END',
'*/',
'',
].join('\n');
expect(preprocessObjectiveCMacroMarkers(source, 'Example.m')).toBe(source);
});
it('does not rewrite markers inside a continued line comment', () => {
const source = [
'// The following token remains part of this comment \\',
'RCT_EXTERN_C_END',
'RCT_EXTERN_C_BEGIN',
'',
].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'CommentedMarker.h');
expect(normalized).toHaveLength(source.length);
expect(normalized).toContain('RCT_EXTERN_C_END');
expect(normalized.split('\n')[2]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
});
it('recognizes form feed and vertical tab as preprocessing whitespace', () => {
const source = [
'\f#define RCT_EXTERN_C_BEGIN',
'\v#define RCT_EXTERN_C_END',
'\fRCT_EXTERN_C_BEGIN',
'\vRCT_EXTERN_C_END',
'@protocol RCTBridgeModule <NSObject>',
'@end',
'',
].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'WhitespaceMarkers.h');
expect(normalized).toHaveLength(source.length);
expect(normalized).toContain('\f#define RCT_EXTERN_C_BEGIN');
expect(normalized).toContain('\v#define RCT_EXTERN_C_END');
expect(normalized.split('\n')[2]).toBe(' '.repeat('\fRCT_EXTERN_C_BEGIN'.length));
expect(normalized.split('\n')[3]).toBe(' '.repeat('\vRCT_EXTERN_C_END'.length));
expect(normalized).toContain('@protocol RCTBridgeModule <NSObject>');
});
it('preserves directives preceded by comments', () => {
const source = [
'/**/ #define RCT_MARKER_SEQUENCE \\',
'RCT_EXTERN_C_END',
'/* leading comment',
' */ #define RCT_SECOND_SEQUENCE \\',
'RCT_EXTERN_C_BEGIN',
'',
].join('\n');
expect(preprocessObjectiveCMacroMarkers(source, 'CommentDirective.h')).toBe(source);
});
it('does not rewrite markers inside a multiline function-like macro invocation', () => {
const source = ['SOME_MACRO(', 'RCT_EXTERN_C_END', ')', 'RCT_EXTERN_C_BEGIN', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'MultilineMacro.m');
expect(normalized.split('\n')[1]).toBe('RCT_EXTERN_C_END');
expect(normalized.split('\n')[3]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
});
it('does not treat directive replacement braces as file-scope nesting', () => {
const source = ['#define WRAP {', 'RCT_EXTERN_C_BEGIN', 'RCT_EXTERN_C_END', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'DirectiveBrace.h');
expect(normalized.split('\n')[0]).toBe('#define WRAP {');
expect(normalized.split('\n')[1]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
expect(normalized.split('\n')[2]).toBe(' '.repeat('RCT_EXTERN_C_END'.length));
});
it('does not rewrite markers inside a block comment opened on a directive line', () => {
const source = ['#define X /*', 'RCT_EXTERN_C_BEGIN', '*/', 'RCT_EXTERN_C_END', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'DirectiveComment.h');
expect(normalized.split('\n')[1]).toBe('RCT_EXTERN_C_BEGIN');
expect(normalized.split('\n')[3]).toBe(' '.repeat('RCT_EXTERN_C_END'.length));
});
it('requires a valid identifier start for a bare marker', () => {
const source = ['123_RCT_EXTERN_C_END', 'RCT_EXTERN_C_END', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'NumericMarker.h');
expect(normalized.split('\n')[0]).toBe('123_RCT_EXTERN_C_END');
expect(normalized.split('\n')[1]).toBe(' '.repeat('RCT_EXTERN_C_END'.length));
});
it('does not rewrite an object-like macro continued from a spliced statement', () => {
const source = ['int value = \\', 'FEATURE_VALUE', ';', 'RCT_EXTERN_C_BEGIN', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'SplicedStatement.m');
expect(normalized.split('\n')[1]).toBe('FEATURE_VALUE');
expect(normalized.split('\n')[3]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
});
it('tracks declarations that follow a leading block comment', () => {
const source = [
'/* documentation */ @interface Widget',
'DECLARE_WIDGET_MEMBERS',
'@end',
'RCT_EXTERN_C_BEGIN',
'',
].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'DocumentedWidget.h');
const lines = normalized.split('\n');
expect(lines[1]).toBe('DECLARE_WIDGET_MEMBERS');
expect(lines[3]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
});
it('tracks a declaration that follows a multiline block comment close', () => {
const source = ['/*', '*/ @interface Widget', 'DECLARE_WIDGET_MEMBERS', '@end', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'MultilineCommentWidget.h');
const lines = normalized.split('\n');
expect(lines[2]).toBe('DECLARE_WIDGET_MEMBERS');
});
it('elides a marker after a directive that ends in a continued line comment', () => {
const source = ['#define X // \\', 'text', 'RCT_EXTERN_C_BEGIN', ''].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'DirectiveLineComment.h');
expect(normalized.split('\n')[1]).toBe('text');
expect(normalized.split('\n')[2]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
});
it('does not rewrite bare macros inside @interface / @protocol / @implementation', () => {
const source = [
'@interface Widget',
'DECLARE_WIDGET_MEMBERS',
'@end',
'@protocol WidgetDelegate',
'DECLARE_WIDGET_DELEGATE',
'@end',
'@implementation Widget',
'DECLARE_WIDGET_IVARS',
'@end',
'RCT_EXTERN_C_BEGIN',
'',
].join('\n');
const normalized = preprocessObjectiveCMacroMarkers(source, 'Widget.h');
const lines = normalized.split('\n');
expect(lines[1]).toBe('DECLARE_WIDGET_MEMBERS');
expect(lines[4]).toBe('DECLARE_WIDGET_DELEGATE');
expect(lines[7]).toBe('DECLARE_WIDGET_IVARS');
expect(lines[9]).toBe(' '.repeat('RCT_EXTERN_C_BEGIN'.length));
});
});