diff --git a/gitnexus/package-lock.json b/gitnexus/package-lock.json index b2f14dea1..66a291cb8 100644 --- a/gitnexus/package-lock.json +++ b/gitnexus/package-lock.json @@ -30,6 +30,7 @@ "tree-sitter-go": "^0.21.0", "tree-sitter-java": "^0.21.0", "tree-sitter-javascript": "^0.21.0", + "tree-sitter-kotlin": "^0.3.8", "tree-sitter-php": "^0.23.12", "tree-sitter-python": "^0.21.0", "tree-sitter-rust": "^0.21.0", @@ -4379,6 +4380,31 @@ "node": "^18 || ^20 || >= 21" } }, + "node_modules/tree-sitter-kotlin": { + "version": "0.3.8", + "resolved": "https://registry.npmjs.org/tree-sitter-kotlin/-/tree-sitter-kotlin-0.3.8.tgz", + "integrity": "sha512-A4obq6bjzmYrA+F0JLLoheFPcofFkctNaZSpnDd+GPn1SfVZLY4/GG4C0cYVBTOShuPBGGAOPLM1JWLZQV4m1g==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.1.0", + "node-gyp-build": "^4.8.0" + }, + "peerDependencies": { + "tree-sitter": "^0.21.0" + }, + "peerDependenciesMeta": { + "tree_sitter": { + "optional": true + } + } + }, + "node_modules/tree-sitter-kotlin/node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, "node_modules/tree-sitter-php": { "version": "0.23.12", "resolved": "https://registry.npmjs.org/tree-sitter-php/-/tree-sitter-php-0.23.12.tgz", diff --git a/gitnexus/package.json b/gitnexus/package.json index 04b20e88a..16de567bb 100644 --- a/gitnexus/package.json +++ b/gitnexus/package.json @@ -62,6 +62,7 @@ "tree-sitter-go": "^0.21.0", "tree-sitter-java": "^0.21.0", "tree-sitter-javascript": "^0.21.0", + "tree-sitter-kotlin": "^0.3.8", "tree-sitter-php": "^0.23.12", "tree-sitter-python": "^0.21.0", "tree-sitter-rust": "^0.21.0", diff --git a/gitnexus/src/config/supported-languages.ts b/gitnexus/src/config/supported-languages.ts index a9bcd8248..7f72bc112 100644 --- a/gitnexus/src/config/supported-languages.ts +++ b/gitnexus/src/config/supported-languages.ts @@ -9,6 +9,7 @@ export enum SupportedLanguages { Go = 'go', Rust = 'rust', PHP = 'php', + Kotlin = 'kotlin', // Ruby = 'ruby', // Swift = 'swift', } \ No newline at end of file diff --git a/gitnexus/src/core/ingestion/call-processor.ts b/gitnexus/src/core/ingestion/call-processor.ts index 421c6ed0c..05148981b 100644 --- a/gitnexus/src/core/ingestion/call-processor.ts +++ b/gitnexus/src/core/ingestion/call-processor.ts @@ -37,6 +37,9 @@ const FUNCTION_NODE_TYPES = new Set([ // Rust 'function_item', 'impl_item', // Methods inside impl blocks + // Kotlin (function_declaration already included above via JS/TS) + 'anonymous_function', + 'lambda_literal', ]); /** @@ -336,6 +339,12 @@ const BUILT_IN_NAMES = new Set([ 'mutex_lock', 'mutex_unlock', 'mutex_init', 'kfree', 'kmalloc', 'kzalloc', 'kcalloc', 'krealloc', 'kvmalloc', 'kvfree', 'get', 'put', + // Kotlin stdlib + 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error', + 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf', + 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless', + 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet', + 'repeat', 'synchronized', ]); const isBuiltInOrNoise = (name: string): boolean => BUILT_IN_NAMES.has(name); diff --git a/gitnexus/src/core/ingestion/parsing-processor.ts b/gitnexus/src/core/ingestion/parsing-processor.ts index c15d39a17..b447afef8 100644 --- a/gitnexus/src/core/ingestion/parsing-processor.ts +++ b/gitnexus/src/core/ingestion/parsing-processor.ts @@ -5,7 +5,7 @@ import { LANGUAGE_QUERIES } from './tree-sitter-queries.js'; import { generateId } from '../../lib/utils.js'; import { SymbolTable } from './symbol-table.js'; import { ASTCache } from './ast-cache.js'; -import { getLanguageFromFilename, yieldToEventLoop } from './utils.js'; +import { findSiblingChild, getLanguageFromFilename, yieldToEventLoop } from './utils.js'; import { WorkerPool } from './workers/worker-pool.js'; import type { ParseWorkerResult, ParseWorkerInput, ExtractedImport, ExtractedCall, ExtractedHeritage } from './workers/parse-worker.js'; @@ -114,6 +114,23 @@ const isNodeExported = (node: any, name: string, language: string): boolean => { case 'cpp': return false; + // Kotlin: Default visibility is public (unlike Java) + // visibility_modifier is inside modifiers, a sibling of the name node within the declaration + case 'kotlin': + while (current) { + if (current.parent) { + const visMod = findSiblingChild(current.parent, 'modifiers', 'visibility_modifier'); + if (visMod) { + const text = visMod.text; + if (text === 'private' || text === 'internal' || text === 'protected') return false; + if (text === 'public') return true; + } + } + current = current.parent; + } + // No visibility modifier = public (Kotlin default) + return true; + default: return false; } diff --git a/gitnexus/src/core/ingestion/tree-sitter-queries.ts b/gitnexus/src/core/ingestion/tree-sitter-queries.ts index ff4f8f28f..cea3824e3 100644 --- a/gitnexus/src/core/ingestion/tree-sitter-queries.ts +++ b/gitnexus/src/core/ingestion/tree-sitter-queries.ts @@ -396,6 +396,68 @@ export const PHP_QUERIES = ` [(name) (qualified_name)] @heritage.trait))) @heritage `; +// Kotlin queries - works with tree-sitter-kotlin (fwcd/tree-sitter-kotlin) +// Based on official tags.scm; functions use simple_identifier, classes use type_identifier +export const KOTLIN_QUERIES = ` +; ── Classes (regular, data, sealed, enum) ──────────────────────────────── +(class_declaration + (type_identifier) @name) @definition.class + +; ── Interfaces ───────────────────────────────────────────────────────────── +(interface_declaration + (type_identifier) @name) @definition.interface + +; ── Object declarations (Kotlin singletons) ────────────────────────────── +(object_declaration + (type_identifier) @name) @definition.class + +; ── Companion objects (named only) ─────────────────────────────────────── +(companion_object + (type_identifier) @name) @definition.class + +; ── Functions (top-level, member, extension) ────────────────────────────── +(function_declaration + (simple_identifier) @name) @definition.function + +; ── Properties ─────────────────────────────────────────────────────────── +(property_declaration + (variable_declaration + (simple_identifier) @name)) @definition.property + +; ── Enum entries ───────────────────────────────────────────────────────── +(enum_entry + (simple_identifier) @name) @definition.property + +; ── Type aliases ───────────────────────────────────────────────────────── +(type_alias + (type_identifier) @name) @definition.type + +; ── Imports ────────────────────────────────────────────────────────────── +(import_header + (identifier) @import.source) @import + +; ── Function calls (direct) ────────────────────────────────────────────── +(call_expression + (simple_identifier) @call.name) @call + +; ── Method calls (via navigation: obj.method()) ────────────────────────── +(call_expression + (navigation_expression + (navigation_suffix + (simple_identifier) @call.name))) @call + +; ── Constructor invocations ────────────────────────────────────────────── +(constructor_invocation + (user_type + (type_identifier) @call.name)) @call + +; ── Heritage: extends / implements via delegation_specifier ────────────── +(class_declaration + (type_identifier) @heritage.class + (delegation_specifier + (user_type (type_identifier) @heritage.extends))) @heritage +`; + export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES, [SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES, @@ -407,5 +469,6 @@ export const LANGUAGE_QUERIES: Record = { [SupportedLanguages.CSharp]: CSHARP_QUERIES, [SupportedLanguages.Rust]: RUST_QUERIES, [SupportedLanguages.PHP]: PHP_QUERIES, + [SupportedLanguages.Kotlin]: KOTLIN_QUERIES, }; \ No newline at end of file diff --git a/gitnexus/src/core/ingestion/utils.ts b/gitnexus/src/core/ingestion/utils.ts index 12b4c6b3e..927e32e60 100644 --- a/gitnexus/src/core/ingestion/utils.ts +++ b/gitnexus/src/core/ingestion/utils.ts @@ -6,6 +6,23 @@ import { SupportedLanguages } from '../../config/supported-languages.js'; */ export const yieldToEventLoop = (): Promise => new Promise(resolve => setImmediate(resolve)); +/** + * Find a child of `childType` within a sibling node of `siblingType`. + * Used for Kotlin AST traversal where visibility_modifier lives inside a modifiers sibling. + */ +export const findSiblingChild = (parent: any, siblingType: string, childType: string): any | null => { + for (let i = 0; i < parent.childCount; i++) { + const sibling = parent.child(i); + if (sibling?.type === siblingType) { + for (let j = 0; j < sibling.childCount; j++) { + const child = sibling.child(j); + if (child?.type === childType) return child; + } + } + } + return null; +}; + /** * Map file extension to SupportedLanguage enum */ @@ -31,6 +48,8 @@ export const getLanguageFromFilename = (filename: string): SupportedLanguages | if (filename.endsWith('.go')) return SupportedLanguages.Go; // Rust if (filename.endsWith('.rs')) return SupportedLanguages.Rust; + // Kotlin + if (filename.endsWith('.kt') || filename.endsWith('.kts')) return SupportedLanguages.Kotlin; // PHP (all common extensions) if (filename.endsWith('.php') || filename.endsWith('.phtml') || filename.endsWith('.php3') || filename.endsWith('.php4') || diff --git a/gitnexus/src/core/ingestion/workers/parse-worker.ts b/gitnexus/src/core/ingestion/workers/parse-worker.ts index ff985ad4c..20bbde7a8 100644 --- a/gitnexus/src/core/ingestion/workers/parse-worker.ts +++ b/gitnexus/src/core/ingestion/workers/parse-worker.ts @@ -10,9 +10,10 @@ import CSharp from 'tree-sitter-c-sharp'; import Go from 'tree-sitter-go'; import Rust from 'tree-sitter-rust'; import PHP from 'tree-sitter-php'; +import Kotlin from 'tree-sitter-kotlin'; import { SupportedLanguages } from '../../../config/supported-languages.js'; import { LANGUAGE_QUERIES } from '../tree-sitter-queries.js'; -import { getLanguageFromFilename } from '../utils.js'; +import { findSiblingChild, getLanguageFromFilename } from '../utils.js'; import { generateId } from '../../../lib/utils.js'; // ============================================================================ @@ -103,6 +104,7 @@ const languageMap: Record = { [SupportedLanguages.Go]: Go, [SupportedLanguages.Rust]: Rust, [SupportedLanguages.PHP]: PHP.php_only, + [SupportedLanguages.Kotlin]: Kotlin, }; const setLanguage = (language: SupportedLanguages, filePath: string): void => { @@ -206,6 +208,23 @@ const isNodeExported = (node: any, name: string, language: string): boolean => { // Top-level functions (no parent class) are globally accessible return true; + // Kotlin: Default visibility is public (unlike Java) + // visibility_modifier is inside modifiers, a sibling of the name node within the declaration + case 'kotlin': + while (current) { + if (current.parent) { + const visMod = findSiblingChild(current.parent, 'modifiers', 'visibility_modifier'); + if (visMod) { + const text = visMod.text; + if (text === 'private' || text === 'internal' || text === 'protected') return false; + if (text === 'public') return true; + } + } + current = current.parent; + } + // No visibility modifier = public (Kotlin default) + return true; + default: return false; } @@ -222,6 +241,8 @@ const FUNCTION_NODE_TYPES = new Set([ 'method_declaration', 'constructor_declaration', 'local_function_statement', 'function_item', 'impl_item', 'anonymous_function_creation_expression', // PHP anonymous functions + // Kotlin (function_declaration already included above via JS/TS) + 'anonymous_function', 'lambda_literal', ]); /** Walk up AST to find enclosing function, return its generateId or null for top-level */ @@ -336,6 +357,12 @@ const BUILT_INS = new Set([ 'preg_match', 'preg_match_all', 'preg_replace', 'preg_split', 'header', 'session_start', 'session_destroy', 'ob_start', 'ob_end_clean', 'ob_get_clean', 'dd', 'dump', + // Kotlin stdlib + 'println', 'print', 'readLine', 'require', 'requireNotNull', 'check', 'assert', 'lazy', 'error', + 'listOf', 'mapOf', 'setOf', 'mutableListOf', 'mutableMapOf', 'mutableSetOf', + 'arrayOf', 'sequenceOf', 'also', 'apply', 'run', 'with', 'takeIf', 'takeUnless', + 'TODO', 'buildString', 'buildList', 'buildMap', 'buildSet', + 'repeat', 'synchronized', ]); // ============================================================================ diff --git a/gitnexus/src/core/kuzu/kuzu-adapter.ts b/gitnexus/src/core/kuzu/kuzu-adapter.ts index 1ba30d15b..b42978ae7 100644 --- a/gitnexus/src/core/kuzu/kuzu-adapter.ts +++ b/gitnexus/src/core/kuzu/kuzu-adapter.ts @@ -676,7 +676,6 @@ export const getEmbeddingTableName = (): string => EMBEDDING_TABLE_NAME; * Load the FTS extension (required before using FTS functions). * Safe to call multiple times — tracks loaded state. */ -let ftsLoaded = false; export const loadFTSExtension = async (): Promise => { if (ftsLoaded) return; if (!conn) { diff --git a/gitnexus/src/core/tree-sitter/parser-loader.ts b/gitnexus/src/core/tree-sitter/parser-loader.ts index e92898424..fb3a0ae93 100644 --- a/gitnexus/src/core/tree-sitter/parser-loader.ts +++ b/gitnexus/src/core/tree-sitter/parser-loader.ts @@ -9,6 +9,7 @@ import CSharp from 'tree-sitter-c-sharp'; import Go from 'tree-sitter-go'; import Rust from 'tree-sitter-rust'; import PHP from 'tree-sitter-php'; +import Kotlin from 'tree-sitter-kotlin'; import { SupportedLanguages } from '../../config/supported-languages.js'; let parser: Parser | null = null; @@ -25,6 +26,7 @@ const languageMap: Record = { [SupportedLanguages.Go]: Go, [SupportedLanguages.Rust]: Rust, [SupportedLanguages.PHP]: PHP.php_only, + [SupportedLanguages.Kotlin]: Kotlin, }; export const loadParser = async (): Promise => {