Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 22 additions & 6 deletions crates/codegraph-core/src/extractors/groovy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@ use tree_sitter::{Node, Tree};
/// is only matched as a callee sub-node inside `handle_call_expr` when examining
/// the `function`/`method` field of a call.
///
/// Note: `juxt_function_call` (Groovy command-style calls like `foo bar(x)`)
/// is not dispatched here — the JS extractor also omits it. Tracked in #1108
/// for adding support to both engines.
/// `juxt_function_call` (Groovy command-style calls like `foo bar(x)` or the
/// Gradle DSL `task someTask { ... }`) is dispatched through `handle_call_expr`:
/// the grammar gives the juxt node a `name` field with the same shape as
/// `method_invocation`, so the existing handler picks up the callee without
/// special-casing.
pub struct GroovyExtractor;

impl SymbolExtractor for GroovyExtractor {
Expand Down Expand Up @@ -59,9 +61,8 @@ fn match_groovy_node(node: &Node, source: &[u8], symbols: &mut FileSymbols, _dep
"constructor_declaration" | "constructor_definition" => handle_constructor_decl(node, source, symbols),
"function_definition" | "function_declaration" => handle_function_decl(node, source, symbols),
"import_declaration" | "import_statement" => handle_import_decl(node, source, symbols),
"method_invocation" | "method_call" | "call_expression" | "function_call" => {
handle_call_expr(node, source, symbols)
}
"method_invocation" | "method_call" | "call_expression" | "function_call"
| "juxt_function_call" => handle_call_expr(node, source, symbols),
"object_creation_expression" => handle_object_creation(node, source, symbols),
_ => {}
}
Expand Down Expand Up @@ -499,6 +500,21 @@ mod tests {
assert!(names.contains(&"GREEN"));
}

#[test]
fn extracts_command_style_juxt_calls() {
// Gradle DSL pattern: `task`, `apply`, and `println` are invoked
// command-style without parens. The grammar emits these as
// `juxt_function_call` nodes; missing dispatch silently drops them
// from the call graph.
let s = parse_groovy(
"apply plugin: 'java'\ntask someTask {\n doLast {\n println \"hello\"\n }\n}",
);
let names: Vec<&str> = s.calls.iter().map(|c| c.name.as_str()).collect();
assert!(names.contains(&"apply"), "missing `apply` juxt call: {:?}", names);
assert!(names.contains(&"task"), "missing `task` juxt call: {:?}", names);
assert!(names.contains(&"println"), "missing `println` juxt call: {:?}", names);
}

#[test]
fn extracts_superclass_and_interfaces() {
let s = parse_groovy("class Sub extends Base implements I1, I2 {}");
Expand Down
1 change: 1 addition & 0 deletions src/extractors/groovy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ function walkGroovyNode(node: TreeSitterNode, ctx: ExtractorOutput): void {
case 'method_invocation':
case 'call_expression':
case 'function_call':
case 'juxt_function_call':
handleGroovyCallExpr(node, ctx);
break;
case 'object_creation_expression':
Expand Down
16 changes: 16 additions & 0 deletions tests/parsers/groovy.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,4 +58,20 @@ describe('Groovy parser', () => {
expect.objectContaining({ name: 'Color', kind: 'enum' }),
);
});

it('extracts command-style (juxt) function calls', () => {
// Gradle DSL pattern: `task` and `apply` are invoked command-style without
// parens. The grammar emits these as `juxt_function_call` nodes; missing
// dispatch silently drops them from the call graph.
const symbols = parseGroovy(`apply plugin: 'java'
task someTask {
doLast {
println "hello"
}
}`);
const callNames = symbols.calls.map((c) => c.name);
expect(callNames).toContain('apply');
expect(callNames).toContain('task');
expect(callNames).toContain('println');
});
});
Loading