feat: Add Dart bare call extraction for selector-based method calls
Addresses Dart method calls like `obj.method()` and `runApp()` that parse as identifier+selector combinations instead of dedicated call nodes. Adds extractBareCall hook to detect selector nodes with argument_part, handling simple function calls, method chains, constructor calls (new/const), and super/this method calls. Enables proper call relationship tracking for Dart's selector-based call syntax.
This commit is contained in:
@@ -12,7 +12,7 @@ export const dartExtractor: LanguageExtractor = {
|
|||||||
enumMemberTypes: ['enum_constant'],
|
enumMemberTypes: ['enum_constant'],
|
||||||
typeAliasTypes: ['type_alias'],
|
typeAliasTypes: ['type_alias'],
|
||||||
importTypes: ['import_or_export'],
|
importTypes: ['import_or_export'],
|
||||||
callTypes: [], // Dart calls use identifier+selector, handled via function body traversal
|
callTypes: [], // Dart calls use identifier+selector, handled via extractBareCall
|
||||||
variableTypes: [],
|
variableTypes: [],
|
||||||
extraClassNodeTypes: ['mixin_declaration', 'extension_declaration'],
|
extraClassNodeTypes: ['mixin_declaration', 'extension_declaration'],
|
||||||
resolveBody: (node, bodyField) => {
|
resolveBody: (node, bodyField) => {
|
||||||
@@ -132,4 +132,64 @@ export const dartExtractor: LanguageExtractor = {
|
|||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
extractBareCall: (node, _source) => {
|
||||||
|
// Dart calls are: identifier + selector(argument_part), not a dedicated call node.
|
||||||
|
// Match on selector nodes that contain argument_part.
|
||||||
|
if (node.type === 'selector') {
|
||||||
|
const hasArgPart = node.namedChildren.some((c: SyntaxNode) => c.type === 'argument_part');
|
||||||
|
if (!hasArgPart) return undefined;
|
||||||
|
|
||||||
|
const prev = node.previousNamedSibling;
|
||||||
|
if (!prev) return undefined;
|
||||||
|
|
||||||
|
// Simple function/constructor call: prev is identifier (e.g., runApp(...), MyWidget(...))
|
||||||
|
if (prev.type === 'identifier') {
|
||||||
|
return prev.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Method call: prev is selector with accessor (e.g., obj.method(...), Navigator.push(...))
|
||||||
|
if (prev.type === 'selector') {
|
||||||
|
const accessor = prev.namedChildren.find((c: SyntaxNode) =>
|
||||||
|
c.type === 'unconditional_assignable_selector' || c.type === 'conditional_assignable_selector'
|
||||||
|
);
|
||||||
|
if (accessor) {
|
||||||
|
const methodId = accessor.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||||
|
if (methodId) {
|
||||||
|
// Include receiver for first call in chain (receiver is a direct identifier)
|
||||||
|
const accessorPrev = prev.previousNamedSibling;
|
||||||
|
if (accessorPrev?.type === 'identifier') {
|
||||||
|
return accessorPrev.text + '.' + methodId.text;
|
||||||
|
}
|
||||||
|
return methodId.text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// super.method() / this.method(): prev is bare unconditional_assignable_selector
|
||||||
|
if (prev.type === 'unconditional_assignable_selector' || prev.type === 'conditional_assignable_selector') {
|
||||||
|
const methodId = prev.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||||
|
if (methodId) return methodId.text;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// new MyWidget() — explicit constructor call
|
||||||
|
if (node.type === 'new_expression') {
|
||||||
|
const typeId = node.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
|
||||||
|
if (typeId) return typeId.text;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
// const EdgeInsets.all(8.0) — const constructor call
|
||||||
|
if (node.type === 'const_object_expression') {
|
||||||
|
const typeId = node.namedChildren.find((c: SyntaxNode) => c.type === 'type_identifier');
|
||||||
|
const nameId = node.namedChildren.find((c: SyntaxNode) => c.type === 'identifier');
|
||||||
|
if (typeId && nameId) return typeId.text + '.' + nameId.text;
|
||||||
|
if (typeId) return typeId.text;
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined;
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user