mirror of
https://github.com/abhigyanpatwari/GitNexus.git
synced 2026-09-12 23:02:45 +00:00
feat: add Objective-C ingestion support
This commit is contained in:
parent
cb772b9e29
commit
9a33fd82c7
14 changed files with 359 additions and 6 deletions
|
|
@ -43,6 +43,7 @@ const EXTENSION_MAP: Record<SupportedLanguages, readonly string[]> = {
|
|||
[SupportedLanguages.Dart]: ['.dart'],
|
||||
[SupportedLanguages.Vue]: ['.vue'],
|
||||
[SupportedLanguages.Cobol]: ['.cbl', '.cob', '.cpy', '.cobol'],
|
||||
[SupportedLanguages.ObjectiveC]: ['.m', '.mm'],
|
||||
} satisfies Record<SupportedLanguages, readonly string[]>; // Ensure exhaustiveness
|
||||
|
||||
/** Pre-built reverse lookup: extension → language (built once at module load). */
|
||||
|
|
@ -101,6 +102,7 @@ const SYNTAX_MAP: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.Dart]: 'dart',
|
||||
[SupportedLanguages.Vue]: 'typescript',
|
||||
[SupportedLanguages.Cobol]: 'cobol',
|
||||
[SupportedLanguages.ObjectiveC]: 'objc',
|
||||
} satisfies Record<SupportedLanguages, string>; // Ensure exhaustiveness
|
||||
|
||||
/** Non-code file extensions → Prism-compatible syntax identifiers */
|
||||
|
|
|
|||
|
|
@ -22,4 +22,6 @@ export enum SupportedLanguages {
|
|||
Vue = 'vue',
|
||||
/** Standalone regex processor — no tree-sitter, no LanguageProvider. */
|
||||
Cobol = 'cobol',
|
||||
/** Objective-C: uses tree-sitter-objc for .m/.mm implementation files. */
|
||||
ObjectiveC = 'objectivec',
|
||||
}
|
||||
|
|
|
|||
49
gitnexus/package-lock.json
generated
49
gitnexus/package-lock.json
generated
|
|
@ -33,6 +33,7 @@
|
|||
"tree-sitter-go": "^0.23.0",
|
||||
"tree-sitter-java": "^0.23.5",
|
||||
"tree-sitter-javascript": "^0.23.0",
|
||||
"tree-sitter-objc": "^3.0.2",
|
||||
"tree-sitter-php": "^0.23.0",
|
||||
"tree-sitter-python": "0.23.4",
|
||||
"tree-sitter-ruby": "^0.23.1",
|
||||
|
|
@ -5258,6 +5259,54 @@
|
|||
"license": "MIT",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/tree-sitter-objc": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-objc/-/tree-sitter-objc-3.0.2.tgz",
|
||||
"integrity": "sha512-Hs0ohmx1u5M+0K7efoW+dv/corhBsfjftfIYLtp7dSGeJ+Zj4c33tDIboBYLs6qijRlz6wtHFxa0YX+FibLulA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4",
|
||||
"tree-sitter-c": "^0.23.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.22.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-objc/node_modules/node-addon-api": {
|
||||
"version": "8.7.0",
|
||||
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.7.0.tgz",
|
||||
"integrity": "sha512-9MdFxmkKaOYVTV+XVRG8ArDwwQ77XIgIPyKASB1k3JPq3M8fGQQQE3YpMOrKm6g//Ktx8ivZr8xo1Qmtqub+GA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^18 || ^20 || >= 21"
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-objc/node_modules/tree-sitter-c": {
|
||||
"version": "0.23.6",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-c/-/tree-sitter-c-0.23.6.tgz",
|
||||
"integrity": "sha512-0dxXKznVyUA0s6PjNolJNs2yF87O5aL538A/eR6njA5oqX3C3vH4vnx3QdOKwuUdpKEcFdHuiDpRKLLCA/tjvQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-addon-api": "^8.3.0",
|
||||
"node-gyp-build": "^4.8.4"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"tree-sitter": "^0.22.1"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"tree-sitter": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/tree-sitter-php": {
|
||||
"version": "0.23.12",
|
||||
"resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.23.12.tgz",
|
||||
|
|
|
|||
|
|
@ -75,6 +75,7 @@
|
|||
"tree-sitter-go": "^0.23.0",
|
||||
"tree-sitter-java": "^0.23.5",
|
||||
"tree-sitter-javascript": "^0.23.0",
|
||||
"tree-sitter-objc": "^3.0.2",
|
||||
"tree-sitter-php": "^0.23.0",
|
||||
"tree-sitter-python": "0.23.4",
|
||||
"tree-sitter-ruby": "^0.23.1",
|
||||
|
|
@ -88,7 +89,6 @@
|
|||
"tree-sitter-swift": "^0.6.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"gitnexus-shared": "file:../gitnexus-shared",
|
||||
"@types/cli-progress": "^3.11.6",
|
||||
"@types/cors": "^2.8.17",
|
||||
"@types/express": "^4.17.21",
|
||||
|
|
@ -96,6 +96,7 @@
|
|||
"@types/node": "^20.0.0",
|
||||
"@types/uuid": "^10.0.0",
|
||||
"@vitest/coverage-v8": "^4.0.18",
|
||||
"gitnexus-shared": "file:../gitnexus-shared",
|
||||
"tsx": "^4.0.0",
|
||||
"typescript": "^5.4.5",
|
||||
"vitest": "^4.0.18"
|
||||
|
|
|
|||
|
|
@ -228,6 +228,7 @@ export const ENTRY_POINT_PATTERNS = {
|
|||
],
|
||||
[SupportedLanguages.Vue]: [], // Vue uses TypeScript queries — entry points handled via TS patterns
|
||||
[SupportedLanguages.Cobol]: [], // Standalone regex processor — no tree-sitter entry points
|
||||
[SupportedLanguages.ObjectiveC]: [], // ObjC has no special entry point patterns
|
||||
} satisfies Record<SupportedLanguages, RegExp[]>;
|
||||
|
||||
/** Pre-computed merged patterns (universal + language-specific) to avoid per-call array allocation. */
|
||||
|
|
|
|||
|
|
@ -893,6 +893,7 @@ export const AST_FRAMEWORK_PATTERNS_BY_LANGUAGE = {
|
|||
],
|
||||
[SupportedLanguages.Vue]: [], // Vue uses TypeScript AST framework detection
|
||||
[SupportedLanguages.Cobol]: [], // Standalone regex processor — no AST framework patterns
|
||||
[SupportedLanguages.ObjectiveC]: [], // ObjC has no dedicated AST framework patterns
|
||||
} satisfies Record<SupportedLanguages, AstFrameworkPatternConfig[]>;
|
||||
|
||||
/** Pre-lowercased patterns for O(1) pattern matching at runtime */
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@ import { swiftProvider } from './swift.js';
|
|||
import { dartProvider } from './dart.js';
|
||||
import { vueProvider } from './vue.js';
|
||||
import { cobolProvider } from './cobol.js';
|
||||
import { objcProvider } from './objc.js';
|
||||
|
||||
export const providers = {
|
||||
[SupportedLanguages.JavaScript]: javascriptProvider,
|
||||
|
|
@ -43,6 +44,7 @@ export const providers = {
|
|||
[SupportedLanguages.Dart]: dartProvider,
|
||||
[SupportedLanguages.Vue]: vueProvider,
|
||||
[SupportedLanguages.Cobol]: cobolProvider,
|
||||
[SupportedLanguages.ObjectiveC]: objcProvider,
|
||||
} satisfies Record<SupportedLanguages, LanguageProvider>;
|
||||
|
||||
/** Get provider by language enum (always succeeds for SupportedLanguages). */
|
||||
|
|
|
|||
91
gitnexus/src/core/ingestion/languages/objc.ts
Normal file
91
gitnexus/src/core/ingestion/languages/objc.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Objective-C Language Provider
|
||||
*
|
||||
* Assembles all Objective-C-specific ingestion capabilities into a single
|
||||
* LanguageProvider, following the Strategy pattern used by the pipeline.
|
||||
*
|
||||
* Key Objective-C traits:
|
||||
* - importSemantics: 'wildcard' (ObjC imports entire modules via #import)
|
||||
* - heritageDefaultEdge: 'EXTENDS' (single class inheritance, multiple protocol adoption)
|
||||
* - ObjC uses the same type config and export checker as C++ since they share
|
||||
* similar declaration patterns for functions and global state.
|
||||
* - message_expression nodes are captured as CALLS (e.g., [self doSomething])
|
||||
* - class_interface / class_implementation / protocol_declaration captured as definitions.
|
||||
*/
|
||||
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { defineLanguage } from '../language-provider.js';
|
||||
import { typeConfig as cCppConfig } from '../type-extractors/c-cpp.js';
|
||||
import { cCppExportChecker } from '../export-detection.js';
|
||||
import { resolveCImport } from '../import-resolvers/standard.js';
|
||||
import { OBJ_C_QUERIES } from '../tree-sitter-queries.js';
|
||||
import { createFieldExtractor } from '../field-extractors/generic.js';
|
||||
import { cConfig as cFieldConfig } from '../field-extractors/configs/c-cpp.js';
|
||||
import { createMethodExtractor } from '../method-extractors/generic.js';
|
||||
import { cMethodConfig } from '../method-extractors/configs/c-cpp.js';
|
||||
|
||||
const OBJC_BUILT_INS: ReadonlySet<string> = new Set([
|
||||
'NSLog',
|
||||
'NSLogv',
|
||||
'dispatch_async',
|
||||
'dispatch_sync',
|
||||
'dispatch_once',
|
||||
'dispatch_after',
|
||||
'dispatch_group_async',
|
||||
'objc_getClass',
|
||||
'objc_getMetaClass',
|
||||
'objc_msgSend',
|
||||
'objc_msgSendSuper',
|
||||
'objc_msgSend_stret',
|
||||
'objc_msgSendSuper_stret',
|
||||
'sel_registerName',
|
||||
'protocol_getName',
|
||||
'class_getName',
|
||||
'class_getSuperclass',
|
||||
'object_getClass',
|
||||
'object_getInstanceSize',
|
||||
'class_addMethod',
|
||||
'class_replaceMethod',
|
||||
'class_getInstanceMethod',
|
||||
'class_getClassMethod',
|
||||
'method_exchangeImplementations',
|
||||
'imp_implementationWithBlock',
|
||||
'imp_getBlock',
|
||||
'imp_removeBlock',
|
||||
'objc_setAssociatedObject',
|
||||
'objc_getAssociatedObject',
|
||||
'objc_removeAssociatedObjects',
|
||||
'class_copyPropertyList',
|
||||
'class_copyMethodList',
|
||||
'class_copyIvarList',
|
||||
'property_getName',
|
||||
'ivar_getName',
|
||||
'ivar_getTypeEncoding',
|
||||
'method_getName',
|
||||
'method_getTypeEncoding',
|
||||
'method_getReturnType',
|
||||
'method_getNumberOfArguments',
|
||||
'method_getArgumentType',
|
||||
'class_isMetaClass',
|
||||
'object_isClass',
|
||||
'class_respondsToSelector',
|
||||
'instancesRespondToSelector',
|
||||
'conformsToProtocol',
|
||||
'CFRetain',
|
||||
'CFRelease',
|
||||
'CFAutorelease',
|
||||
]);
|
||||
|
||||
export const objcProvider = defineLanguage({
|
||||
id: SupportedLanguages.ObjectiveC,
|
||||
extensions: ['.m', '.mm'],
|
||||
treeSitterQueries: OBJ_C_QUERIES,
|
||||
typeConfig: cCppConfig,
|
||||
exportChecker: cCppExportChecker,
|
||||
importResolver: resolveCImport,
|
||||
importSemantics: 'wildcard',
|
||||
heritageDefaultEdge: 'EXTENDS',
|
||||
fieldExtractor: createFieldExtractor(cFieldConfig),
|
||||
methodExtractor: createMethodExtractor(cMethodConfig),
|
||||
builtInNames: OBJC_BUILT_INS,
|
||||
});
|
||||
|
|
@ -318,6 +318,13 @@ const processParsingSequential = async (
|
|||
isVueSetup = extracted.isSetup;
|
||||
}
|
||||
|
||||
// ObjC: strip NS_ASSUME_NONNULL_* macros that break class_interface/protocol parsing
|
||||
if (language === SupportedLanguages.ObjectiveC) {
|
||||
parseContent = parseContent
|
||||
.replace(/NS_ASSUME_NONNULL_BEGIN\s*/g, '')
|
||||
.replace(/NS_ASSUME_NONNULL_END\s*/g, '');
|
||||
}
|
||||
|
||||
try {
|
||||
await loadLanguage(language, file.path);
|
||||
} catch {
|
||||
|
|
|
|||
|
|
@ -950,6 +950,82 @@ export const KOTLIN_QUERIES = `
|
|||
|
||||
`;
|
||||
|
||||
// Objective-C queries - works with tree-sitter-objc
|
||||
export const OBJ_C_QUERIES = `
|
||||
; Class declarations - use anchor to capture only the first identifier (class name)
|
||||
; . anchor ensures we get the FIRST identifier (class name), not superclass or others
|
||||
(class_interface
|
||||
.
|
||||
(identifier) @name) @definition.class
|
||||
|
||||
; Class implementations - @implementation MyClass (methods only, no heritage)
|
||||
(class_implementation
|
||||
.
|
||||
(identifier) @name) @definition.class
|
||||
|
||||
; Protocol declarations - anchor to first identifier (protocol name)
|
||||
(protocol_declaration
|
||||
.
|
||||
(identifier) @name) @definition.interface
|
||||
|
||||
; Categories - class extension (anonymous category = extension)
|
||||
(class_interface
|
||||
.
|
||||
(identifier) @name
|
||||
category: (identifier) @category) @definition.class
|
||||
|
||||
; Heritage - superclass (e.g., @interface Foo : Bar)
|
||||
; . matches the FIRST identifier (Foo = class name); then explicit superclass identifier
|
||||
(class_interface
|
||||
.
|
||||
(identifier) @heritage.class
|
||||
(identifier) @heritage.extends) @heritage
|
||||
|
||||
; Heritage - protocol adoptions (e.g., @interface Foo <Proto1, Proto2>)
|
||||
; . anchors to class name identifier; parameterized_arguments follows as sibling
|
||||
(class_interface
|
||||
.
|
||||
(identifier) @heritage.class
|
||||
(parameterized_arguments
|
||||
(type_name
|
||||
(type_identifier) @heritage.implements))) @heritage.impl
|
||||
|
||||
; Method definitions - capture only the first identifier after +/- and method_type
|
||||
; Use ["+" "-"] to match both class (+) and instance (-) methods
|
||||
(method_declaration
|
||||
["+" "-"]
|
||||
(method_type)
|
||||
(identifier) @name) @definition.method
|
||||
|
||||
; Calls - ObjC message expressions (e.g., [self doSomething], [calc add:5 to:3])
|
||||
; Use method: field to capture the method selector name(s)
|
||||
(message_expression
|
||||
method: (identifier) @call.name) @call
|
||||
|
||||
; Calls - C-style function calls (e.g., NSLog(...))
|
||||
(call_expression
|
||||
(identifier) @call.name) @call
|
||||
|
||||
; Property declarations (e.g., @property (nonatomic, strong) NSString *name)
|
||||
(property_declaration
|
||||
(struct_declaration
|
||||
(_)
|
||||
(struct_declarator
|
||||
(pointer_declarator
|
||||
declarator: (identifier) @name)))) @definition.property
|
||||
|
||||
; Instance variable declarations inside @interface { } block
|
||||
(instance_variable
|
||||
(struct_declaration
|
||||
(_)
|
||||
(struct_declarator
|
||||
(pointer_declarator
|
||||
declarator: (identifier) @name)))) @definition.property
|
||||
|
||||
; Imports - #import "path" or #import <path> (preproc_include is used by tree-sitter-objc for #import)
|
||||
(preproc_include path: (_) @import.source) @import
|
||||
`;
|
||||
|
||||
// Swift queries - works with tree-sitter-swift
|
||||
export const SWIFT_QUERIES = `
|
||||
; Classes
|
||||
|
|
@ -1193,4 +1269,5 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
|
|||
[SupportedLanguages.Dart]: DART_QUERIES,
|
||||
[SupportedLanguages.Vue]: TYPESCRIPT_QUERIES, // Vue <script> blocks are parsed as TypeScript
|
||||
[SupportedLanguages.Cobol]: '', // Standalone regex processor — no tree-sitter queries
|
||||
[SupportedLanguages.ObjectiveC]: OBJ_C_QUERIES,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import Go from 'tree-sitter-go';
|
|||
import Rust from 'tree-sitter-rust';
|
||||
import PHP from 'tree-sitter-php';
|
||||
import Ruby from 'tree-sitter-ruby';
|
||||
import ObjC from 'tree-sitter-objc';
|
||||
import { createRequire } from 'node:module';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
import { getProvider } from '../languages/index.js';
|
||||
|
|
@ -20,21 +21,24 @@ import { SymbolTable } from '../symbol-table.js';
|
|||
/** Language grammar type accepted by Parser.setLanguage(). */
|
||||
type TreeSitterLanguage = Parameters<typeof Parser.prototype.setLanguage>[0];
|
||||
|
||||
/** Extended language type that includes grammar packages with optional name property */
|
||||
type GrammarLanguage = TreeSitterLanguage | { language: unknown; nodeTypeInfo?: unknown[] };
|
||||
|
||||
// tree-sitter-swift is an optionalDependency — may not be installed
|
||||
const _require = createRequire(import.meta.url);
|
||||
let Swift: TreeSitterLanguage | null = null;
|
||||
let Swift: GrammarLanguage = null;
|
||||
try {
|
||||
Swift = _require('tree-sitter-swift');
|
||||
} catch {}
|
||||
|
||||
// tree-sitter-dart is an optionalDependency — may not be installed
|
||||
let Dart: TreeSitterLanguage | null = null;
|
||||
let Dart: GrammarLanguage = null;
|
||||
try {
|
||||
Dart = _require('tree-sitter-dart');
|
||||
} catch {}
|
||||
|
||||
// tree-sitter-kotlin is an optionalDependency — may not be installed
|
||||
let Kotlin: TreeSitterLanguage | null = null;
|
||||
let Kotlin: GrammarLanguage = null;
|
||||
try {
|
||||
Kotlin = _require('tree-sitter-kotlin');
|
||||
} catch {}
|
||||
|
|
@ -277,7 +281,7 @@ type WorkerIncomingMessage =
|
|||
|
||||
const parser = new Parser();
|
||||
|
||||
const languageMap: Record<string, TreeSitterLanguage> = {
|
||||
const languageMap: Record<string, GrammarLanguage> = {
|
||||
[SupportedLanguages.JavaScript]: JavaScript,
|
||||
[SupportedLanguages.TypeScript]: TypeScript.typescript,
|
||||
[`${SupportedLanguages.TypeScript}:tsx`]: TypeScript.tsx,
|
||||
|
|
@ -294,6 +298,7 @@ const languageMap: Record<string, TreeSitterLanguage> = {
|
|||
[SupportedLanguages.Vue]: TypeScript.typescript,
|
||||
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
||||
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
||||
[SupportedLanguages.ObjectiveC]: ObjC,
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
@ -317,7 +322,7 @@ const setLanguage = (language: SupportedLanguages, filePath: string): void => {
|
|||
: language;
|
||||
const lang = languageMap[key];
|
||||
if (!lang) throw new Error(`Unsupported language: ${language}`);
|
||||
parser.setLanguage(lang);
|
||||
parser.setLanguage(lang as TreeSitterLanguage);
|
||||
};
|
||||
|
||||
// ============================================================================
|
||||
|
|
@ -1313,6 +1318,15 @@ const processFileGroup = (
|
|||
isVueSetup = extracted.isSetup;
|
||||
}
|
||||
|
||||
// ObjC: strip NS_ASSUME_NONNULL_* macros that break class_interface/protocol parsing
|
||||
// tree-sitter-objc grammar doesn't handle these Apple nullability宏, causing
|
||||
// @interface declarations after NS_ASSUME_NONNULL_BEGIN to become ERROR nodes
|
||||
if (language === SupportedLanguages.ObjectiveC) {
|
||||
parseContent = parseContent
|
||||
.replace(/NS_ASSUME_NONNULL_BEGIN\s*/g, '')
|
||||
.replace(/NS_ASSUME_NONNULL_END\s*/g, '');
|
||||
}
|
||||
|
||||
clearCaches(); // Reset memoization before each new file
|
||||
|
||||
let tree;
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ import Go from 'tree-sitter-go';
|
|||
import Rust from 'tree-sitter-rust';
|
||||
import PHP from 'tree-sitter-php';
|
||||
import Ruby from 'tree-sitter-ruby';
|
||||
import ObjC from 'tree-sitter-objc';
|
||||
import { createRequire } from 'node:module';
|
||||
import { SupportedLanguages } from 'gitnexus-shared';
|
||||
|
||||
|
|
@ -49,6 +50,7 @@ const languageMap: Record<string, any> = {
|
|||
[SupportedLanguages.Vue]: TypeScript.typescript,
|
||||
...(Dart ? { [SupportedLanguages.Dart]: Dart } : {}),
|
||||
...(Swift ? { [SupportedLanguages.Swift]: Swift } : {}),
|
||||
[SupportedLanguages.ObjectiveC]: ObjC,
|
||||
};
|
||||
|
||||
export const isLanguageAvailable = (language: SupportedLanguages): boolean =>
|
||||
|
|
|
|||
34
gitnexus/test/fixtures/sample-code/simple.m
vendored
Normal file
34
gitnexus/test/fixtures/sample-code/simple.m
vendored
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
#import <Foundation/Foundation.h>
|
||||
@interface Calculator : NSObject
|
||||
@property (nonatomic, strong) NSString *name;
|
||||
- (void)reset;
|
||||
- (NSInteger)add:(NSInteger)a to:(NSInteger)b;
|
||||
- (NSInteger)multiply:(NSInteger)a with:(NSInteger)b;
|
||||
+ (instancetype)sharedCalculator;
|
||||
@end
|
||||
|
||||
@implementation Calculator
|
||||
- (void)reset {
|
||||
NSLog(@"reset");
|
||||
}
|
||||
|
||||
- (NSInteger)add:(NSInteger)a to:(NSInteger)b {
|
||||
return a + b;
|
||||
}
|
||||
|
||||
- (NSInteger)multiply:(NSInteger)a with:(NSInteger)b {
|
||||
return a * b;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedCalculator {
|
||||
Calculator *calc = [Calculator alloc];
|
||||
return [calc init];
|
||||
}
|
||||
@end
|
||||
|
||||
void runCalculatorSample(void) {
|
||||
Calculator *calc = [Calculator sharedCalculator];
|
||||
[calc reset];
|
||||
[calc add:5 to:3];
|
||||
[calc multiply:2 with:4];
|
||||
}
|
||||
|
|
@ -378,6 +378,76 @@ describe('Tree-sitter multi-language parsing', () => {
|
|||
});
|
||||
});
|
||||
|
||||
describe('Objective-C', () => {
|
||||
it('parses class, method declarations if tree-sitter-objc is available', async () => {
|
||||
try {
|
||||
await loadLanguage(SupportedLanguages.ObjectiveC, 'simple.m');
|
||||
} catch {
|
||||
// tree-sitter-objc not installed — skip
|
||||
return;
|
||||
}
|
||||
|
||||
const content = readFixture('simple.m');
|
||||
const provider = getProvider(SupportedLanguages.ObjectiveC);
|
||||
const { matches } = parseAndQuery(parser, content, provider.treeSitterQueries);
|
||||
const defs = extractDefinitions(matches);
|
||||
|
||||
expect(defs.length).toBeGreaterThan(0);
|
||||
const names = defs.map((d) => d.name);
|
||||
// Class names
|
||||
expect(names).toContain('Calculator');
|
||||
// Method names (keyword selectors captured as separate identifiers: add, to, multiply, with)
|
||||
expect(names).toContain('reset');
|
||||
expect(names).toContain('add');
|
||||
expect(names).toContain('to');
|
||||
expect(names).toContain('multiply');
|
||||
expect(names).toContain('with');
|
||||
expect(names).toContain('sharedCalculator');
|
||||
});
|
||||
|
||||
it('captures class interface and implementation', async () => {
|
||||
try {
|
||||
await loadLanguage(SupportedLanguages.ObjectiveC, 'simple.m');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = readFixture('simple.m');
|
||||
const provider = getProvider(SupportedLanguages.ObjectiveC);
|
||||
const { matches } = parseAndQuery(parser, content, provider.treeSitterQueries);
|
||||
const defs = extractDefinitions(matches);
|
||||
const defTypes = defs.map((d) => d.type);
|
||||
expect(defTypes).toContain('definition.class');
|
||||
});
|
||||
|
||||
it('captures ObjC message expressions as calls', async () => {
|
||||
try {
|
||||
await loadLanguage(SupportedLanguages.ObjectiveC, 'simple.m');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
const content = readFixture('simple.m');
|
||||
const provider = getProvider(SupportedLanguages.ObjectiveC);
|
||||
const { matches } = parseAndQuery(parser, content, provider.treeSitterQueries);
|
||||
|
||||
const calls: string[] = [];
|
||||
for (const match of matches) {
|
||||
for (const capture of match.captures) {
|
||||
if (capture.name === 'call.name') calls.push(capture.node.text);
|
||||
}
|
||||
}
|
||||
// ObjC message calls capture method names (selectors), not receivers
|
||||
// [calc reset] -> reset, [calc add:5 to:3] -> add, to, [Calculator sharedCalculator] -> sharedCalculator
|
||||
// C function calls like NSLog() are also captured
|
||||
expect(calls).toContain('reset');
|
||||
expect(calls).toContain('sharedCalculator');
|
||||
expect(calls).toContain('NSLog');
|
||||
expect(calls).toContain('alloc');
|
||||
expect(calls).toContain('init');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Swift', () => {
|
||||
it('parses class, struct, protocol, and function if tree-sitter-swift is available', async () => {
|
||||
try {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue