Skip to content

Do not run ExtractExplicitConstructorInvocationArguments on non-Java sources - #1235

Open
knutwannheden wants to merge 1 commit into
mainfrom
extractexplicitconstructorinvocationarguments-cce-on-groovy
Open

Do not run ExtractExplicitConstructorInvocationArguments on non-Java sources#1235
knutwannheden wants to merge 1 commit into
mainfrom
extractexplicitconstructorinvocationarguments-cce-on-groovy

Conversation

@knutwannheden

@knutwannheden knutwannheden commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

Running ExtractExplicitConstructorInvocationArguments over a corpus of open-source repositories throws on spring-cloud/spring-cloud-contract, in spring-cloud-contract-verifier/src/test/groovy/org/springframework/cloud/contract/verifier/util/SyntaxChecker.groovy:

java.lang.ClassCastException: class org.openrewrite.java.tree.J$FieldAccess cannot be cast to class org.openrewrite.java.tree.J$MethodInvocation
  org.openrewrite.java.internal.template.JavaTemplateParser.parseMethodArguments(JavaTemplateParser.java:223)
  org.openrewrite.java.internal.template.JavaTemplateJavaExtension$1.visitMethodInvocation(JavaTemplateJavaExtension.java:420)
  org.openrewrite.java.JavaTemplate.apply(JavaTemplate.java:196)
  org.openrewrite.java.migrate.lang.ExtractExplicitConstructorInvocationArguments$1$1.visitMethodInvocation(ExtractExplicitConstructorInvocationArguments.java:150)

How a Groovy file gets here

The visitor is a JavaIsoVisitor, whose isAcceptable takes any JavaSourceFile, and that includes G.CompilationUnit. The UsesJavaVersion<>(25) precondition does not narrow it either: the JavaVersion marker on a Groovy source carries the module's Java version, so it matches.

Groovy attributes a method type to super(..) only under @CompileStatic, taken from StaticTypesMarker.DIRECT_METHOD_CALL_TARGET. SyntaxChecker.groovy annotates its nested classes that way, so the getMethodType() == null bail-out does not catch them and the recipe builds a JavaTemplate whose stub, generated from the Groovy LST, is parsed as Java. JavaTemplateParser.parseMethodArguments then casts the result:

return (J.MethodInvocation) statementTemplateGenerator
        .listTemplatedTrees(cu, Statement.class).get(0);

For the two-argument super(URI.create(…), Kind.SOURCE) in that file the parse yields a J.FieldAccess. For a one-argument call the list comes back empty and the same line throws IndexOutOfBoundsException instead.

Fix

isAcceptable now returns sourceFile instanceof J.CompilationUnit. JEP 513 is a Java language feature and the transformation is written as a JavaTemplate, so Groovy and Kotlin are out of scope for this recipe whatever the LST happens to allow.

The cast in parseMethodArguments is unsound in general, but hardening it upstream would only trade one exception for another: a Java template applied to a non-Java LST has no correct result. The guard belongs at the recipe. Nothing about the shape is specific to this recipe, though — any JavaTemplate-based recipe without a source-file guard can reach the same line.

Tests

doNotRunOnGroovySources pins the guard with a minimal @CompileStatic Groovy source; without the fix it fails at JavaTemplateParser.java:223. The SimpleJavaFileObject subclass from SyntaxChecker.groovy reproduces the reported cast verbatim, but a two-line super(name.trim()) defends the same guard, so that is what the suite keeps.

…a sources

The recipe's `JavaIsoVisitor` accepted any `JavaSourceFile`, so it also ran on
Groovy compilation units. There it reached `JavaTemplate`, which generates its
stub from the Groovy LST and parses it as Java, and
`JavaTemplateParser.parseMethodArguments` threw on the result.

Narrow the visitor to `J.CompilationUnit`. JEP 513 is a Java language feature,
so Groovy and Kotlin sources are out of scope for this recipe.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: In Progress

Development

Successfully merging this pull request may close these issues.

1 participant