Skip to content

Commit cd4178c

Browse files
Fix ChangeType UnsupportedOperationException on JS FunctionCall nodes (#7239)
* 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. * Move ChangeTypeAdaptabilityTest to integTest source set This test exercises the JS RPC parser and belongs with the other integration tests that are not run in CI.
1 parent 4baf50b commit cd4178c

9 files changed

Lines changed: 90 additions & 4 deletions

File tree

rewrite-csharp/csharp/OpenRewrite/Java/Rpc/JavaReceiver.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,8 @@ public override J VisitClassDeclaration(ClassDeclaration classDecl, RpcReceiveQu
262262
var implements_ = q.Receive(classDecl.Implements, c => VisitContainer(c, q));
263263
var permits = q.Receive(classDecl.Permits, c => VisitContainer(c, q));
264264
var body = q.Receive((J)classDecl.Body, el => (J)VisitNonNull(el, q));
265-
return classDecl.WithId(_pvId).WithPrefix(_pvPrefix).WithMarkers(_pvMarkers).WithLeadingAnnotations(leadingAnnotations!).WithModifiers(modifiers!).WithClassKind((ClassDeclaration.Kind)kind!).WithName((Identifier)name!).WithTypeParameters(typeParameters).WithPrimaryConstructor(primaryConstructor).WithExtends(extends_).WithImplements(implements_).WithPermits(permits).WithBody((Block)body!);
265+
var type = q.Receive(classDecl.Type, t => (JavaType.FullyQualified?)VisitType(t, q));
266+
return classDecl.WithId(_pvId).WithPrefix(_pvPrefix).WithMarkers(_pvMarkers).WithLeadingAnnotations(leadingAnnotations!).WithModifiers(modifiers!).WithClassKind((ClassDeclaration.Kind)kind!).WithName((Identifier)name!).WithTypeParameters(typeParameters).WithPrimaryConstructor(primaryConstructor).WithExtends(extends_).WithImplements(implements_).WithPermits(permits).WithBody((Block)body!).WithType(type);
266267
}
267268

268269
private J VisitClassDeclarationKind(ClassDeclaration.Kind kind, RpcReceiveQueue q)

rewrite-csharp/csharp/OpenRewrite/Java/Rpc/JavaSender.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,7 @@ public override J VisitClassDeclaration(ClassDeclaration classDecl, RpcSendQueue
234234
q.GetAndSend(classDecl, c => c.Implements, impl => VisitContainer(impl, q));
235235
q.GetAndSend(classDecl, c => c.Permits, perm => VisitContainer(perm, q));
236236
q.GetAndSend(classDecl, c => (J)c.Body, j => Visit(j, q));
237+
q.GetAndSend(classDecl, c => AsRef(c.Type), type => VisitType(GetValueNonNull<JavaType>(type), q));
237238
return classDecl;
238239
}
239240

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) {

rewrite-java/src/main/java/org/openrewrite/java/internal/rpc/JavaReceiver.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -146,7 +146,8 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, RpcReceiveQueue q)
146146
.getPadding().withExtends(q.receive(classDecl.getPadding().getExtends(), e -> visitLeftPadded(e, q)))
147147
.getPadding().withImplements(q.receive(classDecl.getPadding().getImplements(), i -> visitContainer(i, q)))
148148
.getPadding().withPermits(q.receive(classDecl.getPadding().getPermits(), p -> visitContainer(p, q)))
149-
.withBody(q.receive(classDecl.getBody(), b -> (J.Block) visitNonNull(b, q)));
149+
.withBody(q.receive(classDecl.getBody(), b -> (J.Block) visitNonNull(b, q)))
150+
.withType(q.receive(classDecl.getType(), t -> (JavaType.FullyQualified) visitType(t, q)));
150151
}
151152

152153
private J.ClassDeclaration.Kind visitClassDeclarationKind(J.ClassDeclaration.Kind kind, RpcReceiveQueue q) {

rewrite-java/src/main/java/org/openrewrite/java/internal/rpc/JavaSender.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,7 @@ public J visitClassDeclaration(J.ClassDeclaration classDecl, RpcSendQueue q) {
140140
q.getAndSend(classDecl, c -> c.getPadding().getImplements(), impl -> visitContainer(impl, q));
141141
q.getAndSend(classDecl, c -> c.getPadding().getPermits(), impl -> visitContainer(impl, q));
142142
q.getAndSend(classDecl, J.ClassDeclaration::getBody, j -> visit(j, q));
143+
q.getAndSend(classDecl, c -> asRef(c.getType()), type -> visitType(getValueNonNull(type), q));
143144
return classDecl;
144145
}
145146

rewrite-javascript/rewrite/src/java/rpc.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -713,6 +713,7 @@ export class JavaSender extends JavaVisitor<RpcSendQueue> {
713713
await q.getAndSend(cls, c => c.implements, impl => this.visitContainer(impl, q));
714714
await q.getAndSend(cls, c => c.permitting, perm => this.visitContainer(perm, q));
715715
await q.getAndSend(cls, c => c.body, body => this.visit(body, q));
716+
await q.getAndSend(cls, c => asRef(c.type), type => this.visitType(type, q));
716717
return cls;
717718
}
718719

@@ -1435,7 +1436,8 @@ export class JavaReceiver extends JavaVisitor<RpcReceiveQueue> {
14351436
extends: await q.receive(cls.extends, ext => this.visitLeftPadded(ext, q)),
14361437
implements: await q.receive(cls.implements, impl => this.visitContainer(impl, q)),
14371438
permitting: await q.receive(cls.permitting, perm => this.visitContainer(perm, q)),
1438-
body: await q.receive(cls.body, body => this.visit(body, q))
1439+
body: await q.receive(cls.body, body => this.visit(body, q)),
1440+
type: await q.receive(cls.type, type => this.visitType(type, q)) as Type.Class
14391441
};
14401442
return updateIfChanged(cls, updates);
14411443
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
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.junit.jupiter.api.Test;
19+
import org.openrewrite.java.ChangeType;
20+
import org.openrewrite.java.tree.J;
21+
import org.openrewrite.javascript.tree.JS;
22+
import org.openrewrite.test.RewriteTest;
23+
24+
import java.util.concurrent.atomic.AtomicBoolean;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.openrewrite.javascript.Assertions.javascript;
28+
29+
/**
30+
* Prove that {@link ChangeType}, written for Java, does not fail on JavaScript source files
31+
* containing {@link JS.FunctionCall} nodes.
32+
*/
33+
class ChangeTypeAdaptabilityTest implements RewriteTest {
34+
35+
@Test
36+
void functionCallDoesNotThrow() {
37+
AtomicBoolean hasFunctionCall = new AtomicBoolean(false);
38+
rewriteRun(
39+
spec -> spec.recipe(new ChangeType("Foo", "Bar", false)),
40+
javascript(
41+
"""
42+
class Foo {
43+
getHandler() {
44+
return function() { return 1; };
45+
}
46+
}
47+
var result = new Foo().getHandler()();
48+
""",
49+
"""
50+
class Bar {
51+
getHandler() {
52+
return function() { return 1; };
53+
}
54+
}
55+
var result = new Bar().getHandler()();
56+
""",
57+
spec -> spec.afterRecipe(cu -> {
58+
new JavaScriptVisitor<Integer>() {
59+
@Override
60+
public JS.FunctionCall visitFunctionCall(JS.FunctionCall functionCall, Integer p) {
61+
hasFunctionCall.set(true);
62+
return functionCall;
63+
}
64+
}.visit(cu, 0);
65+
assertThat(hasFunctionCall.get())
66+
.as("Expected parsed JavaScript to contain a JS.FunctionCall node")
67+
.isTrue();
68+
assertThat(cu.getClasses().getFirst().getType())
69+
.as("Expected JS ClassDeclaration to have type attribution")
70+
.isNotNull();
71+
})
72+
)
73+
);
74+
}
75+
}

rewrite-python/rewrite/src/rewrite/rpc/python_receiver.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -727,10 +727,11 @@ def _visit_j_class_declaration(self, class_decl, q: RpcReceiveQueue):
727727
lambda c: self._receive_container(c, q) if c else None
728728
)
729729
body = q.receive(class_decl.body)
730+
type_ = q.receive(class_decl.type)
730731
return replace_if_changed(class_decl, leading_annotations=leading_annotations, modifiers=modifiers,
731732
kind=kind, name=name, type_parameters=type_parameters,
732733
primary_constructor=primary_constructor, extends=extends, implements=implements,
733-
permits=permits, body=body)
734+
permits=permits, body=body, type=type_)
734735

735736
def _visit_j_class_declaration_kind(self, kind, q: RpcReceiveQueue):
736737
from rewrite.java.tree import ClassDeclaration

rewrite-python/rewrite/src/rewrite/rpc/python_sender.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -678,6 +678,7 @@ def _visit_j_class_declaration(self, class_decl, q: 'RpcSendQueue') -> None:
678678
q.get_and_send(class_decl, lambda x: x.padding.permits if hasattr(x.padding, 'permits') else None,
679679
lambda c: self._visit_container(c, q) if c else None)
680680
q.get_and_send(class_decl, lambda x: x.body, lambda el: self._visit(el, q))
681+
q.get_and_send_as_ref(class_decl, lambda x: x.type, lambda t: self._visit_type(t, q) if t else None)
681682

682683
def _visit_j_class_declaration_kind(self, kind, q: 'RpcSendQueue') -> None:
683684
# Java ClassDeclaration.Kind: preVisit IS automatically called by _visit before this method

0 commit comments

Comments
 (0)