From 5808b5bff57aaa12d357d33f5dca9307d61f6300 Mon Sep 17 00:00:00 2001 From: "xue.yunqiang" Date: Fri, 29 May 2026 01:44:17 +0800 Subject: [PATCH] feat: add Objective-C ingestion support --- gitnexus-shared/src/language-detection.ts | 2 + gitnexus-shared/src/languages.ts | 2 + .../language-classification.ts | 1 + gitnexus/package-lock.json | 17 +++ gitnexus/package.json | 1 + .../src/core/ingestion/languages/index.ts | 2 + gitnexus/src/core/ingestion/languages/objc.ts | 106 ++++++++++++++++++ .../src/core/ingestion/tree-sitter-queries.ts | 77 +++++++++++++ .../src/core/tree-sitter/parser-loader.ts | 6 + gitnexus/test/fixtures/sample-code/simple.m | 34 ++++++ .../integration/tree-sitter-languages.test.ts | 70 ++++++++++++ 11 files changed, 318 insertions(+) create mode 100644 gitnexus/src/core/ingestion/languages/objc.ts create mode 100644 gitnexus/test/fixtures/sample-code/simple.m diff --git a/gitnexus-shared/src/language-detection.ts b/gitnexus-shared/src/language-detection.ts index f9073b8c0..969816257 100644 --- a/gitnexus-shared/src/language-detection.ts +++ b/gitnexus-shared/src/language-detection.ts @@ -43,6 +43,7 @@ const EXTENSION_MAP: Record = { [SupportedLanguages.Dart]: ['.dart'], [SupportedLanguages.Vue]: ['.vue'], [SupportedLanguages.Cobol]: ['.cbl', '.cob', '.cpy', '.cobol'], + [SupportedLanguages.ObjectiveC]: ['.m', '.mm'], } satisfies Record; // Ensure exhaustiveness /** Pre-built reverse lookup: extension → language (built once at module load). */ @@ -111,6 +112,7 @@ const SYNTAX_MAP: Record = { [SupportedLanguages.Dart]: 'dart', [SupportedLanguages.Vue]: 'typescript', [SupportedLanguages.Cobol]: 'cobol', + [SupportedLanguages.ObjectiveC]: 'objc', } satisfies Record; // Ensure exhaustiveness /** Non-code file extensions → Prism-compatible syntax identifiers */ diff --git a/gitnexus-shared/src/languages.ts b/gitnexus-shared/src/languages.ts index 29edb8d1e..268e1b0ea 100644 --- a/gitnexus-shared/src/languages.ts +++ b/gitnexus-shared/src/languages.ts @@ -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', } diff --git a/gitnexus-shared/src/scope-resolution/language-classification.ts b/gitnexus-shared/src/scope-resolution/language-classification.ts index 10c556cda..c38a85592 100644 --- a/gitnexus-shared/src/scope-resolution/language-classification.ts +++ b/gitnexus-shared/src/scope-resolution/language-classification.ts @@ -39,6 +39,7 @@ export const LanguageClassifications: Readonly; /** Get provider by language enum (always succeeds for SupportedLanguages). */ diff --git a/gitnexus/src/core/ingestion/languages/objc.ts b/gitnexus/src/core/ingestion/languages/objc.ts new file mode 100644 index 000000000..dcb548c73 --- /dev/null +++ b/gitnexus/src/core/ingestion/languages/objc.ts @@ -0,0 +1,106 @@ +/** + * 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 { createImportResolver } from '../import-resolvers/resolver-factory.js'; +import { createStandardStrategy } 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'; +import { createCallExtractor } from '../call-extractors/generic.js'; +import { cCallConfig } from '../call-extractors/configs/c-cpp.js'; +import { createHeritageExtractor } from '../heritage-extractors/generic.js'; + +const OBJC_BUILT_INS: ReadonlySet = 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', +]); + +const stripObjcNullabilityMacros = (sourceText: string): string => + sourceText + .replace(/NS_ASSUME_NONNULL_BEGIN/g, (m) => ' '.repeat(m.length)) + .replace(/NS_ASSUME_NONNULL_END/g, (m) => ' '.repeat(m.length)); + +export const objcProvider = defineLanguage({ + id: SupportedLanguages.ObjectiveC, + extensions: ['.m', '.mm'], + treeSitterQueries: OBJ_C_QUERIES, + preprocessSource: stripObjcNullabilityMacros, + typeConfig: cCppConfig, + exportChecker: cCppExportChecker, + importResolver: createImportResolver({ + language: SupportedLanguages.ObjectiveC, + strategies: [createStandardStrategy(SupportedLanguages.C)], + }), + importSemantics: 'wildcard-transitive', + heritageDefaultEdge: 'EXTENDS', + callExtractor: createCallExtractor(cCallConfig), + fieldExtractor: createFieldExtractor(cFieldConfig), + methodExtractor: createMethodExtractor(cMethodConfig), + heritageExtractor: createHeritageExtractor(SupportedLanguages.ObjectiveC), + builtInNames: OBJC_BUILT_INS, +}); diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index f27fcc8ce..d4c4e06ee 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -1242,6 +1242,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 ) +; . 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 (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 @@ -1543,4 +1619,5 @@ export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.Dart]: DART_QUERIES, [SupportedLanguages.Vue]: TYPESCRIPT_QUERIES, // Vue