90 scala support (#91)
Co-authored-by: Colby McHenry <me@colbymchenry.com> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Colby McHenry
Claude Opus 4.7
parent
00b298966f
commit
8506936f86
@@ -34,6 +34,7 @@ const WASM_GRAMMAR_FILES: Record<GrammarLanguage, string> = {
|
||||
kotlin: 'tree-sitter-kotlin.wasm',
|
||||
dart: 'tree-sitter-dart.wasm',
|
||||
pascal: 'tree-sitter-pascal.wasm',
|
||||
scala: 'tree-sitter-scala.wasm',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -75,6 +76,8 @@ export const EXTENSION_MAP: Record<string, Language> = {
|
||||
'.lpr': 'pascal',
|
||||
'.dfm': 'pascal',
|
||||
'.fmx': 'pascal',
|
||||
'.scala': 'scala',
|
||||
'.sc': 'scala',
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -122,8 +125,8 @@ export async function loadGrammarsForLanguages(languages: Language[]): Promise<v
|
||||
for (const lang of toLoad) {
|
||||
const wasmFile = WASM_GRAMMAR_FILES[lang];
|
||||
try {
|
||||
// Pascal ships its own WASM (not in tree-sitter-wasms)
|
||||
const wasmPath = lang === 'pascal'
|
||||
// Pascal and Scala ship their own WASMs (not in tree-sitter-wasms)
|
||||
const wasmPath = (lang === 'pascal' || lang === 'scala')
|
||||
? path.join(__dirname, 'wasm', wasmFile)
|
||||
: require.resolve(`tree-sitter-wasms/out/${wasmFile}`);
|
||||
const language = await WasmLanguage.load(wasmPath);
|
||||
@@ -287,6 +290,7 @@ export function getLanguageDisplayName(language: Language): string {
|
||||
vue: 'Vue',
|
||||
liquid: 'Liquid',
|
||||
pascal: 'Pascal / Delphi',
|
||||
scala: 'Scala',
|
||||
unknown: 'Unknown',
|
||||
};
|
||||
return names[language] || language;
|
||||
|
||||
@@ -22,6 +22,7 @@ import { swiftExtractor } from './swift';
|
||||
import { kotlinExtractor } from './kotlin';
|
||||
import { dartExtractor } from './dart';
|
||||
import { pascalExtractor } from './pascal';
|
||||
import { scalaExtractor } from './scala';
|
||||
|
||||
export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
|
||||
typescript: typescriptExtractor,
|
||||
@@ -41,4 +42,5 @@ export const EXTRACTORS: Partial<Record<Language, LanguageExtractor>> = {
|
||||
kotlin: kotlinExtractor,
|
||||
dart: dartExtractor,
|
||||
pascal: pascalExtractor,
|
||||
scala: scalaExtractor,
|
||||
};
|
||||
|
||||
@@ -0,0 +1,143 @@
|
||||
import type { Node as SyntaxNode } from 'web-tree-sitter';
|
||||
import { getNodeText } from '../tree-sitter-helpers';
|
||||
import type { LanguageExtractor } from '../tree-sitter-types';
|
||||
|
||||
function getValVarName(node: SyntaxNode, source: string): string | null {
|
||||
const patternNode = node.childForFieldName('pattern');
|
||||
if (!patternNode) return null;
|
||||
if (patternNode.type === 'identifier') return getNodeText(patternNode, source);
|
||||
const identChild = patternNode.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||
return identChild ? getNodeText(identChild, source) : null;
|
||||
}
|
||||
|
||||
function extractVisibility(node: SyntaxNode): 'public' | 'private' | 'protected' {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child) continue;
|
||||
if (child.type === 'modifiers' || child.type === 'access_modifier') {
|
||||
const text = child.text;
|
||||
if (text.includes('private')) return 'private';
|
||||
if (text.includes('protected')) return 'protected';
|
||||
}
|
||||
}
|
||||
return 'public';
|
||||
}
|
||||
|
||||
export const scalaExtractor: LanguageExtractor = {
|
||||
// top-level function_definition is handled via methodTypes (same pattern as Kotlin)
|
||||
functionTypes: [],
|
||||
classTypes: ['class_definition', 'object_definition', 'trait_definition'],
|
||||
methodTypes: ['function_definition', 'function_declaration'],
|
||||
interfaceTypes: [],
|
||||
structTypes: [],
|
||||
enumTypes: ['enum_definition'],
|
||||
enumMemberTypes: [], // handled in visitNode — enum_case_definitions wraps the cases
|
||||
typeAliasTypes: ['type_definition'],
|
||||
importTypes: ['import_declaration'],
|
||||
callTypes: ['call_expression'],
|
||||
variableTypes: [], // val/var handled in visitNode (use `pattern` field, not `name`)
|
||||
fieldTypes: [],
|
||||
extraClassNodeTypes: [],
|
||||
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
returnField: 'return_type',
|
||||
interfaceKind: 'trait',
|
||||
|
||||
classifyClassNode: (node: SyntaxNode) => {
|
||||
if (node.type === 'trait_definition') return 'trait';
|
||||
return 'class';
|
||||
},
|
||||
|
||||
getSignature: (node: SyntaxNode, source: string) => {
|
||||
const params = node.childForFieldName('parameters');
|
||||
const returnType = node.childForFieldName('return_type');
|
||||
if (!params && !returnType) return undefined;
|
||||
let sig = params ? getNodeText(params, source) : '';
|
||||
if (returnType) sig += ': ' + getNodeText(returnType, source);
|
||||
return sig || undefined;
|
||||
},
|
||||
|
||||
getVisibility: (node: SyntaxNode) => extractVisibility(node),
|
||||
|
||||
isAsync: () => false,
|
||||
|
||||
isStatic: (node: SyntaxNode) => {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (child?.type === 'modifiers' && child.text.includes('static')) return true;
|
||||
}
|
||||
return false;
|
||||
},
|
||||
|
||||
visitNode: (node: SyntaxNode, ctx) => {
|
||||
const t = node.type;
|
||||
|
||||
// val/var: name is in `pattern` field (identifier), not `name`
|
||||
if (t === 'val_definition' || t === 'var_definition') {
|
||||
const name = getValVarName(node, ctx.source);
|
||||
if (!name) return false;
|
||||
|
||||
const isInClass = ctx.nodeStack.length > 0 &&
|
||||
(() => {
|
||||
const parentId = ctx.nodeStack[ctx.nodeStack.length - 1];
|
||||
const parentNode = ctx.nodes.find((n) => n.id === parentId);
|
||||
return parentNode != null && (
|
||||
parentNode.kind === 'class' || parentNode.kind === 'trait' ||
|
||||
parentNode.kind === 'interface' || parentNode.kind === 'struct' ||
|
||||
parentNode.kind === 'enum' || parentNode.kind === 'module'
|
||||
);
|
||||
})();
|
||||
|
||||
const kind = isInClass ? 'field' : (t === 'val_definition' ? 'constant' : 'variable');
|
||||
const typeNode = node.childForFieldName('type');
|
||||
const sig = typeNode
|
||||
? `${t === 'val_definition' ? 'val' : 'var'} ${name}: ${getNodeText(typeNode, ctx.source)}`
|
||||
: undefined;
|
||||
|
||||
ctx.createNode(kind, name, node, { signature: sig, visibility: extractVisibility(node) });
|
||||
return true;
|
||||
}
|
||||
|
||||
// enum_case_definitions wraps simple_enum_case / full_enum_case children
|
||||
if (t === 'enum_case_definitions') {
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (!child) continue;
|
||||
if (child.type === 'simple_enum_case' || child.type === 'full_enum_case') {
|
||||
const nameNode = child.childForFieldName('name');
|
||||
if (nameNode) ctx.createNode('enum_member', getNodeText(nameNode, ctx.source), child);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// extension_definition: visit body children directly, no container node
|
||||
if (t === 'extension_definition') {
|
||||
const body = node.childForFieldName('body');
|
||||
if (body) {
|
||||
for (let i = 0; i < body.namedChildCount; i++) {
|
||||
const child = body.namedChild(i);
|
||||
if (child) ctx.visitNode(child);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
|
||||
extractImport: (node: SyntaxNode, source: string) => {
|
||||
const importText = getNodeText(node, source).trim();
|
||||
const pathNode = node.childForFieldName('path');
|
||||
if (pathNode) return { moduleName: getNodeText(pathNode, source), signature: importText };
|
||||
for (let i = 0; i < node.namedChildCount; i++) {
|
||||
const child = node.namedChild(i);
|
||||
if (child?.type === 'identifier' || child?.type === 'stable_identifier') {
|
||||
return { moduleName: getNodeText(child, source), signature: importText };
|
||||
}
|
||||
}
|
||||
return null;
|
||||
},
|
||||
};
|
||||
Binary file not shown.
@@ -84,6 +84,7 @@ export const LANGUAGES = [
|
||||
'vue',
|
||||
'liquid',
|
||||
'pascal',
|
||||
'scala',
|
||||
'unknown',
|
||||
] as const;
|
||||
|
||||
@@ -541,6 +542,9 @@ export const DEFAULT_CONFIG: CodeGraphConfig = {
|
||||
'**/*.lpr',
|
||||
'**/*.dfm',
|
||||
'**/*.fmx',
|
||||
// Scala
|
||||
'**/*.scala',
|
||||
'**/*.sc',
|
||||
],
|
||||
exclude: [
|
||||
// Version control
|
||||
|
||||
Reference in New Issue
Block a user