Apply upstream PR #1623 by danusha2345 (fix commit aa777063), which also addresses #1616, to the current main base. Preserve the upstream WASM and Rust implementations and regression coverage. Index assigned locals, table members, static string keys and nested callback tables as callable symbols, with calls owned by those symbols. Keep dynamic keys unguessed. Add #1650 to the Unreleased changelog and retain the existing re-index guidance without an extraction-version bump. Verified on Linux x64 with Node 22.19.0: - native kernel build, tsc, asset copy, executable CLI - issue repro: 3 nodes / 2 edges -> 4 nodes / 4 edges - EPR.PowerController::SyncHydroPower is indexed; its caller is client.lua - syncHydroPower depth-2 impact reaches client.lua - extraction/resolution/Lua parity: 844 tests passed (kernel expected) - forced-WASM Lua/Luau extraction/resolution: 20 tests passed Co-authored-by: Colby McHenry <colbymchenry@users.noreply.github.com>
This commit is contained in:
co-authored by
Colby McHenry
parent
195888d71f
commit
8c047342cd
@@ -75,7 +75,10 @@ export const luaExtractor: LanguageExtractor = {
|
||||
typeAliasTypes: [],
|
||||
importTypes: [], // `require` is a function_call — handled in visitNode below
|
||||
callTypes: ['function_call'],
|
||||
variableTypes: ['variable_declaration'], // see the `lua` branch in extractVariable
|
||||
// Top-level assignments can introduce module members just as declarations do:
|
||||
// `M.run = function() ... end`. The Lua branch in extractVariable ignores
|
||||
// non-callable member assignments, but extracts function-valued targets.
|
||||
variableTypes: ['variable_declaration', 'assignment_statement'],
|
||||
nameField: 'name',
|
||||
bodyField: 'body',
|
||||
paramsField: 'parameters',
|
||||
|
||||
@@ -648,6 +648,7 @@ export class TreeSitterExtractor {
|
||||
const nodeType = node.type;
|
||||
if (depth > 0 && (
|
||||
this.extractor?.functionTypes.includes(nodeType) ||
|
||||
((this.language === 'lua' || this.language === 'luau') && nodeType === 'function_definition') ||
|
||||
nodeType === 'arrow_function' ||
|
||||
nodeType === 'function_expression' ||
|
||||
nodeType === 'lambda_literal' ||
|
||||
@@ -2905,14 +2906,28 @@ export class TreeSitterExtractor {
|
||||
const varList = assign.namedChildren.find((c) => c.type === 'variable_list');
|
||||
const exprList = assign.namedChildren.find((c) => c.type === 'expression_list');
|
||||
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 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 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') {
|
||||
// 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).
|
||||
* For languages like Go, resolveTypeAliasKind detects when the type_spec
|
||||
|
||||
Reference in New Issue
Block a user