|
@@ -648,6 +648,7 @@ export class TreeSitterExtractor {
|
|
|
const nodeType = node.type;
|
|
const nodeType = node.type;
|
|
|
if (depth > 0 && (
|
|
if (depth > 0 && (
|
|
|
this.extractor?.functionTypes.includes(nodeType) ||
|
|
this.extractor?.functionTypes.includes(nodeType) ||
|
|
|
|
|
+ ((this.language === 'lua' || this.language === 'luau') && nodeType === 'function_definition') ||
|
|
|
nodeType === 'arrow_function' ||
|
|
nodeType === 'arrow_function' ||
|
|
|
nodeType === 'function_expression' ||
|
|
nodeType === 'function_expression' ||
|
|
|
nodeType === 'lambda_literal' ||
|
|
nodeType === 'lambda_literal' ||
|
|
@@ -2905,14 +2906,28 @@ export class TreeSitterExtractor {
|
|
|
const varList = assign.namedChildren.find((c) => c.type === 'variable_list');
|
|
const varList = assign.namedChildren.find((c) => c.type === 'variable_list');
|
|
|
const exprList = assign.namedChildren.find((c) => c.type === 'expression_list');
|
|
const exprList = assign.namedChildren.find((c) => c.type === 'expression_list');
|
|
|
const values = exprList ? exprList.namedChildren : [];
|
|
const values = exprList ? exprList.namedChildren : [];
|
|
|
- const names = varList ? varList.namedChildren.filter((c) => c.type === 'identifier') : [];
|
|
|
|
|
- names.forEach((nameNode, i) => {
|
|
|
|
|
- const name = getNodeText(nameNode, this.source);
|
|
|
|
|
- if (!name) return;
|
|
|
|
|
|
|
+ const targets = varList ? varList.namedChildren : [];
|
|
|
|
|
+ targets.forEach((nameNode, i) => {
|
|
|
const valueNode = values[i];
|
|
const valueNode = values[i];
|
|
|
|
|
+ const target = this.luaAssignmentTarget(nameNode);
|
|
|
|
|
+ if (!target) return;
|
|
|
|
|
+
|
|
|
|
|
+ if (valueNode?.type === 'function_definition') {
|
|
|
|
|
+ this.extractLuaFunctionValue(valueNode, target.name, target.receiver, docstring);
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ if (valueNode?.type === 'table_constructor') {
|
|
|
|
|
+ this.extractLuaTableFunctions(valueNode, target.fullName);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // A dotted assignment updates a table member; it is not a standalone
|
|
|
|
|
+ // variable node. Function-valued members were handled above.
|
|
|
|
|
+ if (target.receiver || node.type === 'assignment_statement') return;
|
|
|
|
|
+
|
|
|
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
const initValue = valueNode ? getNodeText(valueNode, this.source).slice(0, 100) : undefined;
|
|
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
const initSignature = initValue ? `= ${initValue}${initValue.length >= 100 ? '...' : ''}` : undefined;
|
|
|
- this.createNode(kind, name, nameNode, { docstring, signature: initSignature, isExported });
|
|
|
|
|
|
|
+ this.createNode(kind, target.name, nameNode, { docstring, signature: initSignature, isExported });
|
|
|
});
|
|
});
|
|
|
} else if (this.language === 'c') {
|
|
} else if (this.language === 'c') {
|
|
|
// C: a `declaration` node's name nests inside the `declarator` field —
|
|
// C: a `declaration` node's name nests inside the `declarator` field —
|
|
@@ -2992,6 +3007,77 @@ export class TreeSitterExtractor {
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ /** Resolve a Lua assignment target into its callable name and optional table receiver. */
|
|
|
|
|
+ private luaAssignmentTarget(node: SyntaxNode): { name: string; receiver?: string; fullName: string } | null {
|
|
|
|
|
+ if (node.type === 'identifier') {
|
|
|
|
|
+ const name = getNodeText(node, this.source).trim();
|
|
|
|
|
+ return name ? { name, fullName: name } : null;
|
|
|
|
|
+ }
|
|
|
|
|
+ if (
|
|
|
|
|
+ node.type !== 'dot_index_expression' &&
|
|
|
|
|
+ node.type !== 'method_index_expression' &&
|
|
|
|
|
+ node.type !== 'bracket_index_expression'
|
|
|
|
|
+ ) return null;
|
|
|
|
|
+ const table = getChildByField(node, 'table');
|
|
|
|
|
+ const field = getChildByField(node, 'field') ?? getChildByField(node, 'method');
|
|
|
|
|
+ if (!table || !field) return null;
|
|
|
|
|
+ const receiver = getNodeText(table, this.source).trim();
|
|
|
|
|
+ const name = this.luaStaticFieldName(field, node.type === 'bracket_index_expression');
|
|
|
|
|
+ if (!receiver || !name) return null;
|
|
|
|
|
+ return { name, receiver, fullName: `${receiver}.${name}` };
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** A statically-known Lua field name; dynamic bracket keys are not callable identities. */
|
|
|
|
|
+ private luaStaticFieldName(node: SyntaxNode, bracketed: boolean): string {
|
|
|
|
|
+ if (node.type === 'identifier') {
|
|
|
|
|
+ return bracketed ? '' : getNodeText(node, this.source).trim();
|
|
|
|
|
+ }
|
|
|
|
|
+ if (node.type === 'string') {
|
|
|
|
|
+ const content = node.namedChildren.find((child) => child.type === 'string_content');
|
|
|
|
|
+ return content ? getNodeText(content, this.source).trim() : '';
|
|
|
|
|
+ }
|
|
|
|
|
+ return '';
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Extract an anonymous Lua function using the name supplied by its assignment target. */
|
|
|
|
|
+ private extractLuaFunctionValue(
|
|
|
|
|
+ node: SyntaxNode,
|
|
|
|
|
+ name: string,
|
|
|
|
|
+ receiver?: string,
|
|
|
|
|
+ docstring?: string
|
|
|
|
|
+ ): void {
|
|
|
|
|
+ if (!this.extractor) return;
|
|
|
|
|
+ const signature = this.extractor.getSignature?.(node, this.source);
|
|
|
|
|
+ const extra: Partial<Node> = { docstring, signature };
|
|
|
|
|
+ if (receiver) extra.qualifiedName = this.composeReceiverQualifiedName(receiver, name);
|
|
|
|
|
+ else extra.isExported = this.extractor.isExported?.(node, this.source);
|
|
|
|
|
+
|
|
|
|
|
+ const functionNode = this.createNode(receiver ? 'method' : 'function', name, node, extra);
|
|
|
|
|
+ if (!functionNode) return;
|
|
|
|
|
+ this.nodeStack.push(functionNode.id);
|
|
|
|
|
+ const body = getChildByField(node, this.extractor.bodyField);
|
|
|
|
|
+ if (body) this.visitFunctionBody(body, functionNode.id);
|
|
|
|
|
+ this.nodeStack.pop();
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /** Extract function-valued keyed fields from a Lua table, including nested tables. */
|
|
|
|
|
+ private extractLuaTableFunctions(table: SyntaxNode, receiver: string): void {
|
|
|
|
|
+ for (const field of table.namedChildren) {
|
|
|
|
|
+ if (field.type !== 'field') continue;
|
|
|
|
|
+ const nameNode = getChildByField(field, 'name');
|
|
|
|
|
+ const valueNode = getChildByField(field, 'value');
|
|
|
|
|
+ if (!nameNode || !valueNode) continue;
|
|
|
|
|
+ const bracketed = getNodeText(field, this.source).trimStart().startsWith('[');
|
|
|
|
|
+ const name = this.luaStaticFieldName(nameNode, bracketed);
|
|
|
|
|
+ if (!name) continue;
|
|
|
|
|
+ if (valueNode.type === 'function_definition') {
|
|
|
|
|
+ this.extractLuaFunctionValue(valueNode, name, receiver);
|
|
|
|
|
+ } else if (valueNode.type === 'table_constructor') {
|
|
|
|
|
+ this.extractLuaTableFunctions(valueNode, `${receiver}.${name}`);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
/**
|
|
/**
|
|
|
* Extract a type alias (e.g. `export type X = ...` in TypeScript).
|
|
* Extract a type alias (e.g. `export type X = ...` in TypeScript).
|
|
|
* For languages like Go, resolveTypeAliasKind detects when the type_spec
|
|
* For languages like Go, resolveTypeAliasKind detects when the type_spec
|