feat: add Kotlin language support

Add end-to-end Kotlin parsing, symbol extraction, and visibility detection.
Extract findSiblingChild helper into utils.ts for clean AST traversal of
Kotlin's modifiers/visibility_modifier sibling pattern. Fix pre-existing
duplicate ftsLoaded declaration in kuzu-adapter.ts.

Files changed:
- supported-languages.ts: add Kotlin enum member
- parser-loader.ts, parse-worker.ts: register tree-sitter-kotlin
- tree-sitter-queries.ts: add Kotlin queries for classes, interfaces,
  objects, functions, properties, imports, calls, and heritage
- parsing-processor.ts, parse-worker.ts: add Kotlin visibility detection
- call-processor.ts, parse-worker.ts: add Kotlin builtins and node types
- utils.ts: add .kt/.kts extension mapping and findSiblingChild helper
- package.json: add tree-sitter-kotlin dependency
This commit is contained in:
Gary Magyar
2026-02-26 17:01:35 +00:00
parent 7fe8830402
commit c37b63ae8b
10 changed files with 167 additions and 3 deletions
+26
View File
@@ -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",
+1
View File
@@ -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",
@@ -9,6 +9,7 @@ export enum SupportedLanguages {
Go = 'go',
Rust = 'rust',
PHP = 'php',
Kotlin = 'kotlin',
// Ruby = 'ruby',
// Swift = 'swift',
}
@@ -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);
@@ -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;
}
@@ -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, string> = {
[SupportedLanguages.TypeScript]: TYPESCRIPT_QUERIES,
[SupportedLanguages.JavaScript]: JAVASCRIPT_QUERIES,
@@ -407,5 +469,6 @@ export const LANGUAGE_QUERIES: Record<SupportedLanguages, string> = {
[SupportedLanguages.CSharp]: CSHARP_QUERIES,
[SupportedLanguages.Rust]: RUST_QUERIES,
[SupportedLanguages.PHP]: PHP_QUERIES,
[SupportedLanguages.Kotlin]: KOTLIN_QUERIES,
};
+19
View File
@@ -6,6 +6,23 @@ import { SupportedLanguages } from '../../config/supported-languages.js';
*/
export const yieldToEventLoop = (): Promise<void> => 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') ||
@@ -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<string, any> = {
[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',
]);
// ============================================================================
-1
View File
@@ -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<void> => {
if (ftsLoaded) return;
if (!conn) {
@@ -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<string, any> = {
[SupportedLanguages.Go]: Go,
[SupportedLanguages.Rust]: Rust,
[SupportedLanguages.PHP]: PHP.php_only,
[SupportedLanguages.Kotlin]: Kotlin,
};
export const loadParser = async (): Promise<Parser> => {