feat: Add Ruby bare method call extraction for identifier nodes

Addresses Ruby bare method calls like `reset` that parse as identifier nodes instead of call expressions. Adds extractBareCall hook to detect statement-level identifiers that represent method calls, filtering out keywords, literals, and constants. Enables proper call relationship tracking for Ruby's parentheses-optional method syntax.
This commit is contained in:
Colby McHenry
2026-04-07 09:27:00 -05:00
parent 59ea5a43be
commit 9382a087f4
3 changed files with 51 additions and 0 deletions
+29
View File
@@ -38,6 +38,35 @@ export const rubyExtractor: LanguageExtractor = {
ctx.popScope(); ctx.popScope();
return true; // handled return true; // handled
}, },
extractBareCall: (node, _source) => {
// Ruby bare method calls (no parens, no receiver) parse as plain identifiers.
// e.g., `reset` in a method body is `identifier "reset"` not a `call` node.
if (node.type !== 'identifier') return undefined;
const parent = node.parent;
if (!parent) return undefined;
// Only statement-level identifiers — direct children of block/body nodes
const BLOCK_PARENTS = new Set([
'body_statement', 'then', 'else', 'do', 'begin',
'rescue', 'ensure', 'when',
]);
if (!BLOCK_PARENTS.has(parent.type)) return undefined;
const name = node.text;
// Skip Ruby keywords/literals
const SKIP = new Set([
'true', 'false', 'nil', 'self', 'super',
'__FILE__', '__LINE__', '__dir__',
]);
if (SKIP.has(name)) return undefined;
// Skip constants (uppercase start) — these are class/module refs, not calls
if (name.length > 0 && name.charCodeAt(0) >= 65 && name.charCodeAt(0) <= 90) return undefined;
return name;
},
getVisibility: (node) => { getVisibility: (node) => {
// Ruby visibility is based on preceding visibility modifiers // Ruby visibility is based on preceding visibility modifiers
let sibling = node.previousNamedSibling; let sibling = node.previousNamedSibling;
+8
View File
@@ -198,4 +198,12 @@ export interface LanguageExtractor {
* structural nodes (classes, structs, enums). * structural nodes (classes, structs, enums).
*/ */
isMisparsedFunction?: (name: string, node: SyntaxNode) => boolean; isMisparsedFunction?: (name: string, node: SyntaxNode) => boolean;
/**
* Detect bare method calls that don't use call expression syntax.
* Used by Ruby where `reset` (no parens, no receiver) is a method call but
* tree-sitter parses it as a plain `identifier` node instead of `call`/`method_call`.
* Returns the callee name if this node is a bare call, or undefined if not.
*/
extractBareCall?: (node: SyntaxNode, source: string) => string | undefined;
} }
+14
View File
@@ -1391,6 +1391,20 @@ export class TreeSitterExtractor {
if (this.extractor!.callTypes.includes(nodeType)) { if (this.extractor!.callTypes.includes(nodeType)) {
this.extractCall(node); this.extractCall(node);
} else if (this.extractor!.extractBareCall) {
const calleeName = this.extractor!.extractBareCall(node, this.source);
if (calleeName && this.nodeStack.length > 0) {
const callerId = this.nodeStack[this.nodeStack.length - 1];
if (callerId) {
this.unresolvedReferences.push({
fromNodeId: callerId,
referenceName: calleeName,
referenceKind: 'calls',
line: node.startPosition.row + 1,
column: node.startPosition.column,
});
}
}
} }
// Extract structural nodes found inside function bodies. // Extract structural nodes found inside function bodies.