Skip to content

Commit 02a581c

Browse files
authored
fix(groovy): dispatch juxt_function_call in both engines (#1124)
Groovy command-style calls (`foo bar(x)`, Gradle DSL `task someTask { ... }`, `apply plugin: 'java'`, etc.) parse as `juxt_function_call` nodes, which neither the JS source-of-truth extractor nor the Rust port was dispatching — so these call edges were silently dropped from the call graph in both engines. The juxt node has a `name` field with the same shape as `method_invocation`, so adding it to the existing dispatch in `walkGroovyNode` / `match_groovy_node` is sufficient — the existing call handler picks up the callee without further special-casing. Closes #1108
1 parent 2ba882a commit 02a581c

3 files changed

Lines changed: 39 additions & 6 deletions

File tree

crates/codegraph-core/src/extractors/groovy.rs

Lines changed: 22 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,11 @@ use tree_sitter::{Node, Tree};
2323
/// is only matched as a callee sub-node inside `handle_call_expr` when examining
2424
/// the `function`/`method` field of a call.
2525
///
26-
/// Note: `juxt_function_call` (Groovy command-style calls like `foo bar(x)`)
27-
/// is not dispatched here — the JS extractor also omits it. Tracked in #1108
28-
/// for adding support to both engines.
26+
/// `juxt_function_call` (Groovy command-style calls like `foo bar(x)` or the
27+
/// Gradle DSL `task someTask { ... }`) is dispatched through `handle_call_expr`:
28+
/// the grammar gives the juxt node a `name` field with the same shape as
29+
/// `method_invocation`, so the existing handler picks up the callee without
30+
/// special-casing.
2931
pub struct GroovyExtractor;
3032

3133
impl SymbolExtractor for GroovyExtractor {
@@ -59,9 +61,8 @@ fn match_groovy_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dep
5961
"constructor_declaration" | "constructor_definition" => handle_constructor_decl(node, source, symbols),
6062
"function_definition" | "function_declaration" => handle_function_decl(node, source, symbols),
6163
"import_declaration" | "import_statement" => handle_import_decl(node, source, symbols),
62-
"method_invocation" | "method_call" | "call_expression" | "function_call" => {
63-
handle_call_expr(node, source, symbols)
64-
}
64+
"method_invocation" | "method_call" | "call_expression" | "function_call"
65+
| "juxt_function_call" => handle_call_expr(node, source, symbols),
6566
"object_creation_expression" => handle_object_creation(node, source, symbols),
6667
_ => {}
6768
}
@@ -499,6 +500,21 @@ mod tests {
499500
assert!(names.contains(&"GREEN"));
500501
}
501502

503+
#[test]
504+
fn extracts_command_style_juxt_calls() {
505+
// Gradle DSL pattern: `task`, `apply`, and `println` are invoked
506+
// command-style without parens. The grammar emits these as
507+
// `juxt_function_call` nodes; missing dispatch silently drops them
508+
// from the call graph.
509+
let s = parse_groovy(
510+
"apply plugin: 'java'\ntask someTask {\n doLast {\n println \"hello\"\n }\n}",
511+
);
512+
let names: Vec<&str> = s.calls.iter().map(|c| c.name.as_str()).collect();
513+
assert!(names.contains(&"apply"), "missing `apply` juxt call: {:?}", names);
514+
assert!(names.contains(&"task"), "missing `task` juxt call: {:?}", names);
515+
assert!(names.contains(&"println"), "missing `println` juxt call: {:?}", names);
516+
}
517+
502518
#[test]
503519
fn extracts_superclass_and_interfaces() {
504520
let s = parse_groovy("class Sub extends Base implements I1, I2 {}");

src/extractors/groovy.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ function walkGroovyNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
6868
case 'method_invocation':
6969
case 'call_expression':
7070
case 'function_call':
71+
case 'juxt_function_call':
7172
handleGroovyCallExpr(node, ctx);
7273
break;
7374
case 'object_creation_expression':

tests/parsers/groovy.test.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,4 +58,20 @@ describe('Groovy parser', () => {
5858
expect.objectContaining({ name: 'Color', kind: 'enum' }),
5959
);
6060
});
61+
62+
it('extracts command-style (juxt) function calls', () => {
63+
// Gradle DSL pattern: `task` and `apply` are invoked command-style without
64+
// parens. The grammar emits these as `juxt_function_call` nodes; missing
65+
// dispatch silently drops them from the call graph.
66+
const symbols = parseGroovy(`apply plugin: 'java'
67+
task someTask {
68+
doLast {
69+
println "hello"
70+
}
71+
}`);
72+
const callNames = symbols.calls.map((c) => c.name);
73+
expect(callNames).toContain('apply');
74+
expect(callNames).toContain('task');
75+
expect(callNames).toContain('println');
76+
});
6177
});

0 commit comments

Comments
 (0)