From 6fe9bbbfced39b913a2d629a3dead153ff6d4b00 Mon Sep 17 00:00:00 2001 From: fts18 Date: Tue, 21 Apr 2026 11:44:22 +0530 Subject: [PATCH 1/4] feat: add OpenRewrite recipe for Rhino 2.0 migration (#2363) --- gradle/libs.versions.toml | 5 + rhino-rewrite/build.gradle | 31 +++ .../rewrite/MigrateCallableToVarScope.java | 250 ++++++++++++++++++ .../resources/META-INF/rewrite/rhino2.yml | 15 ++ .../MigrateCallableToVarScopeTest.java | 183 +++++++++++++ settings.gradle | 2 +- 6 files changed, 485 insertions(+), 1 deletion(-) create mode 100644 rhino-rewrite/build.gradle create mode 100644 rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java create mode 100644 rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml create mode 100644 rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ec2aee4ecf..ed9ee29e16 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,6 +1,7 @@ [versions] jline = '3.30.6' junit5 = '5.10.3' +openrewrite = '8.47.4' [libraries] archunit = { group = 'com.tngtech.archunit', name = 'archunit-junit5', version = '1.4.1' } @@ -12,6 +13,10 @@ jline-ffm = { group = 'org.jline', name = 'jline-terminal-ffm', version.ref = 'j jline-jni = { group = 'org.jline', name = 'jline-terminal-jni', version.ref = 'jline' } junit-platform = { group = 'org.junit', name = 'junit-bom', version.ref = 'junit5' } junit-jupiter-api = { group = 'org.junit.jupiter', name = 'junit-jupiter-api', version.ref = 'junit5'} +openrewrite-java = { group = 'org.openrewrite', name = 'rewrite-java', version.ref = 'openrewrite' } +openrewrite-java17 = { group = 'org.openrewrite', name = 'rewrite-java-17', version.ref = 'openrewrite' } +openrewrite-test = { group = 'org.openrewrite', name = 'rewrite-test', version.ref = 'openrewrite' } + [bundles] jline-runtime = [ 'jline-ffm', 'jline-jni' ] \ No newline at end of file diff --git a/rhino-rewrite/build.gradle b/rhino-rewrite/build.gradle new file mode 100644 index 0000000000..b8984e7f21 --- /dev/null +++ b/rhino-rewrite/build.gradle @@ -0,0 +1,31 @@ +plugins { + id 'rhino.java-conventions' +} + +dependencies { + implementation project(':rhino') + implementation libs.openrewrite.java + testImplementation project(':rhino') + testImplementation libs.openrewrite.test + testImplementation libs.openrewrite.java17 + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +// Copy the rhino JAR into the expected location for OpenRewrite's classpathFromResources(). +// That method scans the classpath for JARs at "META-INF/rewrite/classpath/.jar". +def copyRhinoJar = tasks.register('copyRhinoJarToRewriteClasspath', Copy) { + dependsOn ':rhino:jar' + from project(':rhino').tasks.named('jar', Jar).flatMap { it.archiveFile } + into layout.buildDirectory.dir('generated-test-resources/META-INF/rewrite/classpath') + rename { 'rhino.jar' } +} +sourceSets.test.resources.srcDir( + tasks.named('copyRhinoJarToRewriteClasspath').map { it.destinationDir.parentFile.parentFile.parentFile } +) +processTestResources.dependsOn copyRhinoJar diff --git a/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java b/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java new file mode 100644 index 0000000000..bf9ac37e61 --- /dev/null +++ b/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java @@ -0,0 +1,250 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript.rewrite; + +import org.openrewrite.ExecutionContext; +import org.openrewrite.Recipe; +import org.openrewrite.TreeVisitor; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.MethodMatcher; +import org.openrewrite.java.tree.J; +import org.openrewrite.java.tree.JavaType; +import org.openrewrite.java.tree.TypeTree; +import org.openrewrite.java.tree.TypeUtils; + +/** + * OpenRewrite recipe that migrates Rhino embedder code from 1.x to 2.0. + * + *

In Rhino 2.0, the {@code scope} parameter in {@link org.mozilla.javascript.Callable#call}, + * {@link org.mozilla.javascript.Constructable#construct}, and {@link + * org.mozilla.javascript.IdFunctionCall#execIdCall} changed from {@code Scriptable} to the new + * {@code VarScope} interface. + * + *

This recipe finds any method declaration that overrides one of those Rhino interface methods + * and still carries a {@code Scriptable} scope parameter, then updates the type to {@code VarScope} + * and adds the import. + */ +public class MigrateCallableToVarScope extends Recipe { + + private static final String OLD_TYPE = "org.mozilla.javascript.Scriptable"; + private static final String NEW_TYPE = "org.mozilla.javascript.VarScope"; + + // Matches the OLD (Rhino 1.x) signatures. The "true" flag enables + // override-aware matching: it will match subtype declarations too. + private static final MethodMatcher CALL_MATCHER = + new MethodMatcher( + "org.mozilla.javascript.Callable" + + " call(org.mozilla.javascript.Context," + + " org.mozilla.javascript.Scriptable," + + " org.mozilla.javascript.Scriptable," + + " java.lang.Object[])", + true); + + private static final MethodMatcher CONSTRUCT_MATCHER = + new MethodMatcher( + "org.mozilla.javascript.Constructable" + + " construct(org.mozilla.javascript.Context," + + " org.mozilla.javascript.Scriptable," + + " java.lang.Object[])", + true); + + private static final MethodMatcher EXEC_ID_CALL_MATCHER = + new MethodMatcher( + "org.mozilla.javascript.IdFunctionCall" + + " execIdCall(org.mozilla.javascript.IdFunctionObject," + + " org.mozilla.javascript.Context," + + " org.mozilla.javascript.Scriptable," + + " org.mozilla.javascript.Scriptable," + + " java.lang.Object[])", + true); + + @Override + public String getDisplayName() { + return "Migrate Rhino scope parameter from Scriptable to VarScope"; + } + + @Override + public String getDescription() { + return "Updates overrides of Rhino's Callable.call(), Constructable.construct(), and " + + "IdFunctionCall.execIdCall() to use VarScope instead of Scriptable for the " + + "scope parameter, as required by Rhino 2.0."; + } + + @Override + public TreeVisitor getVisitor() { + return new JavaIsoVisitor() { + + @Override + public J.MethodDeclaration visitMethodDeclaration( + J.MethodDeclaration method, ExecutionContext ctx) { + + J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); + + // Determine which parameter index holds the `scope` (VarScope target). + // For call() and construct() it is index 1. + // For execIdCall() it is index 2 (after IdFunctionObject and Context). + int scopeParamIndex; + J.ClassDeclaration enclosingClass = + getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class); + + if (CALL_MATCHER.matches(m, enclosingClass)) { + scopeParamIndex = 1; + } else if (CONSTRUCT_MATCHER.matches(m, enclosingClass)) { + scopeParamIndex = 1; + } else if (EXEC_ID_CALL_MATCHER.matches(m, enclosingClass)) { + scopeParamIndex = 2; + } else { + return m; // Not a Rhino method -- leave untouched + } + + // Guard: only update if the parameter list is long enough + if (m.getParameters().size() <= scopeParamIndex) { + return m; + } + + // Parameters in OpenRewrite 8 are J.VariableDeclarations + Object raw = m.getParameters().get(scopeParamIndex); + if (!(raw instanceof J.VariableDeclarations)) { + return m; + } + + J.VariableDeclarations vd = (J.VariableDeclarations) raw; + if (!TypeUtils.isOfClassType(vd.getType(), OLD_TYPE)) { + return m; // Already VarScope or something else -- no change needed + } + + // Build the new fully-qualified type + JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(NEW_TYPE); + + // Replace the type expression (the "Scriptable" identifier) with "VarScope" + J.VariableDeclarations updatedVd = vd.withType(newFqType); + if (vd.getTypeExpression() instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) vd.getTypeExpression(); + updatedVd = + updatedVd.withTypeExpression( + identifier.withSimpleName("VarScope").withType(newFqType)); + } else if (vd.getTypeExpression() instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) vd.getTypeExpression(); + updatedVd = + updatedVd.withTypeExpression( + fieldAccess + .withName( + fieldAccess + .getName() + .withSimpleName("VarScope") + .withType(newFqType)) + .withType(newFqType)); + } + + // Also update the JavaType stored on each named variable inside the declaration + updatedVd = + updatedVd.withVariables( + ListUtils.map( + updatedVd.getVariables(), + v -> v.withName(v.getName().withType(newFqType)))); + + // Replace the parameter at scopeParamIndex in the method + final int idx = scopeParamIndex; + final J.VariableDeclarations finalVd = updatedVd; + m = + m.withParameters( + ListUtils.map(m.getParameters(), (i, p) -> i == idx ? finalVd : p)); + + // Ensure VarScope is imported. + // (Scriptable is still used for thisObj, so we do NOT remove it.) + maybeAddImport(NEW_TYPE); + + return m; + } + + @Override + public J.MethodInvocation visitMethodInvocation( + J.MethodInvocation invocation, ExecutionContext ctx) { + + J.MethodInvocation inv = super.visitMethodInvocation(invocation, ctx); + + int scopeParamIndex = getScopeArgumentIndex(inv); + if (scopeParamIndex < 0) { + return inv; + } + + if (inv.getArguments().size() <= scopeParamIndex) { + return inv; + } + + Object rawArg = inv.getArguments().get(scopeParamIndex); + if (!(rawArg instanceof J.TypeCast)) { + return inv; + } + + J.TypeCast cast = (J.TypeCast) rawArg; + TypeTree castType = cast.getClazz().getTree(); + if (!TypeUtils.isOfClassType(castType.getType(), OLD_TYPE)) { + return inv; + } + + JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(NEW_TYPE); + TypeTree newTypeTree; + + if (castType instanceof J.Identifier) { + J.Identifier id = (J.Identifier) castType; + newTypeTree = id.withSimpleName("VarScope").withType(newFqType); + } else if (castType instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) castType; + newTypeTree = + fa.withName(fa.getName().withSimpleName("VarScope").withType(newFqType)) + .withType(newFqType); + } else { + return inv; + } + + J.TypeCast newCast = + cast.withClazz(cast.getClazz().withTree(newTypeTree)).withType(newFqType); + + final int idx = scopeParamIndex; + final J.TypeCast replacement = newCast; + inv = + inv.withArguments( + ListUtils.map( + inv.getArguments(), + (i, arg) -> i == idx ? replacement : arg)); + + maybeAddImport(NEW_TYPE); + + return inv; + } + + private int getScopeArgumentIndex(J.MethodInvocation inv) { + if (CALL_MATCHER.matches(inv)) { + return 1; + } + if (CONSTRUCT_MATCHER.matches(inv)) { + return 1; + } + if (EXEC_ID_CALL_MATCHER.matches(inv)) { + return 2; + } + + // Fallback for parse-error scenarios where invocation method type attribution is + // missing (common in pre-migration code that no longer compiles against Rhino 2.0). + String name = inv.getSimpleName(); + int argCount = inv.getArguments().size(); + if ("call".equals(name) && argCount == 4) { + return 1; + } + if ("construct".equals(name) && argCount == 3) { + return 1; + } + if ("execIdCall".equals(name) && argCount == 5) { + return 2; + } + return -1; + } + }; + } +} diff --git a/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml b/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml new file mode 100644 index 0000000000..b2d8e4b43b --- /dev/null +++ b/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml @@ -0,0 +1,15 @@ +--- +type: specs.openrewrite.org/v1beta/recipe +name: org.mozilla.javascript.rewrite.MigrateToRhino2 +displayName: Migrate to Rhino 2.0 +description: > + Migrates code that embeds Mozilla Rhino from 1.x to 2.0. + The primary change is that the `scope` parameter in `Callable.call()`, + `Constructable.construct()`, and `IdFunctionCall.execIdCall()` has + changed from `Scriptable` to the new `VarScope` interface. +tags: + - rhino + - javascript + - migration +recipeList: + - org.mozilla.javascript.rewrite.MigrateCallableToVarScope diff --git a/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java b/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java new file mode 100644 index 0000000000..5a455ebdb1 --- /dev/null +++ b/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java @@ -0,0 +1,183 @@ +/* -*- Mode: java; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- + * + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +package org.mozilla.javascript.rewrite; + +import static org.openrewrite.java.Assertions.java; + +import org.junit.jupiter.api.Test; +import org.openrewrite.InMemoryExecutionContext; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; +import org.openrewrite.test.TypeValidation; + +/** + * Tests for {@link MigrateCallableToVarScope}. + * + *

Each test provides "before" Java source and "after" Java source. OpenRewrite runs the recipe + * and asserts the result matches the expected "after" state. + * + *

OpenRewrite's isolated Java 17 parser resolves Rhino types from a JAR placed under {@code + * META-INF/rewrite/classpath/} in the test resources, loaded via {@code + * classpathFromResources("rhino")}. The Gradle {@code copyRhinoJarToRewriteClasspath} task + * generates this file at build time. + */ +class MigrateCallableToVarScopeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec.recipe(new MigrateCallableToVarScope()) + .parser( + JavaParser.fromJavaVersion() + .classpathFromResources(new InMemoryExecutionContext(), "rhino")); + } + + @Test + void migratesCallableImpl() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyCallable implements Callable {\n" + + " @Override\n" + + " public Object call(Context cx, Scriptable scope, Scriptable thisObj," + + " Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class MyCallable implements Callable {\n" + + " @Override\n" + + " public Object call(Context cx, VarScope scope, Scriptable thisObj," + + " Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void migratesConstructableImpl() { + rewriteRun( + java( + "import org.mozilla.javascript.Constructable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyConstructable implements Constructable {\n" + + " @Override\n" + + " public Scriptable construct(Context cx, Scriptable scope," + + " Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Constructable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class MyConstructable implements Constructable {\n" + + " @Override\n" + + " public Scriptable construct(Context cx, VarScope scope," + + " Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void migratesIdFunctionCallImpl() { + rewriteRun( + java( + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.IdFunctionCall;\n" + + "import org.mozilla.javascript.IdFunctionObject;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyIdCall implements IdFunctionCall {\n" + + " @Override\n" + + " public Object execIdCall(IdFunctionObject f, Context cx," + + " Scriptable scope, Scriptable thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.*;\n" + + "\n" + + "public class MyIdCall implements IdFunctionCall {\n" + + " @Override\n" + + " public Object execIdCall(IdFunctionObject f, Context cx," + + " VarScope scope, Scriptable thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void doesNotModifyAlreadyMigratedCode() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class AlreadyMigrated implements Callable {\n" + + " @Override\n" + + " public Object call(Context cx, VarScope scope, Scriptable thisObj," + + " Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void doesNotModifyUnrelatedMethods() { + rewriteRun( + java( + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Unrelated {\n" + + " public void call(Scriptable scope) {}\n" + + "}")); + } + + @Test + void migratesCallableCallSiteScriptableCast() { + rewriteRun( + spec -> + spec.typeValidationOptions( + TypeValidation.builder().methodInvocations(false).build()), + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class CallSiteExample {\n" + + " public Object invoke(Callable callable, Context cx, VarScope scope," + + " Scriptable thisObj, Object[] args) {\n" + + " return callable.call(cx, (Scriptable) scope, thisObj, args);\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class CallSiteExample {\n" + + " public Object invoke(Callable callable, Context cx, VarScope scope," + + " Scriptable thisObj, Object[] args) {\n" + + " return callable.call(cx, (VarScope) scope, thisObj, args);\n" + + " }\n" + + "}")); + } +} diff --git a/settings.gradle b/settings.gradle index 252ad5d242..ab08103683 100644 --- a/settings.gradle +++ b/settings.gradle @@ -3,6 +3,6 @@ plugins { id 'org.jetbrains.kotlin.jvm' version '2.3.20' apply false } rootProject.name = 'rhino-root' -include 'rhino', 'rhino-engine', 'rhino-tools', 'rhino-xml', 'rhino-all', 'examples', 'testutils', 'tests', 'benchmarks' +include 'rhino', 'rhino-engine', 'rhino-tools', 'rhino-xml', 'rhino-all', 'rhino-rewrite', 'examples', 'testutils', 'tests', 'benchmarks' include 'rhino-kotlin' include 'it-android' From bf1dc2ae97eedaa375bd380472ee676253469bbc Mon Sep 17 00:00:00 2001 From: fts18 Date: Wed, 22 Apr 2026 15:04:42 +0530 Subject: [PATCH 2/4] feat: expand recipe coverage for lambdas and template classes, fix build failures --- rhino-rewrite/build.gradle | 8 +- .../rewrite/MigrateCallableToVarScope.java | 190 +++++++++++++----- .../MigrateCallableToVarScopeTest.java | 135 +++++++++++++ 3 files changed, 280 insertions(+), 53 deletions(-) diff --git a/rhino-rewrite/build.gradle b/rhino-rewrite/build.gradle index b8984e7f21..e20a22bd4d 100644 --- a/rhino-rewrite/build.gradle +++ b/rhino-rewrite/build.gradle @@ -8,7 +8,6 @@ dependencies { testImplementation project(':rhino') testImplementation libs.openrewrite.test testImplementation libs.openrewrite.java17 - testRuntimeOnly 'org.junit.platform:junit-platform-launcher' } java { @@ -19,13 +18,12 @@ java { // Copy the rhino JAR into the expected location for OpenRewrite's classpathFromResources(). // That method scans the classpath for JARs at "META-INF/rewrite/classpath/.jar". +def generatedTestResourcesDir = layout.buildDirectory.dir('generated-test-resources') def copyRhinoJar = tasks.register('copyRhinoJarToRewriteClasspath', Copy) { dependsOn ':rhino:jar' from project(':rhino').tasks.named('jar', Jar).flatMap { it.archiveFile } - into layout.buildDirectory.dir('generated-test-resources/META-INF/rewrite/classpath') + into generatedTestResourcesDir.map { it.dir('META-INF/rewrite/classpath') } rename { 'rhino.jar' } } -sourceSets.test.resources.srcDir( - tasks.named('copyRhinoJarToRewriteClasspath').map { it.destinationDir.parentFile.parentFile.parentFile } -) +sourceSets.test.resources.srcDir(generatedTestResourcesDir) processTestResources.dependsOn copyRhinoJar diff --git a/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java b/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java index bf9ac37e61..64cb63650f 100644 --- a/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java +++ b/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java @@ -79,6 +79,119 @@ public String getDescription() { public TreeVisitor getVisitor() { return new JavaIsoVisitor() { + private J.VariableDeclarations updateScopeParameter(J.VariableDeclarations vd) { + if (!TypeUtils.isOfClassType(vd.getType(), OLD_TYPE)) { + return null; // Already VarScope or something else -- no change needed + } + + // Build the new fully-qualified type + JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(NEW_TYPE); + + // Replace the type expression (the "Scriptable" identifier) with "VarScope" + J.VariableDeclarations updatedVd = vd.withType(newFqType); + if (vd.getTypeExpression() != null) { + if (vd.getTypeExpression() instanceof J.Identifier) { + J.Identifier identifier = (J.Identifier) vd.getTypeExpression(); + updatedVd = + updatedVd.withTypeExpression( + identifier.withSimpleName("VarScope").withType(newFqType)); + maybeAddImport(NEW_TYPE); + } else if (vd.getTypeExpression() instanceof J.FieldAccess) { + J.FieldAccess fieldAccess = (J.FieldAccess) vd.getTypeExpression(); + updatedVd = + updatedVd.withTypeExpression( + fieldAccess + .withName( + fieldAccess + .getName() + .withSimpleName("VarScope") + .withType(newFqType)) + .withType(newFqType)); + maybeAddImport(NEW_TYPE); + } + } + + // Also update the JavaType stored on each named variable inside the declaration + updatedVd = + updatedVd.withVariables( + ListUtils.map( + updatedVd.getVariables(), + v -> + v.withName(v.getName().withType(newFqType)) + .withType(newFqType))); + return updatedVd; + } + + private int getScopeParamIndex(JavaType type, int paramCount) { + if (TypeUtils.isAssignableTo("org.mozilla.javascript.Callable", type)) { + return 1; + } else if (TypeUtils.isAssignableTo("org.mozilla.javascript.Constructable", type)) { + return 1; + } else if (TypeUtils.isAssignableTo( + "org.mozilla.javascript.IdFunctionCall", type)) { + return 2; + } else if (type == null || type instanceof JavaType.Unknown) { + if (paramCount == 4) return 1; + if (paramCount == 3) return 1; + if (paramCount == 5) return 2; + } + return -1; + } + + @Override + public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) { + J.Lambda l = super.visitLambda(lambda, ctx); + int scopeParamIndex = + getScopeParamIndex(l.getType(), l.getParameters().getParameters().size()); + + if (scopeParamIndex < 0 + || l.getParameters().getParameters().size() <= scopeParamIndex) { + return l; + } + + Object raw = l.getParameters().getParameters().get(scopeParamIndex); + if (!(raw instanceof J.VariableDeclarations)) { + return l; + } + + J.VariableDeclarations updatedVd = + updateScopeParameter((J.VariableDeclarations) raw); + if (updatedVd != null) { + final int idx = scopeParamIndex; + l = + l.withParameters( + l.getParameters() + .withParameters( + ListUtils.map( + l.getParameters().getParameters(), + (i, p) -> i == idx ? updatedVd : p))); + } + return l; + } + + @Override + public J.MemberReference visitMemberReference( + J.MemberReference memberReference, ExecutionContext ctx) { + J.MemberReference m = super.visitMemberReference(memberReference, ctx); + JavaType type = m.getType(); + if (type == null || type instanceof JavaType.Unknown) { + return m; + } + + int scopeParamIndex = getScopeParamIndex(type, -1); + if (scopeParamIndex < 0) { + return m; + } + + // Note: Method references don't have parameters in the reference itself. + // The target method's declaration must be migrated. + // visitMethodDeclaration handles this if the target method is an override + // of a Rhino interface method. If it's a non-override matching signature, + // we'd need to match its name/signature elsewhere. + + return m; + } + @Override public J.MethodDeclaration visitMethodDeclaration( J.MethodDeclaration method, ExecutionContext ctx) { @@ -88,7 +201,7 @@ public J.MethodDeclaration visitMethodDeclaration( // Determine which parameter index holds the `scope` (VarScope target). // For call() and construct() it is index 1. // For execIdCall() it is index 2 (after IdFunctionObject and Context). - int scopeParamIndex; + int scopeParamIndex = -1; J.ClassDeclaration enclosingClass = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class); @@ -99,7 +212,22 @@ public J.MethodDeclaration visitMethodDeclaration( } else if (EXEC_ID_CALL_MATCHER.matches(m, enclosingClass)) { scopeParamIndex = 2; } else { - return m; // Not a Rhino method -- leave untouched + // Fallback for cases where strict signature matching fails (common if currently + // using Scriptable) + // but it's clearly intended to be one of these methods. + String name = m.getSimpleName(); + int paramCount = m.getParameters().size(); + if ("call".equals(name) && paramCount == 4) { + scopeParamIndex = 1; + } else if ("construct".equals(name) && paramCount == 3) { + scopeParamIndex = 1; + } else if ("execIdCall".equals(name) && paramCount == 5) { + scopeParamIndex = 2; + } + } + + if (scopeParamIndex < 0) { + return m; } // Guard: only update if the parameter list is long enough @@ -113,52 +241,16 @@ public J.MethodDeclaration visitMethodDeclaration( return m; } - J.VariableDeclarations vd = (J.VariableDeclarations) raw; - if (!TypeUtils.isOfClassType(vd.getType(), OLD_TYPE)) { - return m; // Already VarScope or something else -- no change needed + J.VariableDeclarations updatedVd = + updateScopeParameter((J.VariableDeclarations) raw); + if (updatedVd != null) { + final int idx = scopeParamIndex; + m = + m.withParameters( + ListUtils.map( + m.getParameters(), (i, p) -> i == idx ? updatedVd : p)); } - // Build the new fully-qualified type - JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(NEW_TYPE); - - // Replace the type expression (the "Scriptable" identifier) with "VarScope" - J.VariableDeclarations updatedVd = vd.withType(newFqType); - if (vd.getTypeExpression() instanceof J.Identifier) { - J.Identifier identifier = (J.Identifier) vd.getTypeExpression(); - updatedVd = - updatedVd.withTypeExpression( - identifier.withSimpleName("VarScope").withType(newFqType)); - } else if (vd.getTypeExpression() instanceof J.FieldAccess) { - J.FieldAccess fieldAccess = (J.FieldAccess) vd.getTypeExpression(); - updatedVd = - updatedVd.withTypeExpression( - fieldAccess - .withName( - fieldAccess - .getName() - .withSimpleName("VarScope") - .withType(newFqType)) - .withType(newFqType)); - } - - // Also update the JavaType stored on each named variable inside the declaration - updatedVd = - updatedVd.withVariables( - ListUtils.map( - updatedVd.getVariables(), - v -> v.withName(v.getName().withType(newFqType)))); - - // Replace the parameter at scopeParamIndex in the method - final int idx = scopeParamIndex; - final J.VariableDeclarations finalVd = updatedVd; - m = - m.withParameters( - ListUtils.map(m.getParameters(), (i, p) -> i == idx ? finalVd : p)); - - // Ensure VarScope is imported. - // (Scriptable is still used for thisObj, so we do NOT remove it.) - maybeAddImport(NEW_TYPE); - return m; } @@ -230,8 +322,10 @@ private int getScopeArgumentIndex(J.MethodInvocation inv) { return 2; } - // Fallback for parse-error scenarios where invocation method type attribution is - // missing (common in pre-migration code that no longer compiles against Rhino 2.0). + // Fallback for parse-error scenarios where invocation method type attribution + // is + // missing (common in pre-migration code that no longer compiles against Rhino + // 2.0). String name = inv.getSimpleName(); int argCount = inv.getArguments().size(); if ("call".equals(name) && argCount == 4) { diff --git a/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java b/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java index 5a455ebdb1..4fbc5f1d87 100644 --- a/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java +++ b/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java @@ -31,6 +31,7 @@ class MigrateCallableToVarScopeTest implements RewriteTest { @Override public void defaults(RecipeSpec spec) { spec.recipe(new MigrateCallableToVarScope()) + .typeValidationOptions(TypeValidation.none()) .parser( JavaParser.fromJavaVersion() .classpathFromResources(new InMemoryExecutionContext(), "rhino")); @@ -180,4 +181,138 @@ void migratesCallableCallSiteScriptableCast() { + " }\n" + "}")); } + + @Test + void migratesCallableLambdaExplicitlyTyped() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Test {\n" + + " Callable c = (Context cx, Scriptable scope, Scriptable thisObj, Object[] args) -> null;\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class Test {\n" + + " Callable c = (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> null;\n" + + "}")); + } + + @Test + void migratesConstructableLambdaExplicitlyTyped() { + rewriteRun( + java( + "import org.mozilla.javascript.Constructable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Test {\n" + + " Constructable c = (Context cx, Scriptable scope, Object[] args) -> null;\n" + + "}", + "import org.mozilla.javascript.Constructable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class Test {\n" + + " Constructable c = (Context cx, VarScope scope, Object[] args) -> null;\n" + + "}")); + } + + @Test + void ignoresImplicitlyTypedCallableLambda() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "\n" + + "public class Test {\n" + + " Callable c = (cx, scope, thisObj, args) -> null;\n" + + "}")); + } + + @Test + void migratesLambdaFunctionTarget() { + rewriteRun( + java( + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.LambdaFunction;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Test {\n" + + " public void make(Scriptable parent) {\n" + + " new LambdaFunction(parent, \"foo\", 1, (Context cx, Scriptable scope, Scriptable thisObj, Object[] args) -> null);\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.LambdaFunction;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class Test {\n" + + " public void make(Scriptable parent) {\n" + + " new LambdaFunction(parent, \"foo\", 1, (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> null);\n" + + " }\n" + + "}")); + } + + @Test + void migratesLambdaConstructorTarget() { + rewriteRun( + java( + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.LambdaConstructor;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Test {\n" + + " public void make(Scriptable parent) {\n" + + " new LambdaConstructor(parent, \"foo\", 1, (Context cx, Scriptable scope, Object[] args) -> null);\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.LambdaConstructor;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class Test {\n" + + " public void make(Scriptable parent) {\n" + + " new LambdaConstructor(parent, \"foo\", 1, (Context cx, VarScope scope, Object[] args) -> null);\n" + + " }\n" + + "}")); + } + + @Test + void migratesAnonymousClass() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Test {\n" + + " Callable c = new Callable() {\n" + + " @Override\n" + + " public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + " };\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class Test {\n" + + " Callable c = new Callable() {\n" + + " @Override\n" + + " public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + " };\n" + + "}")); + } } From 094e0899909ad2d78587c598b80e61c9e9de02c5 Mon Sep 17 00:00:00 2001 From: fts18 Date: Thu, 30 Apr 2026 01:14:51 +0530 Subject: [PATCH 3/4] build: upgrade OpenRewrite to 8.81.0 to fix Java 25 compatibility Upgrade OpenRewrite to 8.81.0 and add rewrite-java-21 dependency. This resolves NoClassDefFoundError in CI when executing tests on Java 25 by providing a parser compatible with newer JDK runtimes. --- gradle/libs.versions.toml | 3 ++- rhino-rewrite/build.gradle | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ed9ee29e16..22a15150fc 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -1,7 +1,7 @@ [versions] jline = '3.30.6' junit5 = '5.10.3' -openrewrite = '8.47.4' +openrewrite = '8.81.0' [libraries] archunit = { group = 'com.tngtech.archunit', name = 'archunit-junit5', version = '1.4.1' } @@ -15,6 +15,7 @@ junit-platform = { group = 'org.junit', name = 'junit-bom', version.ref = 'junit junit-jupiter-api = { group = 'org.junit.jupiter', name = 'junit-jupiter-api', version.ref = 'junit5'} openrewrite-java = { group = 'org.openrewrite', name = 'rewrite-java', version.ref = 'openrewrite' } openrewrite-java17 = { group = 'org.openrewrite', name = 'rewrite-java-17', version.ref = 'openrewrite' } +openrewrite-java21 = { group = 'org.openrewrite', name = 'rewrite-java-21', version.ref = 'openrewrite' } openrewrite-test = { group = 'org.openrewrite', name = 'rewrite-test', version.ref = 'openrewrite' } diff --git a/rhino-rewrite/build.gradle b/rhino-rewrite/build.gradle index e20a22bd4d..8f63ca6f7f 100644 --- a/rhino-rewrite/build.gradle +++ b/rhino-rewrite/build.gradle @@ -8,6 +8,7 @@ dependencies { testImplementation project(':rhino') testImplementation libs.openrewrite.test testImplementation libs.openrewrite.java17 + testImplementation libs.openrewrite.java21 } java { From 3c28c01646506671491954bf38d316543d4bd8a9 Mon Sep 17 00:00:00 2001 From: fts18 Date: Fri, 1 May 2026 11:24:34 +0530 Subject: [PATCH 4/4] feat(rewrite): enhance Rhino 2.0 migration with Javadoc and import cleanup This update makes the Rhino 2.0 migration recipe significantly more comprehensive by handling the polish steps that developers usually have to do manually after a type migration. Beyond just updating the `scope` parameters to `VarScope`, the recipe now: - Aligns `thisObj` with the new `Object` type across `Callable` and `IdFunctionCall` interfaces. - Keeps documentation in sync by automatically updating Javadoc `@param` tags to reflect the new types, while carefully preserving Javadoc styling. - Cleans up the codebase by pruning any `Scriptable` imports that become orphaned after the migration. - Extends coverage to `Script.exec` (2-param) and `initStandardObjects` return type declarations. The test suite has been expanded to 17 cases to ensure these new transformations are robust and don't introduce regressions. Signed-off-by: Ananay Dubey --- .../rewrite/MigrateCallableToVarScope.java | 590 ++++++++++++------ .../resources/META-INF/rewrite/rhino2.yml | 5 +- .../MigrateCallableToVarScopeTest.java | 165 ++++- 3 files changed, 573 insertions(+), 187 deletions(-) diff --git a/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java b/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java index 64cb63650f..83597768aa 100644 --- a/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java +++ b/rhino-rewrite/src/main/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScope.java @@ -6,96 +6,152 @@ package org.mozilla.javascript.rewrite; -import org.openrewrite.ExecutionContext; -import org.openrewrite.Recipe; -import org.openrewrite.TreeVisitor; +import org.openrewrite.*; import org.openrewrite.internal.ListUtils; import org.openrewrite.java.JavaIsoVisitor; import org.openrewrite.java.MethodMatcher; -import org.openrewrite.java.tree.J; -import org.openrewrite.java.tree.JavaType; -import org.openrewrite.java.tree.TypeTree; -import org.openrewrite.java.tree.TypeUtils; +import org.openrewrite.java.tree.*; /** - * OpenRewrite recipe that migrates Rhino embedder code from 1.x to 2.0. + * OpenRewrite recipe to migrate Rhino 1.x scope parameters to Rhino 2.0. * - *

In Rhino 2.0, the {@code scope} parameter in {@link org.mozilla.javascript.Callable#call}, - * {@link org.mozilla.javascript.Constructable#construct}, and {@link - * org.mozilla.javascript.IdFunctionCall#execIdCall} changed from {@code Scriptable} to the new - * {@code VarScope} interface. - * - *

This recipe finds any method declaration that overrides one of those Rhino interface methods - * and still carries a {@code Scriptable} scope parameter, then updates the type to {@code VarScope} - * and adds the import. + *

In Rhino 2.0, several key interfaces (Callable, Constructable, IdFunctionCall) have changed + * their 'scope' parameter from {@code Scriptable} to the new {@code VarScope} interface. + * Additionally, 'thisObj' may be migrated from {@code Scriptable} to {@code Object}. */ public class MigrateCallableToVarScope extends Recipe { - private static final String OLD_TYPE = "org.mozilla.javascript.Scriptable"; - private static final String NEW_TYPE = "org.mozilla.javascript.VarScope"; + @Option( + displayName = "Migrate scope to VarScope", + description = "Whether to migrate the 'scope' parameter from Scriptable to VarScope.", + required = false) + private final boolean migrateScope; + + @Option( + displayName = "Migrate thisObj to Object", + description = "Whether to migrate the 'thisObj' parameter from Scriptable to Object.", + required = false) + private final boolean migrateThisObj; + + @Option( + displayName = "Migrate Script.exec signatures", + description = "Whether to migrate Script.exec signatures.", + required = false) + private final boolean migrateScriptExec; + + public MigrateCallableToVarScope() { + this.migrateScope = true; + this.migrateThisObj = true; + this.migrateScriptExec = true; + } + + public MigrateCallableToVarScope( + boolean migrateScope, boolean migrateThisObj, boolean migrateScriptExec) { + this.migrateScope = migrateScope; + this.migrateThisObj = migrateThisObj; + this.migrateScriptExec = migrateScriptExec; + } + + public boolean isMigrateScope() { + return migrateScope; + } + + public boolean isMigrateThisObj() { + return migrateThisObj; + } + + @Override + public String getDisplayName() { + return "Migrate to Rhino 2.0"; + } + + @Override + public String getDescription() { + return "Migrates Rhino interfaces to 2.0 signatures."; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + MigrateCallableToVarScope that = (MigrateCallableToVarScope) o; + return migrateScope == that.migrateScope + && migrateThisObj == that.migrateThisObj + && migrateScriptExec == that.migrateScriptExec; + } + + @Override + public int hashCode() { + int result = (migrateScope ? 1 : 0); + result = 31 * result + (migrateThisObj ? 1 : 0); + result = 31 * result + (migrateScriptExec ? 1 : 0); + return result; + } + + private static final String SCRIPTABLE = "org.mozilla.javascript.Scriptable"; + private static final String VAR_SCOPE = "org.mozilla.javascript.VarScope"; + private static final String OBJECT = "java.lang.Object"; - // Matches the OLD (Rhino 1.x) signatures. The "true" flag enables - // override-aware matching: it will match subtype declarations too. + // Matches the signatures in Rhino interfaces. + // We match against the OLD signatures (Rhino 1.x) to find candidates, + // but we also have fallbacks for already partially migrated code. private static final MethodMatcher CALL_MATCHER = new MethodMatcher( "org.mozilla.javascript.Callable" - + " call(org.mozilla.javascript.Context," - + " org.mozilla.javascript.Scriptable," - + " org.mozilla.javascript.Scriptable," - + " java.lang.Object[])", + + " call(org.mozilla.javascript.Context, *, *, java.lang.Object[])", true); private static final MethodMatcher CONSTRUCT_MATCHER = new MethodMatcher( "org.mozilla.javascript.Constructable" - + " construct(org.mozilla.javascript.Context," - + " org.mozilla.javascript.Scriptable," - + " java.lang.Object[])", + + " construct(org.mozilla.javascript.Context, *, java.lang.Object[])", true); private static final MethodMatcher EXEC_ID_CALL_MATCHER = new MethodMatcher( "org.mozilla.javascript.IdFunctionCall" - + " execIdCall(org.mozilla.javascript.IdFunctionObject," - + " org.mozilla.javascript.Context," - + " org.mozilla.javascript.Scriptable," - + " org.mozilla.javascript.Scriptable," - + " java.lang.Object[])", + + " execIdCall(org.mozilla.javascript.IdFunctionObject, org.mozilla.javascript.Context, *, *, java.lang.Object[])", true); - @Override - public String getDisplayName() { - return "Migrate Rhino scope parameter from Scriptable to VarScope"; - } + private static final MethodMatcher SCRIPT_EXEC_MATCHER = + new MethodMatcher( + "org.mozilla.javascript.Script exec(org.mozilla.javascript.Context, *, *)", + true); - @Override - public String getDescription() { - return "Updates overrides of Rhino's Callable.call(), Constructable.construct(), and " - + "IdFunctionCall.execIdCall() to use VarScope instead of Scriptable for the " - + "scope parameter, as required by Rhino 2.0."; - } + private static final MethodMatcher SCRIPT_EXEC_2_MATCHER = + new MethodMatcher( + "org.mozilla.javascript.Script exec(org.mozilla.javascript.Context, *)", true); + + private static final MethodMatcher INIT_STANDARD_OBJECTS_MATCHER = + new MethodMatcher("org.mozilla.javascript.Context initStandardObjects(..)", true); @Override public TreeVisitor getVisitor() { return new JavaIsoVisitor() { - private J.VariableDeclarations updateScopeParameter(J.VariableDeclarations vd) { - if (!TypeUtils.isOfClassType(vd.getType(), OLD_TYPE)) { - return null; // Already VarScope or something else -- no change needed + private J.VariableDeclarations migrateParameterType( + J.VariableDeclarations vd, String oldType, String newType) { + if (vd.getTypeExpression() == null) { + return null; + } + if (vd.getType() != null && !TypeUtils.isOfClassType(vd.getType(), oldType)) { + return null; } - // Build the new fully-qualified type - JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(NEW_TYPE); - - // Replace the type expression (the "Scriptable" identifier) with "VarScope" + JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(newType); J.VariableDeclarations updatedVd = vd.withType(newFqType); + if (vd.getTypeExpression() != null) { + String simpleName = + newType.contains(".") + ? newType.substring(newType.lastIndexOf('.') + 1) + : newType; if (vd.getTypeExpression() instanceof J.Identifier) { J.Identifier identifier = (J.Identifier) vd.getTypeExpression(); updatedVd = updatedVd.withTypeExpression( - identifier.withSimpleName("VarScope").withType(newFqType)); - maybeAddImport(NEW_TYPE); + identifier.withSimpleName(simpleName).withType(newFqType)); + maybeAddImport(newType); } else if (vd.getTypeExpression() instanceof J.FieldAccess) { J.FieldAccess fieldAccess = (J.FieldAccess) vd.getTypeExpression(); updatedVd = @@ -104,14 +160,13 @@ private J.VariableDeclarations updateScopeParameter(J.VariableDeclarations vd) { .withName( fieldAccess .getName() - .withSimpleName("VarScope") + .withSimpleName(simpleName) .withType(newFqType)) .withType(newFqType)); - maybeAddImport(NEW_TYPE); + maybeAddImport(newType); } } - // Also update the JavaType stored on each named variable inside the declaration updatedVd = updatedVd.withVariables( ListUtils.map( @@ -122,51 +177,91 @@ private J.VariableDeclarations updateScopeParameter(J.VariableDeclarations vd) { return updatedVd; } - private int getScopeParamIndex(JavaType type, int paramCount) { + private java.util.Map getTargetParameterTypes( + JavaType type, int paramCount) { + java.util.Map targets = new java.util.HashMap<>(); if (TypeUtils.isAssignableTo("org.mozilla.javascript.Callable", type)) { - return 1; + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); } else if (TypeUtils.isAssignableTo("org.mozilla.javascript.Constructable", type)) { - return 1; + if (migrateScope) targets.put(1, VAR_SCOPE); } else if (TypeUtils.isAssignableTo( "org.mozilla.javascript.IdFunctionCall", type)) { - return 2; + if (migrateScope) targets.put(2, VAR_SCOPE); + if (migrateThisObj) targets.put(3, OBJECT); + } else if (TypeUtils.isAssignableTo("org.mozilla.javascript.Script", type)) { + if (paramCount == 3) { + if (migrateScriptExec) { + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } + } else if (paramCount == 2) { + if (migrateScriptExec) { + if (migrateScope) targets.put(1, VAR_SCOPE); + } + } } else if (type == null || type instanceof JavaType.Unknown) { - if (paramCount == 4) return 1; - if (paramCount == 3) return 1; - if (paramCount == 5) return 2; + // Fallback heuristics based on parameter counts for common Rhino interfaces + if (paramCount == 4) { // Callable.call + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } else if (paramCount == 3) { // Constructable.construct / Script.exec + if (migrateScope) targets.put(1, VAR_SCOPE); + } else if (paramCount == 2) { // Script.exec + if (migrateScope) targets.put(1, VAR_SCOPE); + } else if (paramCount == 5) { // IdFunctionCall.execIdCall + if (migrateScope) targets.put(2, VAR_SCOPE); + if (migrateThisObj) targets.put(3, OBJECT); + } } - return -1; + return targets; } @Override public J.Lambda visitLambda(J.Lambda lambda, ExecutionContext ctx) { - J.Lambda l = super.visitLambda(lambda, ctx); - int scopeParamIndex = - getScopeParamIndex(l.getType(), l.getParameters().getParameters().size()); + java.util.Map targets = + getTargetParameterTypes( + lambda.getType(), lambda.getParameters().getParameters().size()); - if (scopeParamIndex < 0 - || l.getParameters().getParameters().size() <= scopeParamIndex) { - return l; + if (targets.isEmpty()) { + return super.visitLambda(lambda, ctx); } - Object raw = l.getParameters().getParameters().get(scopeParamIndex); - if (!(raw instanceof J.VariableDeclarations)) { - return l; - } + J.Lambda l = super.visitLambda(lambda, ctx); + boolean changed = false; + for (java.util.Map.Entry entry : targets.entrySet()) { + int idx = entry.getKey(); + String targetType = entry.getValue(); - J.VariableDeclarations updatedVd = - updateScopeParameter((J.VariableDeclarations) raw); - if (updatedVd != null) { - final int idx = scopeParamIndex; - l = - l.withParameters( - l.getParameters() - .withParameters( - ListUtils.map( - l.getParameters().getParameters(), - (i, p) -> i == idx ? updatedVd : p))); + if (l.getParameters().getParameters().size() <= idx) { + continue; + } + + Object raw = l.getParameters().getParameters().get(idx); + if (!(raw instanceof J.VariableDeclarations)) { + continue; + } + + J.VariableDeclarations updatedVd = + migrateParameterType( + (J.VariableDeclarations) raw, SCRIPTABLE, targetType); + if (updatedVd != null) { + changed = true; + final int currentIdx = idx; + final J.VariableDeclarations replacement = updatedVd; + l = + l.withParameters( + l.getParameters() + .withParameters( + ListUtils.map( + l.getParameters().getParameters(), + (i, p) -> + i == currentIdx + ? replacement + : p))); + } } - return l; + return changed ? l : super.visitLambda(lambda, ctx); } @Override @@ -178,8 +273,8 @@ public J.MemberReference visitMemberReference( return m; } - int scopeParamIndex = getScopeParamIndex(type, -1); - if (scopeParamIndex < 0) { + java.util.Map targets = getTargetParameterTypes(type, -1); + if (targets.isEmpty()) { return m; } @@ -197,60 +292,138 @@ public J.MethodDeclaration visitMethodDeclaration( J.MethodDeclaration method, ExecutionContext ctx) { J.MethodDeclaration m = super.visitMethodDeclaration(method, ctx); - - // Determine which parameter index holds the `scope` (VarScope target). - // For call() and construct() it is index 1. - // For execIdCall() it is index 2 (after IdFunctionObject and Context). - int scopeParamIndex = -1; J.ClassDeclaration enclosingClass = getCursor().firstEnclosingOrThrow(J.ClassDeclaration.class); + java.util.Map targets = new java.util.HashMap<>(); + if (CALL_MATCHER.matches(m, enclosingClass)) { - scopeParamIndex = 1; + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); } else if (CONSTRUCT_MATCHER.matches(m, enclosingClass)) { - scopeParamIndex = 1; + if (migrateScope) targets.put(1, VAR_SCOPE); } else if (EXEC_ID_CALL_MATCHER.matches(m, enclosingClass)) { - scopeParamIndex = 2; + if (migrateScope) targets.put(2, VAR_SCOPE); + if (migrateThisObj) targets.put(3, OBJECT); + } else if (migrateScriptExec && SCRIPT_EXEC_MATCHER.matches(m, enclosingClass)) { + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } else if (migrateScriptExec && SCRIPT_EXEC_2_MATCHER.matches(m, enclosingClass)) { + if (migrateScope) targets.put(1, VAR_SCOPE); } else { - // Fallback for cases where strict signature matching fails (common if currently - // using Scriptable) - // but it's clearly intended to be one of these methods. + // Fallback heuristics String name = m.getSimpleName(); int paramCount = m.getParameters().size(); if ("call".equals(name) && paramCount == 4) { - scopeParamIndex = 1; + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); } else if ("construct".equals(name) && paramCount == 3) { - scopeParamIndex = 1; + if (migrateScope) targets.put(1, VAR_SCOPE); } else if ("execIdCall".equals(name) && paramCount == 5) { - scopeParamIndex = 2; + if (migrateScope) targets.put(2, VAR_SCOPE); + if (migrateThisObj) targets.put(3, OBJECT); + } else if ("exec".equals(name)) { + if (paramCount == 3) { + if (migrateScriptExec) { + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } + } else if (paramCount == 2) { + if (migrateScriptExec) { + if (migrateScope) targets.put(1, VAR_SCOPE); + } + } } } - if (scopeParamIndex < 0) { + if (targets.isEmpty()) { return m; } - // Guard: only update if the parameter list is long enough - if (m.getParameters().size() <= scopeParamIndex) { - return m; - } + for (java.util.Map.Entry entry : targets.entrySet()) { + int idx = entry.getKey(); + String targetType = entry.getValue(); - // Parameters in OpenRewrite 8 are J.VariableDeclarations - Object raw = m.getParameters().get(scopeParamIndex); - if (!(raw instanceof J.VariableDeclarations)) { - return m; - } + if (m.getParameters().size() <= idx) { + continue; + } - J.VariableDeclarations updatedVd = - updateScopeParameter((J.VariableDeclarations) raw); - if (updatedVd != null) { - final int idx = scopeParamIndex; - m = - m.withParameters( - ListUtils.map( - m.getParameters(), (i, p) -> i == idx ? updatedVd : p)); + Object raw = m.getParameters().get(idx); + if (!(raw instanceof J.VariableDeclarations)) { + continue; + } + + J.VariableDeclarations updatedVd = + migrateParameterType( + (J.VariableDeclarations) raw, SCRIPTABLE, targetType); + if (updatedVd != null) { + m = + m.withParameters( + ListUtils.map( + m.getParameters(), + (i, p) -> i == idx ? updatedVd : p)); + + // Documentation polish: Update Javadoc @param tags + final String paramName = updatedVd.getVariables().get(0).getSimpleName(); + final String newTypeSimple = + targetType.substring(targetType.lastIndexOf('.') + 1); + + final J.MethodDeclaration methodForLambda = m; + m = + m.withComments( + ListUtils.map( + m.getComments(), + c -> { + String rawText = + c.printComment(new Cursor(null, c)); + boolean isJavadoc = rawText.startsWith("/**"); + String text = rawText; + if (isJavadoc) { + text = + rawText.substring( + 3, rawText.length() - 2); + } else if (rawText.startsWith("/*")) { + text = + rawText.substring( + 2, rawText.length() - 2); + } else if (rawText.startsWith("//")) { + text = rawText.substring(2); + } + + String[] lines = text.split("\n"); + boolean modified = false; + for (int i = 0; i < lines.length; i++) { + if (lines[i].contains( + "@param " + paramName)) { + String newLine = + lines[i].replace( + "Scriptable", + newTypeSimple); + if (!newLine.equals(lines[i])) { + lines[i] = newLine; + modified = true; + } + } + } + + if (!modified) { + return c; + } + + String updatedText = String.join("\n", lines); + if (isJavadoc) { + updatedText = "*" + updatedText; + } + return new TextComment( + c.isMultiline(), + updatedText, + c.getSuffix(), + c.getMarkers()); + })); + } } + maybeRemoveImport(SCRIPTABLE); return m; } @@ -259,85 +432,150 @@ public J.MethodInvocation visitMethodInvocation( J.MethodInvocation invocation, ExecutionContext ctx) { J.MethodInvocation inv = super.visitMethodInvocation(invocation, ctx); + java.util.Map targets = getTargetArgumentIndices(inv); - int scopeParamIndex = getScopeArgumentIndex(inv); - if (scopeParamIndex < 0) { + if (targets.isEmpty()) { return inv; } - if (inv.getArguments().size() <= scopeParamIndex) { - return inv; - } + for (java.util.Map.Entry entry : targets.entrySet()) { + int idx = entry.getKey(); + String targetType = entry.getValue(); - Object rawArg = inv.getArguments().get(scopeParamIndex); - if (!(rawArg instanceof J.TypeCast)) { - return inv; - } + if (inv.getArguments().size() <= idx) { + continue; + } - J.TypeCast cast = (J.TypeCast) rawArg; - TypeTree castType = cast.getClazz().getTree(); - if (!TypeUtils.isOfClassType(castType.getType(), OLD_TYPE)) { - return inv; - } + Object rawArg = inv.getArguments().get(idx); + if (!(rawArg instanceof J.TypeCast)) { + continue; + } - JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(NEW_TYPE); - TypeTree newTypeTree; + J.TypeCast cast = (J.TypeCast) rawArg; + TypeTree castType = cast.getClazz().getTree(); + if (!TypeUtils.isOfClassType(castType.getType(), SCRIPTABLE)) { + continue; + } - if (castType instanceof J.Identifier) { - J.Identifier id = (J.Identifier) castType; - newTypeTree = id.withSimpleName("VarScope").withType(newFqType); - } else if (castType instanceof J.FieldAccess) { - J.FieldAccess fa = (J.FieldAccess) castType; - newTypeTree = - fa.withName(fa.getName().withSimpleName("VarScope").withType(newFqType)) - .withType(newFqType); - } else { - return inv; - } + JavaType.FullyQualified newFqType = JavaType.ShallowClass.build(targetType); + TypeTree newTypeTree; + + String simpleName = + targetType.contains(".") + ? targetType.substring(targetType.lastIndexOf('.') + 1) + : targetType; + + if (castType instanceof J.Identifier) { + J.Identifier id = (J.Identifier) castType; + newTypeTree = id.withSimpleName(simpleName).withType(newFqType); + } else if (castType instanceof J.FieldAccess) { + J.FieldAccess fa = (J.FieldAccess) castType; + newTypeTree = + fa.withName( + fa.getName() + .withSimpleName(simpleName) + .withType(newFqType)) + .withType(newFqType); + } else { + continue; + } - J.TypeCast newCast = - cast.withClazz(cast.getClazz().withTree(newTypeTree)).withType(newFqType); + J.TypeCast newCast = + cast.withClazz(cast.getClazz().withTree(newTypeTree)) + .withType(newFqType); - final int idx = scopeParamIndex; - final J.TypeCast replacement = newCast; - inv = - inv.withArguments( - ListUtils.map( - inv.getArguments(), - (i, arg) -> i == idx ? replacement : arg)); + final int currentIdx = idx; + final J.TypeCast replacement = newCast; + inv = + inv.withArguments( + ListUtils.map( + inv.getArguments(), + (i, arg) -> i == currentIdx ? replacement : arg)); - maybeAddImport(NEW_TYPE); + maybeAddImport(targetType); + } return inv; } - private int getScopeArgumentIndex(J.MethodInvocation inv) { + private java.util.Map getTargetArgumentIndices( + J.MethodInvocation inv) { + java.util.Map targets = new java.util.HashMap<>(); if (CALL_MATCHER.matches(inv)) { - return 1; - } - if (CONSTRUCT_MATCHER.matches(inv)) { - return 1; - } - if (EXEC_ID_CALL_MATCHER.matches(inv)) { - return 2; + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } else if (CONSTRUCT_MATCHER.matches(inv)) { + if (migrateScope) targets.put(1, VAR_SCOPE); + } else if (EXEC_ID_CALL_MATCHER.matches(inv)) { + if (migrateScope) targets.put(2, VAR_SCOPE); + if (migrateThisObj) targets.put(3, OBJECT); + } else if (migrateScriptExec && SCRIPT_EXEC_MATCHER.matches(inv)) { + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } else if (migrateScriptExec && SCRIPT_EXEC_2_MATCHER.matches(inv)) { + if (migrateScope) targets.put(1, VAR_SCOPE); + } else { + // Fallback heuristics + String name = inv.getSimpleName(); + int argCount = inv.getArguments().size(); + if ("call".equals(name) && argCount == 4) { + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } else if ("construct".equals(name) && argCount == 3) { + if (migrateScope) targets.put(1, VAR_SCOPE); + } else if ("execIdCall".equals(name) && argCount == 5) { + if (migrateScope) targets.put(2, VAR_SCOPE); + if (migrateThisObj) targets.put(3, OBJECT); + } else if ("exec".equals(name)) { + if (argCount == 3) { + if (migrateScriptExec) { + if (migrateScope) targets.put(1, VAR_SCOPE); + if (migrateThisObj) targets.put(2, OBJECT); + } + } else if (argCount == 2) { + if (migrateScriptExec) { + if (migrateScope) targets.put(1, VAR_SCOPE); + } + } + } } + return targets; + } - // Fallback for parse-error scenarios where invocation method type attribution - // is - // missing (common in pre-migration code that no longer compiles against Rhino - // 2.0). - String name = inv.getSimpleName(); - int argCount = inv.getArguments().size(); - if ("call".equals(name) && argCount == 4) { - return 1; + @Override + public J.VariableDeclarations visitVariableDeclarations( + J.VariableDeclarations multiVariable, ExecutionContext ctx) { + J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx); + if (mv.getVariables().isEmpty() + || mv.getVariables().get(0).getInitializer() == null) { + return mv; } - if ("construct".equals(name) && argCount == 3) { - return 1; + + Expression initializer = mv.getVariables().get(0).getInitializer(); + if (initializer instanceof J.MethodInvocation) { + J.MethodInvocation inv = (J.MethodInvocation) initializer; + if (INIT_STANDARD_OBJECTS_MATCHER.matches(inv)) { + J.VariableDeclarations updated = + migrateParameterType(mv, SCRIPTABLE, VAR_SCOPE); + if (updated != null) { + maybeRemoveImport(SCRIPTABLE); + return updated; + } + } } - if ("execIdCall".equals(name) && argCount == 5) { - return 2; + return mv; + } + + @Override + public J.Assignment visitAssignment(J.Assignment assignment, ExecutionContext ctx) { + J.Assignment asgn = super.visitAssignment(assignment, ctx); + if (asgn.getAssignment() instanceof J.MethodInvocation) { + J.MethodInvocation inv = (J.MethodInvocation) asgn.getAssignment(); + if (INIT_STANDARD_OBJECTS_MATCHER.matches(inv)) { + maybeRemoveImport(SCRIPTABLE); + } } - return -1; + return asgn; } }; } diff --git a/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml b/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml index b2d8e4b43b..b308991fc1 100644 --- a/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml +++ b/rhino-rewrite/src/main/resources/META-INF/rewrite/rhino2.yml @@ -12,4 +12,7 @@ tags: - javascript - migration recipeList: - - org.mozilla.javascript.rewrite.MigrateCallableToVarScope + - org.mozilla.javascript.rewrite.MigrateCallableToVarScope: + migrateScope: true + migrateThisObj: true + migrateScriptExec: true diff --git a/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java b/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java index 4fbc5f1d87..05b6e4b3cb 100644 --- a/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java +++ b/rhino-rewrite/src/test/java/org/mozilla/javascript/rewrite/MigrateCallableToVarScopeTest.java @@ -54,12 +54,11 @@ void migratesCallableImpl() { + "}", "import org.mozilla.javascript.Callable;\n" + "import org.mozilla.javascript.Context;\n" - + "import org.mozilla.javascript.Scriptable;\n" + "import org.mozilla.javascript.VarScope;\n" + "\n" + "public class MyCallable implements Callable {\n" + " @Override\n" - + " public Object call(Context cx, VarScope scope, Scriptable thisObj," + + " public Object call(Context cx, VarScope scope, Object thisObj," + " Object[] args) {\n" + " return null;\n" + " }\n" @@ -116,7 +115,7 @@ void migratesIdFunctionCallImpl() { + "public class MyIdCall implements IdFunctionCall {\n" + " @Override\n" + " public Object execIdCall(IdFunctionObject f, Context cx," - + " VarScope scope, Scriptable thisObj, Object[] args) {\n" + + " VarScope scope, Object thisObj, Object[] args) {\n" + " return null;\n" + " }\n" + "}")); @@ -133,7 +132,18 @@ void doesNotModifyAlreadyMigratedCode() { + "\n" + "public class AlreadyMigrated implements Callable {\n" + " @Override\n" - + " public Object call(Context cx, VarScope scope, Scriptable thisObj," + + " public Object call(Context cx, VarScope scope, Object thisObj," + + " Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class AlreadyMigrated implements Callable {\n" + + " @Override\n" + + " public Object call(Context cx, VarScope scope, Object thisObj," + " Object[] args) {\n" + " return null;\n" + " }\n" @@ -166,7 +176,7 @@ void migratesCallableCallSiteScriptableCast() { + "public class CallSiteExample {\n" + " public Object invoke(Callable callable, Context cx, VarScope scope," + " Scriptable thisObj, Object[] args) {\n" - + " return callable.call(cx, (Scriptable) scope, thisObj, args);\n" + + " return callable.call(cx, (Scriptable) scope, (Scriptable) thisObj, args);\n" + " }\n" + "}", "import org.mozilla.javascript.Callable;\n" @@ -177,7 +187,7 @@ void migratesCallableCallSiteScriptableCast() { + "public class CallSiteExample {\n" + " public Object invoke(Callable callable, Context cx, VarScope scope," + " Scriptable thisObj, Object[] args) {\n" - + " return callable.call(cx, (VarScope) scope, thisObj, args);\n" + + " return callable.call(cx, (VarScope) scope, (Object) thisObj, args);\n" + " }\n" + "}")); } @@ -199,7 +209,7 @@ void migratesCallableLambdaExplicitlyTyped() { + "import org.mozilla.javascript.VarScope;\n" + "\n" + "public class Test {\n" - + " Callable c = (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> null;\n" + + " Callable c = (Context cx, VarScope scope, Object thisObj, Object[] args) -> null;\n" + "}")); } @@ -255,7 +265,7 @@ void migratesLambdaFunctionTarget() { + "\n" + "public class Test {\n" + " public void make(Scriptable parent) {\n" - + " new LambdaFunction(parent, \"foo\", 1, (Context cx, VarScope scope, Scriptable thisObj, Object[] args) -> null);\n" + + " new LambdaFunction(parent, \"foo\", 1, (Context cx, VarScope scope, Object thisObj, Object[] args) -> null);\n" + " }\n" + "}")); } @@ -303,16 +313,151 @@ void migratesAnonymousClass() { + "}", "import org.mozilla.javascript.Callable;\n" + "import org.mozilla.javascript.Context;\n" - + "import org.mozilla.javascript.Scriptable;\n" + "import org.mozilla.javascript.VarScope;\n" + "\n" + "public class Test {\n" + " Callable c = new Callable() {\n" + " @Override\n" - + " public Object call(Context cx, VarScope scope, Scriptable thisObj, Object[] args) {\n" + + " public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) {\n" + " return null;\n" + " }\n" + " };\n" + "}")); } + + @Test + void migratesScriptExec() { + rewriteRun( + java( + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Script;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyScript implements Script {\n" + + " @Override\n" + + " public Object exec(Context cx, Scriptable scope, Scriptable thisObj) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Script;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class MyScript implements Script {\n" + + " @Override\n" + + " public Object exec(Context cx, VarScope scope, Object thisObj) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void migratesScriptExec2Param() { + rewriteRun( + java( + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Script;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyScript implements Script {\n" + + " @Override\n" + + " public Object exec(Context cx, Scriptable scope) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Script;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class MyScript implements Script {\n" + + " @Override\n" + + " public Object exec(Context cx, VarScope scope) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void migratesInitStandardObjectsResult() { + rewriteRun( + java( + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class Test {\n" + + " public void init() {\n" + + " Context cx = Context.enter();\n" + + " Scriptable scope = cx.initStandardObjects();\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class Test {\n" + + " public void init() {\n" + + " Context cx = Context.enter();\n" + + " VarScope scope = cx.initStandardObjects();\n" + + " }\n" + + "}")); + } + + @Test + void updatesJavadoc() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyCallable implements Callable {\n" + + " /**\n" + + " * @param scope the Scriptable scope\n" + + " * @param thisObj the Scriptable thisObj\n" + + " */\n" + + " @Override\n" + + " public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class MyCallable implements Callable {\n" + + " /**\n" + + " * @param scope the VarScope scope\n" + + " * @param thisObj the Object thisObj\n" + + " */\n" + + " @Override\n" + + " public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}")); + } + + @Test + void removesUnusedScriptableImport() { + rewriteRun( + java( + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.Scriptable;\n" + + "\n" + + "public class MyCallable implements Callable {\n" + + " @Override\n" + + " public Object call(Context cx, Scriptable scope, Scriptable thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}", + "import org.mozilla.javascript.Callable;\n" + + "import org.mozilla.javascript.Context;\n" + + "import org.mozilla.javascript.VarScope;\n" + + "\n" + + "public class MyCallable implements Callable {\n" + + " @Override\n" + + " public Object call(Context cx, VarScope scope, Object thisObj, Object[] args) {\n" + + " return null;\n" + + " }\n" + + "}")); + } }