refactor: Extract per-language configs and standalone extractors from tree-sitter.ts
Splits the monolithic tree-sitter.ts (3,358 lines) into modular files: - 14 language config files under src/extraction/languages/ - 3 standalone extractors (Liquid, Svelte, DFM) - Shared helpers and types modules to avoid circular imports Also fixes a bug where Java's extractImport hook incorrectly set handledRefs: true, preventing unresolved reference creation and degrading codegraph_explore results for Java codebases. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
parent
0d63166f9d
commit
c8407ad007
@@ -0,0 +1,83 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const cExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: [],
|
||||
methodTypes: [],
|
||||
interfaceTypes: [],
|
||||
structTypes: ['struct_specifier'],
|
||||
enumTypes: ['enum_specifier'],
|
||||
typeAliasTypes: ['type_definition'], // typedef
|
||||
importTypes: ['preproc_include'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['declaration'],
|
||||
nameField: 'declarator',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
// C includes: #include <stdio.h>, #include "myheader.h"
|
||||
const systemLib = node.namedChildren.find((c: SyntaxNode) => c.type === 'system_lib_string');
|
||||
if (systemLib) {
|
||||
return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ''), signature: importText };
|
||||
}
|
||||
const stringLiteral = node.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
|
||||
if (stringLiteral) {
|
||||
const stringContent = stringLiteral.namedChildren.find((c: SyntaxNode) => c.type === 'string_content');
|
||||
if (stringContent) {
|
||||
return { moduleName: getNodeText(stringContent, source), signature: importText };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
|
||||
export const cppExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: ['class_specifier'],
|
||||
methodTypes: ['function_definition'],
|
||||
interfaceTypes: [],
|
||||
structTypes: ['struct_specifier'],
|
||||
enumTypes: ['enum_specifier'],
|
||||
typeAliasTypes: ['type_definition', 'alias_declaration'], // typedef and using
|
||||
importTypes: ['preproc_include'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['declaration'],
|
||||
nameField: 'declarator',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
getVisibility: (node) => {
|
||||
// Check for access specifier in parent
|
||||
const parent = node.parent;
|
||||
if (parent) {
|
||||
for (let i = 0; i < parent.childCount; i++) {
|
||||
const child = parent.child(i);
|
||||
if (child?.type === 'access_specifier') {
|
||||
const text = child.text;
|
||||
if (text.includes('public')) return 'public';
|
||||
if (text.includes('private')) return 'private';
|
||||
if (text.includes('protected')) return 'protected';
|
||||
}
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
// C++ includes: #include <iostream>, #include "myheader.h"
|
||||
const systemLib = node.namedChildren.find((c: SyntaxNode) => c.type === 'system_lib_string');
|
||||
if (systemLib) {
|
||||
return { moduleName: getNodeText(systemLib, source).replace(/^<|>$/g, ''), signature: importText };
|
||||
}
|
||||
const stringLiteral = node.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
|
||||
if (stringLiteral) {
|
||||
const stringContent = stringLiteral.namedChildren.find((c: SyntaxNode) => c.type === 'string_content');
|
||||
if (stringContent) {
|
||||
return { moduleName: getNodeText(stringContent, source), signature: importText };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const csharpExtractor: LanguageExtractor = {
|
||||
functionTypes: [],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['method_declaration', 'constructor_declaration'],
|
||||
interfaceTypes: ['interface_declaration'],
|
||||
structTypes: ['struct_declaration'],
|
||||
enumTypes: ['enum_declaration'],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['using_directive'],
|
||||
callTypes: ['invocation_expression'],
|
||||
variableTypes: ['local_declaration_statement', 'field_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameter_list',
|
||||
getVisibility: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifier') {
|
||||
const text = child.text;
|
||||
if (text === 'public') return 'public';
|
||||
if (text === 'private') return 'private';
|
||||
if (text === 'protected') return 'protected';
|
||||
if (text === 'internal') return 'internal';
|
||||
}
|
||||
}
|
||||
return 'private'; // C# defaults to private
|
||||
},
|
||||
isStatic: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifier' && child.text === 'static') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isAsync: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifier' && child.text === 'async') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
// C# using directives: using System, using System.Collections.Generic, using static X, using Alias = X
|
||||
const qualifiedName = node.namedChildren.find((c: SyntaxNode) => c.type === 'qualified_name');
|
||||
if (qualifiedName) {
|
||||
return { moduleName: getNodeText(qualifiedName, source), signature: importText };
|
||||
}
|
||||
// Simple namespace like "using System;" - get the first identifier
|
||||
const identifier = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||
if (identifier) {
|
||||
return { moduleName: getNodeText(identifier, source), signature: importText };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,134 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const dartExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_signature'],
|
||||
classTypes: ['class_definition'],
|
||||
methodTypes: ['method_signature'],
|
||||
interfaceTypes: [],
|
||||
structTypes: [],
|
||||
enumTypes: ['enum_declaration'],
|
||||
typeAliasTypes: ['type_alias'],
|
||||
importTypes: ['import_or_export'],
|
||||
callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
|
||||
variableTypes: [],
|
||||
extraClassNodeTypes: ['mixin_declaration', 'extension_declaration'],
|
||||
resolveBody: (node, bodyField) => {
|
||||
// Dart: function_body is a next sibling of function_signature/method_signature
|
||||
if (node.type === 'function_signature' || node.type === 'method_signature') {
|
||||
const next = node.nextNamedSibling;
|
||||
if (next?.type === 'function_body') return next;
|
||||
return null;
|
||||
}
|
||||
// For class/mixin/extension: try standard field, then class_body/extension_body
|
||||
const standard = node.childForFieldName(bodyField);
|
||||
if (standard) return standard;
|
||||
return node.namedChildren.find((c: SyntaxNode) =>
|
||||
c.type === 'class_body' || c.type === 'extension_body'
|
||||
) || null;
|
||||
},
|
||||
nameField: 'name',
|
||||
bodyField: 'body', // class_definition uses 'body' field
|
||||
paramsField: 'formal_parameter_list',
|
||||
returnField: 'type',
|
||||
getSignature: (node, source) => {
|
||||
// For function_signature: extract params + return type
|
||||
// For method_signature: delegate to inner function_signature
|
||||
let sig = node;
|
||||
if (node.type === 'method_signature') {
|
||||
const inner = node.namedChildren.find((c: SyntaxNode) =>
|
||||
c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature'
|
||||
);
|
||||
if (inner) sig = inner;
|
||||
}
|
||||
const params = sig.namedChildren.find((c: SyntaxNode) => c.type === 'formal_parameter_list');
|
||||
const retType = sig.namedChildren.find((c: SyntaxNode) =>
|
||||
c.type === 'type_identifier' || c.type === 'void_type'
|
||||
);
|
||||
if (!params && !retType) return undefined;
|
||||
let result = '';
|
||||
if (retType) result += getNodeText(retType, source) + ' ';
|
||||
if (params) result += getNodeText(params, source);
|
||||
return result.trim() || undefined;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
// Dart convention: _ prefix means private, otherwise public
|
||||
let nameNode: SyntaxNode | null = null;
|
||||
if (node.type === 'method_signature') {
|
||||
const inner = node.namedChildren.find((c: SyntaxNode) =>
|
||||
c.type === 'function_signature' || c.type === 'getter_signature' || c.type === 'setter_signature'
|
||||
);
|
||||
if (inner) nameNode = inner.namedChildren.find((c: SyntaxNode) => c.type === 'identifier') || null;
|
||||
} else {
|
||||
nameNode = node.childForFieldName('name');
|
||||
}
|
||||
if (nameNode && nameNode.text.startsWith('_')) return 'private';
|
||||
return 'public';
|
||||
},
|
||||
isAsync: (node) => {
|
||||
// In Dart, 'async' is on the function_body (next sibling), not the signature
|
||||
const nextSibling = node.nextNamedSibling;
|
||||
if (nextSibling?.type === 'function_body') {
|
||||
for (let i = 0; i < nextSibling.childCount; i++) {
|
||||
const child = nextSibling.child(i);
|
||||
if (child?.type === 'async') return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isStatic: (node) => {
|
||||
// For method_signature, check for 'static' child
|
||||
if (node.type === 'method_signature') {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'static') return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
let moduleName = '';
|
||||
|
||||
// Dart imports: import 'dart:async'; import 'package:foo/bar.dart' as bar;
|
||||
const libraryImport = node.namedChildren.find((c: SyntaxNode) => c.type === 'library_import');
|
||||
if (libraryImport) {
|
||||
const importSpec = libraryImport.namedChildren.find((c: SyntaxNode) => c.type === 'import_specification');
|
||||
if (importSpec) {
|
||||
const configurableUri = importSpec.namedChildren.find((c: SyntaxNode) => c.type === 'configurable_uri');
|
||||
if (configurableUri) {
|
||||
const uri = configurableUri.namedChildren.find((c: SyntaxNode) => c.type === 'uri');
|
||||
if (uri) {
|
||||
const stringLiteral = uri.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
|
||||
if (stringLiteral) {
|
||||
moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Also handle exports: export 'src/foo.dart';
|
||||
if (!moduleName) {
|
||||
const libraryExport = node.namedChildren.find((c: SyntaxNode) => c.type === 'library_export');
|
||||
if (libraryExport) {
|
||||
const configurableUri = libraryExport.namedChildren.find((c: SyntaxNode) => c.type === 'configurable_uri');
|
||||
if (configurableUri) {
|
||||
const uri = configurableUri.namedChildren.find((c: SyntaxNode) => c.type === 'uri');
|
||||
if (uri) {
|
||||
const stringLiteral = uri.namedChildren.find((c: SyntaxNode) => c.type === 'string_literal');
|
||||
if (stringLiteral) {
|
||||
moduleName = getNodeText(stringLiteral, source).replace(/['"]/g, '');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (moduleName) {
|
||||
return { moduleName, signature: importText };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,30 @@
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const goExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration'],
|
||||
classTypes: [], // Go doesn't have classes
|
||||
methodTypes: ['method_declaration'],
|
||||
interfaceTypes: ['interface_type'],
|
||||
structTypes: ['struct_type'],
|
||||
enumTypes: [],
|
||||
typeAliasTypes: ['type_spec'], // Go type declarations
|
||||
importTypes: ['import_declaration'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['var_declaration', 'short_var_declaration', 'const_declaration'],
|
||||
methodsAreTopLevel: true,
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'result',
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
const result = getChildByField(node, 'result');
|
||||
if (!params) return undefined;
|
||||
let sig = getNodeText(params, source);
|
||||
if (result) {
|
||||
sig += ' ' + getNodeText(result, source);
|
||||
}
|
||||
return sig;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Per-language extraction configurations.
|
||||
*
|
||||
* Each file exports a LanguageExtractor config object.
|
||||
* This barrel builds the EXTRACTORS map consumed by TreeSitterExtractor.
|
||||
*/
|
||||
|
||||
import { Language } from '../../types';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
import { typescriptExtractor } from './typescript';
|
||||
import { javascriptExtractor } from './javascript';
|
||||
import { pythonExtractor } from './python';
|
||||
import { goExtractor } from './go';
|
||||
import { rustExtractor } from './rust';
|
||||
import { javaExtractor } from './java';
|
||||
import { cExtractor, cppExtractor } from './c-cpp';
|
||||
import { csharpExtractor } from './csharp';
|
||||
import { phpExtractor } from './php';
|
||||
import { rubyExtractor } from './ruby';
|
||||
import { swiftExtractor } from './swift';
|
||||
import { kotlinExtractor } from './kotlin';
|
||||
import { dartExtractor } from './dart';
|
||||
import { pascalExtractor } from './pascal';
|
||||
|
||||
export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
|
||||
typescript: typescriptExtractor,
|
||||
tsx: typescriptExtractor,
|
||||
javascript: javascriptExtractor,
|
||||
jsx: javascriptExtractor,
|
||||
python: pythonExtractor,
|
||||
go: goExtractor,
|
||||
rust: rustExtractor,
|
||||
java: javaExtractor,
|
||||
c: cExtractor,
|
||||
cpp: cppExtractor,
|
||||
csharp: csharpExtractor,
|
||||
php: phpExtractor,
|
||||
ruby: rubyExtractor,
|
||||
swift: swiftExtractor,
|
||||
kotlin: kotlinExtractor,
|
||||
dart: dartExtractor,
|
||||
pascal: pascalExtractor,
|
||||
};
|
||||
@@ -0,0 +1,57 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const javaExtractor: LanguageExtractor = {
|
||||
functionTypes: [],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['method_declaration', 'constructor_declaration'],
|
||||
interfaceTypes: ['interface_declaration'],
|
||||
structTypes: [],
|
||||
enumTypes: ['enum_declaration'],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['import_declaration'],
|
||||
callTypes: ['method_invocation'],
|
||||
variableTypes: ['local_variable_declaration', 'field_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'type',
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
const returnType = getChildByField(node, 'type');
|
||||
if (!params) return undefined;
|
||||
const paramsText = getNodeText(params, source);
|
||||
return returnType ? getNodeText(returnType, source) + ' ' + paramsText : paramsText;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers') {
|
||||
const text = child.text;
|
||||
if (text.includes('public')) return 'public';
|
||||
if (text.includes('private')) return 'private';
|
||||
if (text.includes('protected')) return 'protected';
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
isStatic: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers' && child.text.includes('static')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
const scopedId = node.namedChildren.find((c: SyntaxNode) => c.type === 'scoped_identifier');
|
||||
if (scopedId) {
|
||||
const moduleName = source.substring(scopedId.startIndex, scopedId.endIndex);
|
||||
return { moduleName, signature: importText };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,56 @@
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const javascriptExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['method_definition', 'field_definition'],
|
||||
interfaceTypes: [],
|
||||
structTypes: [],
|
||||
enumTypes: [],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['import_statement'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
return params ? getNodeText(params, source) : undefined;
|
||||
},
|
||||
isExported: (node, _source) => {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
if (current.type === 'export_statement') return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isAsync: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'async') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isConst: (node) => {
|
||||
if (node.type === 'lexical_declaration') {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'const') return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const sourceField = node.childForFieldName('source');
|
||||
if (sourceField) {
|
||||
const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, '');
|
||||
if (moduleName) {
|
||||
return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,68 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const kotlinExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration'],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['function_declaration'], // Methods are functions inside classes
|
||||
interfaceTypes: ['class_declaration'], // Interfaces use class_declaration with 'interface' modifier
|
||||
structTypes: [], // Kotlin uses data classes
|
||||
enumTypes: ['class_declaration'], // Enums use class_declaration with 'enum' modifier
|
||||
typeAliasTypes: ['type_alias'],
|
||||
importTypes: ['import_header'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['property_declaration'],
|
||||
nameField: 'simple_identifier',
|
||||
bodyField: 'function_body',
|
||||
paramsField: 'function_value_parameters',
|
||||
returnField: 'type',
|
||||
getSignature: (node, source) => {
|
||||
// Kotlin function signature: fun name(params): ReturnType
|
||||
const params = getChildByField(node, 'function_value_parameters');
|
||||
const returnType = getChildByField(node, 'type');
|
||||
if (!params) return undefined;
|
||||
let sig = getNodeText(params, source);
|
||||
if (returnType) {
|
||||
sig += ': ' + getNodeText(returnType, source);
|
||||
}
|
||||
return sig;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
// Check for visibility modifiers in Kotlin
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers') {
|
||||
const text = child.text;
|
||||
if (text.includes('public')) return 'public';
|
||||
if (text.includes('private')) return 'private';
|
||||
if (text.includes('protected')) return 'protected';
|
||||
if (text.includes('internal')) return 'internal';
|
||||
}
|
||||
}
|
||||
return 'public'; // Kotlin defaults to public
|
||||
},
|
||||
isStatic: (_node) => {
|
||||
// Kotlin doesn't have static, uses companion objects
|
||||
// Check if inside companion object would require more context
|
||||
return false;
|
||||
},
|
||||
isAsync: (node) => {
|
||||
// Kotlin uses suspend keyword for coroutines
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers' && child.text.includes('suspend')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
const identifier = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||
if (identifier) {
|
||||
return { moduleName: source.substring(identifier.startIndex, identifier.endIndex), signature: importText };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,62 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const pascalExtractor: LanguageExtractor = {
|
||||
functionTypes: ['declProc'],
|
||||
classTypes: ['declClass'],
|
||||
methodTypes: ['declProc'],
|
||||
interfaceTypes: ['declIntf'],
|
||||
structTypes: [],
|
||||
enumTypes: ['declEnum'],
|
||||
typeAliasTypes: ['declType'],
|
||||
importTypes: ['declUses'],
|
||||
callTypes: ['exprCall'],
|
||||
variableTypes: ['declField', 'declConst'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'args',
|
||||
returnField: 'type',
|
||||
getSignature: (node, source) => {
|
||||
const args = getChildByField(node, 'args');
|
||||
const returnType = node.namedChildren.find(
|
||||
(c: SyntaxNode) => c.type === 'typeref'
|
||||
);
|
||||
if (!args && !returnType) return undefined;
|
||||
let sig = '';
|
||||
if (args) sig = getNodeText(args, source);
|
||||
if (returnType) {
|
||||
sig += ': ' + getNodeText(returnType, source);
|
||||
}
|
||||
return sig || undefined;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
if (current.type === 'declSection') {
|
||||
for (let i = 0; i < current.childCount; i++) {
|
||||
const child = current.child(i);
|
||||
if (child?.type === 'kPublic' || child?.type === 'kPublished')
|
||||
return 'public';
|
||||
if (child?.type === 'kPrivate') return 'private';
|
||||
if (child?.type === 'kProtected') return 'protected';
|
||||
}
|
||||
}
|
||||
current = current.parent;
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
isExported: (_node, _source) => {
|
||||
// In Pascal, symbols declared in the interface section are exported
|
||||
return false;
|
||||
},
|
||||
isStatic: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
if (node.child(i)?.type === 'kClass') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isConst: (node) => {
|
||||
return node.type === 'declConst';
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,63 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const phpExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['method_declaration'],
|
||||
interfaceTypes: ['interface_declaration'],
|
||||
structTypes: [],
|
||||
enumTypes: ['enum_declaration'],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['namespace_use_declaration'],
|
||||
callTypes: ['function_call_expression', 'member_call_expression', 'scoped_call_expression'],
|
||||
variableTypes: ['property_declaration', 'const_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'return_type',
|
||||
getVisibility: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'visibility_modifier') {
|
||||
const text = child.text;
|
||||
if (text === 'public') return 'public';
|
||||
if (text === 'private') return 'private';
|
||||
if (text === 'protected') return 'protected';
|
||||
}
|
||||
}
|
||||
return 'public'; // PHP defaults to public
|
||||
},
|
||||
isStatic: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'static_modifier') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
|
||||
// Check for grouped imports: use X\{A, B} - return null for core fallback
|
||||
const namespacePrefix = node.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_name');
|
||||
const useGroup = node.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_use_group');
|
||||
if (namespacePrefix && useGroup) {
|
||||
return null; // Grouped imports create multiple nodes - let core handle
|
||||
}
|
||||
|
||||
// Single import - find namespace_use_clause
|
||||
const useClause = node.namedChildren.find((c: SyntaxNode) => c.type === 'namespace_use_clause');
|
||||
if (useClause) {
|
||||
const qualifiedName = useClause.namedChildren.find((c: SyntaxNode) => c.type === 'qualified_name');
|
||||
if (qualifiedName) {
|
||||
return { moduleName: getNodeText(qualifiedName, source), signature: importText };
|
||||
}
|
||||
const name = useClause.namedChildren.find((c: SyntaxNode) => c.type === 'name');
|
||||
if (name) {
|
||||
return { moduleName: getNodeText(name, source), signature: importText };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,53 @@
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const pythonExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_definition'],
|
||||
classTypes: ['class_definition'],
|
||||
methodTypes: ['function_definition'], // Methods are functions inside classes
|
||||
interfaceTypes: [],
|
||||
structTypes: [],
|
||||
enumTypes: [],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['import_statement', 'import_from_statement'],
|
||||
callTypes: ['call'],
|
||||
variableTypes: ['assignment'], // Python uses assignment for variable declarations
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'return_type',
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
const returnType = getChildByField(node, 'return_type');
|
||||
if (!params) return undefined;
|
||||
let sig = getNodeText(params, source);
|
||||
if (returnType) {
|
||||
sig += ' -> ' + getNodeText(returnType, source);
|
||||
}
|
||||
return sig;
|
||||
},
|
||||
isAsync: (node) => {
|
||||
const prev = node.previousSibling;
|
||||
return prev?.type === 'async';
|
||||
},
|
||||
isStatic: (node) => {
|
||||
// Check for @staticmethod decorator
|
||||
const prev = node.previousNamedSibling;
|
||||
if (prev?.type === 'decorator') {
|
||||
const text = prev.text;
|
||||
return text.includes('staticmethod');
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
if (node.type === 'import_from_statement') {
|
||||
const moduleNode = node.childForFieldName('module_name');
|
||||
if (moduleNode) {
|
||||
return { moduleName: source.substring(moduleNode.startIndex, moduleNode.endIndex), signature: importText };
|
||||
}
|
||||
}
|
||||
// import_statement creates multiple imports - return null for core fallback
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,60 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const rubyExtractor: LanguageExtractor = {
|
||||
functionTypes: ['method'],
|
||||
classTypes: ['class'],
|
||||
methodTypes: ['method', 'singleton_method'],
|
||||
interfaceTypes: [], // Ruby uses modules
|
||||
structTypes: [],
|
||||
enumTypes: [],
|
||||
typeAliasTypes: [],
|
||||
importTypes: ['call'], // require/require_relative
|
||||
callTypes: ['call', 'method_call'],
|
||||
variableTypes: ['assignment'], // Ruby uses assignment like Python
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
getVisibility: (node) => {
|
||||
// Ruby visibility is based on preceding visibility modifiers
|
||||
let sibling = node.previousNamedSibling;
|
||||
while (sibling) {
|
||||
if (sibling.type === 'call') {
|
||||
const methodName = getChildByField(sibling, 'method');
|
||||
if (methodName) {
|
||||
const text = methodName.text;
|
||||
if (text === 'private') return 'private';
|
||||
if (text === 'protected') return 'protected';
|
||||
if (text === 'public') return 'public';
|
||||
}
|
||||
}
|
||||
sibling = sibling.previousNamedSibling;
|
||||
}
|
||||
return 'public';
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
|
||||
// Check if this is a require/require_relative call
|
||||
const identifier = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||
if (!identifier) return null;
|
||||
const methodName = getNodeText(identifier, source);
|
||||
if (methodName !== 'require' && methodName !== 'require_relative') {
|
||||
return null; // Not an import, skip
|
||||
}
|
||||
|
||||
// Find the argument (string)
|
||||
const argList = node.namedChildren.find((c: SyntaxNode) => c.type === 'argument_list');
|
||||
if (argList) {
|
||||
const stringNode = argList.namedChildren.find((c: SyntaxNode) => c.type === 'string');
|
||||
if (stringNode) {
|
||||
const stringContent = stringNode.namedChildren.find((c: SyntaxNode) => c.type === 'string_content');
|
||||
if (stringContent) {
|
||||
return { moduleName: getNodeText(stringContent, source), signature: importText };
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,78 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const rustExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_item'],
|
||||
classTypes: [], // Rust has impl blocks
|
||||
methodTypes: ['function_item'], // Methods are functions in impl blocks
|
||||
interfaceTypes: ['trait_item'],
|
||||
structTypes: ['struct_item'],
|
||||
enumTypes: ['enum_item'],
|
||||
typeAliasTypes: ['type_item'], // Rust type aliases
|
||||
importTypes: ['use_declaration'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['let_declaration', 'const_item', 'static_item'],
|
||||
interfaceKind: 'trait',
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'return_type',
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
const returnType = getChildByField(node, 'return_type');
|
||||
if (!params) return undefined;
|
||||
let sig = getNodeText(params, source);
|
||||
if (returnType) {
|
||||
sig += ' -> ' + getNodeText(returnType, source);
|
||||
}
|
||||
return sig;
|
||||
},
|
||||
isAsync: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'async') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'visibility_modifier') {
|
||||
return child.text.includes('pub') ? 'public' : 'private';
|
||||
}
|
||||
}
|
||||
return 'private'; // Rust defaults to private
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
|
||||
// Helper to get the root crate/module from a scoped path
|
||||
const getRootModule = (scopedNode: SyntaxNode): string => {
|
||||
const firstChild = scopedNode.namedChild(0);
|
||||
if (!firstChild) return source.substring(scopedNode.startIndex, scopedNode.endIndex);
|
||||
if (firstChild.type === 'identifier' ||
|
||||
firstChild.type === 'crate' ||
|
||||
firstChild.type === 'super' ||
|
||||
firstChild.type === 'self') {
|
||||
return source.substring(firstChild.startIndex, firstChild.endIndex);
|
||||
} else if (firstChild.type === 'scoped_identifier') {
|
||||
return getRootModule(firstChild);
|
||||
}
|
||||
return source.substring(firstChild.startIndex, firstChild.endIndex);
|
||||
};
|
||||
|
||||
// Find the use argument (scoped_use_list or scoped_identifier)
|
||||
const useArg = node.namedChildren.find((c: SyntaxNode) =>
|
||||
c.type === 'scoped_use_list' ||
|
||||
c.type === 'scoped_identifier' ||
|
||||
c.type === 'use_list' ||
|
||||
c.type === 'identifier'
|
||||
);
|
||||
|
||||
if (useArg) {
|
||||
return { moduleName: getRootModule(useArg), signature: importText };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,82 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const swiftExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration'],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['function_declaration'], // Methods are functions inside classes
|
||||
interfaceTypes: ['protocol_declaration'],
|
||||
structTypes: ['struct_declaration'],
|
||||
enumTypes: ['enum_declaration'],
|
||||
typeAliasTypes: ['typealias_declaration'],
|
||||
importTypes: ['import_declaration'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['property_declaration', 'constant_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameter',
|
||||
returnField: 'return_type',
|
||||
getSignature: (node, source) => {
|
||||
// Swift function signature: func name(params) -> ReturnType
|
||||
const params = getChildByField(node, 'parameter');
|
||||
const returnType = getChildByField(node, 'return_type');
|
||||
if (!params) return undefined;
|
||||
let sig = getNodeText(params, source);
|
||||
if (returnType) {
|
||||
sig += ' -> ' + getNodeText(returnType, source);
|
||||
}
|
||||
return sig;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
// Check for visibility modifiers in Swift
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers') {
|
||||
const text = child.text;
|
||||
if (text.includes('public')) return 'public';
|
||||
if (text.includes('private')) return 'private';
|
||||
if (text.includes('internal')) return 'internal';
|
||||
if (text.includes('fileprivate')) return 'private';
|
||||
}
|
||||
}
|
||||
return 'internal'; // Swift defaults to internal
|
||||
},
|
||||
isStatic: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers') {
|
||||
if (child.text.includes('static') || child.text.includes('class')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
classifyClassNode: (node) => {
|
||||
// Swift uses class_declaration for classes, structs, and enums
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'struct') return 'struct';
|
||||
if (child?.type === 'enum') return 'enum';
|
||||
}
|
||||
return 'class';
|
||||
},
|
||||
isAsync: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'modifiers' && child.text.includes('async')) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const importText = source.substring(node.startIndex, node.endIndex).trim();
|
||||
const identifier = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||
if (identifier) {
|
||||
return { moduleName: source.substring(identifier.startIndex, identifier.endIndex), signature: importText };
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,88 @@
|
||||
import { getNodeText, getChildByField } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
export const typescriptExtractor: LanguageExtractor = {
|
||||
functionTypes: ['function_declaration', 'arrow_function', 'function_expression'],
|
||||
classTypes: ['class_declaration'],
|
||||
methodTypes: ['method_definition', 'public_field_definition'],
|
||||
interfaceTypes: ['interface_declaration'],
|
||||
structTypes: [],
|
||||
enumTypes: ['enum_declaration'],
|
||||
typeAliasTypes: ['type_alias_declaration'],
|
||||
importTypes: ['import_statement'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: ['lexical_declaration', 'variable_declaration'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'return_type',
|
||||
getSignature: (node, source) => {
|
||||
const params = getChildByField(node, 'parameters');
|
||||
const returnType = getChildByField(node, 'return_type');
|
||||
if (!params) return undefined;
|
||||
let sig = getNodeText(params, source);
|
||||
if (returnType) {
|
||||
sig += ': ' + getNodeText(returnType, source).replace(/^:\s*/, '');
|
||||
}
|
||||
return sig;
|
||||
},
|
||||
getVisibility: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'accessibility_modifier') {
|
||||
const text = child.text;
|
||||
if (text === 'public') return 'public';
|
||||
if (text === 'private') return 'private';
|
||||
if (text === 'protected') return 'protected';
|
||||
}
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
isExported: (node, _source) => {
|
||||
// Walk the parent chain to find an export_statement ancestor.
|
||||
// This correctly handles deeply nested nodes like arrow functions
|
||||
// inside variable declarations: `export const X = () => { ... }`
|
||||
// where the arrow_function is 3 levels deep under export_statement.
|
||||
let current = node.parent;
|
||||
while (current) {
|
||||
if (current.type === 'export_statement') return true;
|
||||
current = current.parent;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isAsync: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'async') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isStatic: (node) => {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'static') return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
isConst: (node) => {
|
||||
// For lexical_declaration, check if it's 'const' or 'let'
|
||||
// For variable_declaration, it's always 'var'
|
||||
if (node.type === 'lexical_declaration') {
|
||||
for (let i = 0; i < node.childCount; i++) {
|
||||
const child = node.child(i);
|
||||
if (child?.type === 'const') return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
},
|
||||
extractImport: (node, source) => {
|
||||
const sourceField = node.childForFieldName('source');
|
||||
if (sourceField) {
|
||||
const moduleName = source.substring(sourceField.startIndex, sourceField.endIndex).replace(/['"]/g, '');
|
||||
if (moduleName) {
|
||||
return { moduleName, signature: source.substring(node.startIndex, node.endIndex).trim() };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user