feat(extraction): add Objective-C language support (#165)
Adds tree-sitter-objc extractor for `.m`/`.mm` files and `.h` files that content-sniff as Objective-C (`@interface`/`@implementation`/`@protocol`/`@synthesize`). Extraction covers: - `@interface` / `@implementation` (deduplicated into a single class node) - `@protocol` (as `protocol` nodes via new `interfaceKind` config) - Methods with full multi-part selectors (`doThing:with:`, not just `doThing`), including `+`/`-` static distinction - `@property` declarations - Inheritance (`extends`) and protocol conformance (`implements`) - C-style `function_definition` and `#import` (both `<system>` and `"local"` forms) - Call edges from both `call_expression` and `message_expression`, with `self`/`super` skipped on qualified callee names Two new generic hooks on `LanguageExtractor` (`resolveName`, `extractPropertyName`) handle the cases where the default name walk doesn't fit; usable by future languages with similar shape. Import resolver tries `.h`, `.m`, `.mm` for `objc` imports. Validated on AFNetworking (84 files, 100% file coverage), RestKit (282 files, 99.6%), and Texture (926 files, 100%, heavy `.mm` content) — multi-keyword selectors preserved up to 7 parts, no parse failures on ObjC++. Known limitations (disclosed in README): - Categories produce duplicate class nodes (one per category file) - Chained/nested message sends record only the innermost method - `[Class alloc]` patterns don't emit `instantiates` edges - `@protocol Foo <Bar>` refinement lists not yet wired to `implements` - Heavy C++ in `.mm` files may parse incompletely under the ObjC grammar
This commit is contained in:
@@ -37,6 +37,7 @@ const WASM_GRAMMAR_FILES: Record<GrammarLanguage, string> = {
|
||||
scala: 'tree-sitter-scala.wasm',
|
||||
lua: 'tree-sitter-lua.wasm',
|
||||
luau: 'tree-sitter-luau.wasm',
|
||||
objc: 'tree-sitter-objc.wasm',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -92,6 +93,8 @@ export const EXTENSION_MAP: Record<string, Language> = {
|
||||
'.sc': 'scala',
|
||||
'.lua': 'lua',
|
||||
'.luau': 'luau',
|
||||
'.m': 'objc',
|
||||
'.mm': 'objc',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -228,9 +231,10 @@ export function detectLanguage(filePath: string, source?: string): Language {
|
||||
const ext = filePath.substring(filePath.lastIndexOf('.')).toLowerCase();
|
||||
const lang = EXTENSION_MAP[ext] || 'unknown';
|
||||
|
||||
// .h files could be C or C++ — check source content for C++ features
|
||||
// .h files could be C, C++, or Objective-C — check source content
|
||||
if (lang === 'c' && ext === '.h' && source) {
|
||||
if (looksLikeCpp(source)) return 'cpp';
|
||||
if (looksLikeObjc(source)) return 'objc';
|
||||
}
|
||||
|
||||
return lang;
|
||||
@@ -245,6 +249,14 @@ function looksLikeCpp(source: string): boolean {
|
||||
return /\bnamespace\b|\bclass\s+\w+\s*[:{]|\btemplate\s*<|\b(?:public|private|protected)\s*:|\bvirtual\b|\busing\s+(?:namespace\b|\w+\s*=)/.test(sample);
|
||||
}
|
||||
|
||||
/**
|
||||
* Heuristic: does a .h file contain Objective-C constructs?
|
||||
*/
|
||||
function looksLikeObjc(source: string): boolean {
|
||||
const sample = source.substring(0, 8192);
|
||||
return /@(?:interface|implementation|protocol|synthesize)\b/.test(sample);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a language is supported (has a grammar defined).
|
||||
* Returns true if the grammar exists, even if not yet loaded.
|
||||
@@ -342,6 +354,7 @@ export function getLanguageDisplayName(language: Language): string {
|
||||
scala: 'Scala',
|
||||
lua: 'Lua',
|
||||
luau: 'Luau',
|
||||
objc: 'Objective-C',
|
||||
yaml: 'YAML',
|
||||
twig: 'Twig',
|
||||
unknown: 'Unknown',
|
||||
|
||||
Reference in New Issue
Block a user