Skip to content

Commit b5f46c2

Browse files
committed
Fix ChangeType UnsupportedOperationException on JS FunctionCall nodes
ChangeType.postVisit() falls through to the TypedTree branch for JS.FunctionCall nodes, which throws UnsupportedOperationException because withType() is not supported on MethodCall implementations. Add a MethodCall catch-all branch before the TypedTree fallback that uses withMethodType() instead, handling JS.FunctionCall and any other future MethodCall implementations not explicitly handled.
1 parent 5b212d9 commit b5f46c2

2 files changed

Lines changed: 84 additions & 0 deletions

File tree

rewrite-java/src/main/java/org/openrewrite/java/ChangeType.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -221,6 +221,9 @@ private void addImport(JavaType.FullyQualified owningClass) {
221221
} else if (j instanceof J.NewClass) {
222222
J.NewClass n = (J.NewClass) j;
223223
j = n.withConstructorType(updateType(n.getConstructorType()));
224+
} else if (j instanceof MethodCall) {
225+
MethodCall call = (MethodCall) j;
226+
j = (J) call.withMethodType(updateType(call.getMethodType()));
224227
} else if (tree instanceof TypedTree) {
225228
j = ((TypedTree) tree).withType(updateType(((TypedTree) tree).getType()));
226229
} else if (tree instanceof JavaSourceFile) {
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2025 the original author or authors.
3+
* <p>
4+
* Licensed under the Moderne Source Available License (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
* <p>
8+
* https://docs.moderne.io/licensing/moderne-source-available-license
9+
* <p>
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package org.openrewrite.javascript;
17+
18+
import org.jspecify.annotations.Nullable;
19+
import org.junit.jupiter.api.Test;
20+
import org.openrewrite.ExecutionContext;
21+
import org.openrewrite.java.tree.J;
22+
import org.openrewrite.java.tree.MethodCall;
23+
import org.openrewrite.java.tree.TypedTree;
24+
import org.openrewrite.javascript.tree.JS;
25+
import org.openrewrite.test.RewriteTest;
26+
27+
import java.util.concurrent.atomic.AtomicBoolean;
28+
29+
import static org.assertj.core.api.Assertions.assertThat;
30+
import static org.openrewrite.javascript.Assertions.javascript;
31+
32+
/**
33+
* Verify that a Java recipe using {@code postVisit} to update types on {@link TypedTree} nodes
34+
* does not fail on JavaScript source files containing {@link JS.FunctionCall} nodes.
35+
* <p>
36+
* This reproduces the issue where {@link org.openrewrite.java.ChangeType} throws
37+
* {@link UnsupportedOperationException} when its {@code postVisit} calls
38+
* {@link TypedTree#withType} on a {@link JS.FunctionCall}, which requires
39+
* {@link JS.FunctionCall#withFunctionType} instead.
40+
*/
41+
class ChangeTypeAdaptabilityTest implements RewriteTest {
42+
43+
@Test
44+
void functionCallDoesNotThrow() {
45+
AtomicBoolean hasFunctionCall = new AtomicBoolean(false);
46+
rewriteRun(
47+
spec -> spec.recipe(RewriteTest.toRecipe(() -> new JavaScriptVisitor<ExecutionContext>() {
48+
@Override
49+
public @Nullable J postVisit(J tree, ExecutionContext ctx) {
50+
J j = super.postVisit(tree, ctx);
51+
if (j instanceof JS.FunctionCall) {
52+
hasFunctionCall.set(true);
53+
}
54+
// Reproduce what ChangeType.postVisit does: specific handling for
55+
// J.MethodInvocation and J.NewClass, then MethodCall as a catch-all
56+
// for other implementations like JS.FunctionCall, and finally TypedTree
57+
if (j instanceof J.MethodInvocation) {
58+
// handled specifically by ChangeType
59+
} else if (j instanceof J.NewClass) {
60+
// handled specifically by ChangeType
61+
} else if (j instanceof MethodCall) {
62+
MethodCall call = (MethodCall) j;
63+
j = (J) call.withMethodType(call.getMethodType());
64+
} else if (j instanceof TypedTree) {
65+
j = ((TypedTree) j).withType(((TypedTree) j).getType());
66+
}
67+
return j;
68+
}
69+
})),
70+
// getHandler()() produces a JS.FunctionCall because the callee is a call expression
71+
javascript(
72+
"""
73+
var result = getHandler()();
74+
"""
75+
)
76+
);
77+
assertThat(hasFunctionCall.get())
78+
.as("Expected parsed JavaScript to contain a JS.FunctionCall node")
79+
.isTrue();
80+
}
81+
}

0 commit comments

Comments
 (0)