diff --git a/src/main/java/org/openrewrite/java/migrate/jspecify/MoveAnnotationToArrayType.java b/src/main/java/org/openrewrite/java/migrate/jspecify/MoveAnnotationToArrayType.java new file mode 100644 index 0000000000..a28f7db7ac --- /dev/null +++ b/src/main/java/org/openrewrite/java/migrate/jspecify/MoveAnnotationToArrayType.java @@ -0,0 +1,127 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.jspecify; + +import lombok.EqualsAndHashCode; +import lombok.Value; +import org.openrewrite.*; +import org.openrewrite.internal.ListUtils; +import org.openrewrite.java.JavaIsoVisitor; +import org.openrewrite.java.TypeMatcher; +import org.openrewrite.java.search.UsesType; +import org.openrewrite.java.tree.*; + +import org.jspecify.annotations.Nullable; + +import java.util.List; + +import static java.util.Collections.singletonList; + +@EqualsAndHashCode(callSuper = false) +@Value +public class MoveAnnotationToArrayType extends Recipe { + + @Option(displayName = "Annotation type", + description = "The type of annotation to move to the array type. " + + "Should target the pre-migration annotation type to avoid changing the semantics " + + "of pre-existing type-use annotations on object arrays.", + example = "javax.annotation.*ull*") + String annotationType; + + String displayName = "Move annotation to array type"; + + String description = "When an annotation like `@Nullable` is applied to an array type in declaration position, " + + "this recipe moves it to the array brackets. " + + "For example, `@Nullable byte[]` becomes `byte @Nullable[]`. " + + "Best used before `ChangeType` in a migration pipeline, targeting the pre-migration annotation type."; + + @Override + public TreeVisitor getVisitor() { + return Preconditions.check(new UsesType<>(annotationType, null), new JavaIsoVisitor() { + final TypeMatcher typeMatcher = new TypeMatcher(annotationType); + + @Override + public J.MethodDeclaration visitMethodDeclaration(J.MethodDeclaration method, ExecutionContext ctx) { + J.MethodDeclaration md = super.visitMethodDeclaration(method, ctx); + + if (!(md.getReturnTypeExpression() instanceof J.ArrayType)) { + return md; + } + + J.@Nullable Annotation[] match = {null}; + List leading = ListUtils.map(md.getLeadingAnnotations(), a -> { + if (match[0] == null && matchesType(a)) { + match[0] = a; + return null; + } + return a; + }); + if (leading == md.getLeadingAnnotations()) { + return md; + } + md = md.withLeadingAnnotations(leading); + + J.ArrayType arrayType = (J.ArrayType) md.getReturnTypeExpression(); + //noinspection DataFlowIssue + arrayType = arrayType.withAnnotations( + singletonList(match[0].withPrefix(Space.SINGLE_SPACE))); + md = md.withReturnTypeExpression(arrayType); + if (md.getLeadingAnnotations().isEmpty()) { + md = md.withReturnTypeExpression(arrayType.withPrefix( + arrayType.getPrefix().withWhitespace(""))); + } + return autoFormat(md, arrayType, ctx, getCursor().getParentOrThrow()); + } + + @Override + public J.VariableDeclarations visitVariableDeclarations(J.VariableDeclarations multiVariable, ExecutionContext ctx) { + J.VariableDeclarations mv = super.visitVariableDeclarations(multiVariable, ctx); + + if (!(mv.getTypeExpression() instanceof J.ArrayType)) { + return mv; + } + + J.@Nullable Annotation[] match = {null}; + List leading = ListUtils.map(mv.getLeadingAnnotations(), a -> { + if (match[0] == null && matchesType(a)) { + match[0] = a; + return null; + } + return a; + }); + if (leading == mv.getLeadingAnnotations()) { + return mv; + } + mv = mv.withLeadingAnnotations(leading); + + J.ArrayType arrayType = (J.ArrayType) mv.getTypeExpression(); + //noinspection DataFlowIssue + arrayType = arrayType.withAnnotations( + singletonList(match[0].withPrefix(Space.SINGLE_SPACE))); + if (mv.getLeadingAnnotations().isEmpty()) { + arrayType = arrayType.withPrefix(arrayType.getPrefix().withWhitespace("")); + } + mv = mv.withTypeExpression(arrayType); + return autoFormat(mv, arrayType, ctx, getCursor().getParentOrThrow()); + } + + private boolean matchesType(J.Annotation ann) { + JavaType.FullyQualified fq = TypeUtils.asFullyQualified(ann.getType()); + return fq != null && typeMatcher.matches(fq); + } + }); + } +} diff --git a/src/main/resources/META-INF/rewrite/jspecify.yml b/src/main/resources/META-INF/rewrite/jspecify.yml index c3b8a50e05..bbf2217104 100644 --- a/src/main/resources/META-INF/rewrite/jspecify.yml +++ b/src/main/resources/META-INF/rewrite/jspecify.yml @@ -65,6 +65,8 @@ recipeList: version: latest.release onlyIfUsing: javax.annotation.*ull* acceptTransitive: true + - org.openrewrite.java.migrate.jspecify.MoveAnnotationToArrayType: + annotationType: javax.annotation.*ull* - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: javax.annotation.Nullable newFullyQualifiedTypeName: org.jspecify.annotations.Nullable @@ -93,6 +95,8 @@ recipeList: version: 1.0.0 onlyIfUsing: jakarta.annotation.*ull* acceptTransitive: true + # Not moving jakarta annotations to array brackets; they target TYPE_USE, + # so `@Nullable X[]` (element nullable) already differs from `X @Nullable[]` (array nullable). - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: jakarta.annotation.Nullable newFullyQualifiedTypeName: org.jspecify.annotations.Nullable @@ -117,6 +121,8 @@ recipeList: version: 1.0.0 onlyIfUsing: org.jetbrains.annotations.*ull* acceptTransitive: true + # Not moving JetBrains annotations to array brackets; they target TYPE_USE, + # so `@Nullable X[]` (element nullable) already differs from `X @Nullable[]` (array nullable). - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.jetbrains.annotations.Nullable newFullyQualifiedTypeName: org.jspecify.annotations.Nullable @@ -141,6 +147,8 @@ recipeList: version: 1.0.0 onlyIfUsing: org.springframework.lang.*ull* acceptTransitive: true + - org.openrewrite.java.migrate.jspecify.MoveAnnotationToArrayType: + annotationType: io.micrometer.core.lang.*ull* - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: io.micrometer.core.lang.Nullable newFullyQualifiedTypeName: org.jspecify.annotations.Nullable @@ -165,6 +173,8 @@ recipeList: version: 1.0.0 onlyIfUsing: org.springframework.lang.*ull* acceptTransitive: true + - org.openrewrite.java.migrate.jspecify.MoveAnnotationToArrayType: + annotationType: org.springframework.lang.*ull* - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: org.springframework.lang.Nullable newFullyQualifiedTypeName: org.jspecify.annotations.Nullable @@ -189,6 +199,8 @@ recipeList: version: 1.0.0 onlyIfUsing: io.micronaut.core.annotation.*ull* acceptTransitive: true + # Not moving Micronaut annotations to array brackets; they target TYPE_USE, + # so `@Nullable X[]` (element nullable) already differs from `X @Nullable[]` (array nullable). - org.openrewrite.java.ChangeType: oldFullyQualifiedTypeName: io.micronaut.core.annotation.Nullable newFullyQualifiedTypeName: org.jspecify.annotations.Nullable diff --git a/src/main/resources/META-INF/rewrite/recipes.csv b/src/main/resources/META-INF/rewrite/recipes.csv index 26a2aef002..753df7c33f 100644 --- a/src/main/resources/META-INF/rewrite/recipes.csv +++ b/src/main/resources/META-INF/rewrite/recipes.csv @@ -40,7 +40,7 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.R maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.RemovedJaxBModuleProvided,Do not package `java.xml.bind` and `java.activation` modules in WebSphere Liberty applications,"The `java.xml.bind` and `java.activation` modules were removed in Java11. Websphere Liberty provides its own implementation of the modules, which can be used by specifying the `jaxb-2.2` feature in the server.xml file. This recipe updates the `javax.xml.bind` and `javax.activation` dependencies to use the `provided` scope to avoid class loading issues.",5,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.WasDevMvnChangeParentArtifactId,Change `net.wasdev.maven.parent:java8-parent` to `:parent`,This recipe changes the artifactId of the `` tag in the `pom.xml` from `java8-parent` to `parent`.,2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.ComIntelliJAnnotationsToOrgJetbrainsAnnotations,Migrate com.intellij:annotations to org.jetbrains:annotations,This recipe will upgrade old dependency of com.intellij:annotations to the newer org.jetbrains:annotations.,2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JavaBestPractices,Java best practices,"Applies opinionated best practices for Java projects targeting Java 25. This recipe includes the full Java 25 upgrade chain plus additional improvements to code style, API usage, and third-party dependency reduction that go beyond what the version migration recipes apply.",20934,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.JavaBestPractices,Java best practices,"Applies opinionated best practices for Java projects targeting Java 25. This recipe includes the full Java 25 upgrade chain plus additional improvements to code style, API usage, and third-party dependency reduction that go beyond what the version migration recipes apply.",20939,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,,"[{""name"":""org.openrewrite.maven.table.MavenMetadataFailures"",""displayName"":""Maven metadata failures"",""instanceName"":""Maven metadata failures"",""description"":""Attempts to resolve maven metadata that failed."",""columns"":[{""name"":""group"",""type"":""String"",""displayName"":""Group id"",""description"":""The groupId of the artifact for which the metadata download failed.""},{""name"":""artifactId"",""type"":""String"",""displayName"":""Artifact id"",""description"":""The artifactId of the artifact for which the metadata download failed.""},{""name"":""version"",""type"":""String"",""displayName"":""Version"",""description"":""The version of the artifact for which the metadata download failed.""},{""name"":""mavenRepositoryUri"",""type"":""String"",""displayName"":""Maven repository"",""description"":""The URL of the Maven repository that the metadata download failed on.""},{""name"":""snapshots"",""type"":""String"",""displayName"":""Snapshots"",""description"":""Does the repository support snapshots.""},{""name"":""releases"",""type"":""String"",""displayName"":""Releases"",""description"":""Does the repository support releases.""},{""name"":""failure"",""type"":""String"",""displayName"":""Failure"",""description"":""The reason the metadata download failed.""}]}]" maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javaee6,Migrate to JavaEE6,"These recipes help with the Migration to Java EE 6, flagging and updating deprecated methods.",2,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javaee7,Migrate to JavaEE7,"These recipes help with the Migration to Java EE 7, flagging and updating deprecated methods.",8,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javaee8,Migrate to JavaEE8,"These recipes help with the Migration to Java EE 8, flagging and updating deprecated methods.",18,,,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -264,9 +264,9 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.j maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpdateJakartaFacesApi41,Update Jakarta EE Java Faces Dependencies to 4.1.x,Update Jakarta EE Java Faces Dependencies to 4.1.x.,2,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.OmniFacesNamespaceMigration,OmniFaces Namespace Migration,Find and replace legacy OmniFaces namespaces.,3,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jakarta.UpgradeFaces41OpenSourceLibraries,Upgrade Faces open source libraries,Upgrade OmniFaces and MyFaces/Mojarra libraries to Jakarta EE11 versions.,5,,Jakarta,Modernize,Java,,Recipes for migrating to [Jakarta EE](https://jakarta.ee/).,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.AddColumnAnnotation,`@ElementCollection` annotations must be accompanied by a defined `@Column` annotation,"When an attribute is annotated with `@ElementCollection`, a separate table is created for the attribute that includes the attribute -ID and value. In OpenJPA, the column for the annotated attribute is named element, whereas EclipseLink names the column based on -the name of the attribute. To remain compatible with tables that were created with OpenJPA, add a `@Column` annotation with the name +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.AddColumnAnnotation,`@ElementCollection` annotations must be accompanied by a defined `@Column` annotation,"When an attribute is annotated with `@ElementCollection`, a separate table is created for the attribute that includes the attribute +ID and value. In OpenJPA, the column for the annotated attribute is named element, whereas EclipseLink names the column based on +the name of the attribute. To remain compatible with tables that were created with OpenJPA, add a `@Column` annotation with the name attribute set to element.",1,,`javax` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.AddDefaultConstructorToEntityClass,`@Entity` objects with constructors must also have a default constructor,"When a Java Persistence API (JPA) entity class has a constructor with arguments, the class must also have a default, no-argument constructor. The OpenJPA implementation automatically generates the no-argument constructor, but the EclipseLink implementation does not.",1,,`javax` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.javax.AddJaxwsRuntime,Use the latest JAX-WS API and runtime for Jakarta EE 8,"Update build files to use the latest JAX-WS runtime from Jakarta EE 8 to maintain compatibility with Java version 11 or greater. The recipe will add a JAX-WS run-time, in Gradle `compileOnly`+`testImplementation` and Maven `provided` scope, to any project that has a transitive dependency on the JAX-WS API. **The resulting dependencies still use the `javax` namespace, despite the move to the Jakarta artifact**.",3,,`javax` APIs,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, @@ -462,13 +462,14 @@ maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.d maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.datanucleus.UpgradeDataNucleus_5_2,Migrate to DataNucleus 5.2,"Migrate DataNucleus applications to 5.2. This recipe first applies the 5.1 migration, then handles the column mapping package move and query-related property renames introduced in 5.2.",87,,DataNucleus,Modernize,Java,,Recipes for migrating [DataNucleus](https://www.datanucleus.org/) JDO/JPA persistence applications.,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.datanucleus.DataNucleusPackageMoves_5_2,DataNucleus 5.2 package moves,Relocate packages that were moved in DataNucleus 5.2.,2,,DataNucleus,Modernize,Java,,Recipes for migrating [DataNucleus](https://www.datanucleus.org/) JDO/JPA persistence applications.,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.datanucleus.DataNucleusProperties_5_2,DataNucleus 5.2 property migrations,Rename property keys that changed in DataNucleus 5.2.,7,,DataNucleus,Modernize,Java,,Recipes for migrating [DataNucleus](https://www.datanucleus.org/) JDO/JPA persistence applications.,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.JSpecifyBestPractices,JSpecify best practices,"Apply JSpecify best practices, such as migrating off of alternatives, and adding missing `@Nullable` annotations.",32,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateToJSpecify,Migrate to JSpecify,This recipe will migrate to JSpecify annotations from various other nullability annotation standards.,27,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromJavaxAnnotationApi,Migrate from javax annotation API to JSpecify,Migrate from javax annotation API to JSpecify.,6,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromJakartaAnnotationApi,Migrate from Jakarta annotation API to JSpecify,Migrate from Jakarta annotation API to JSpecify.,5,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromJetbrainsAnnotations,Migrate from JetBrains annotations to JSpecify,Migrate from JetBrains annotations to JSpecify.,5,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromMicrometerAnnotations,Migrate from Micrometer annotations to JSpecify,Migrate from Micrometer annotations to JSpecify.,5,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromSpringFrameworkAnnotations,Migrate from Spring Framework annotations to JSpecify,Migrate from Spring Framework annotations to JSpecify.,5,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, -maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromMicronautAnnotations,Migrate from Micronaut Framework annotations to JSpecify,Migrate from Micronaut Framework annotations to JSpecify.,5,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.migrate.jspecify.MoveAnnotationToArrayType,Move annotation to array type,"When an annotation like `@Nullable` is applied to an array type in declaration position, this recipe moves it to the array brackets. For example, `@Nullable byte[]` becomes `byte @Nullable[]`. Best used before `ChangeType` in a migration pipeline, targeting the pre-migration annotation type.",1,,Jspecify,Modernize,Java,,,Modernize your code to best use the project's current JDK version. Take advantage of newly available APIs and reduce the dependency of your code on third party dependencies where there is equivalent functionality in the Java standard library.,Basic building blocks for transforming Java code.,"[{""name"":""annotationType"",""type"":""String"",""displayName"":""Annotation type"",""description"":""The type of annotation to move to the array type. Should target the pre-migration annotation type to avoid changing the semantics of pre-existing type-use annotations on object arrays."",""example"":""javax.annotation.*ull*"",""required"":true}]", +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.JSpecifyBestPractices,JSpecify best practices,"Apply JSpecify best practices, such as migrating off of alternatives, and adding missing `@Nullable` annotations.",37,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateToJSpecify,Migrate to JSpecify,This recipe will migrate to JSpecify annotations from various other nullability annotation standards.,32,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromJavaxAnnotationApi,Migrate from javax annotation API to JSpecify,Migrate from javax annotation API to JSpecify.,7,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromJakartaAnnotationApi,Migrate from Jakarta annotation API to JSpecify,Migrate from Jakarta annotation API to JSpecify.,6,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromJetbrainsAnnotations,Migrate from JetBrains annotations to JSpecify,Migrate from JetBrains annotations to JSpecify.,6,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromMicrometerAnnotations,Migrate from Micrometer annotations to JSpecify,Migrate from Micrometer annotations to JSpecify.,6,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromSpringFrameworkAnnotations,Migrate from Spring Framework annotations to JSpecify,Migrate from Spring Framework annotations to JSpecify.,6,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, +maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.java.jspecify.MigrateFromMicronautAnnotations,Migrate from Micronaut Framework annotations to JSpecify,Migrate from Micronaut Framework annotations to JSpecify.,6,,,JSpecify,Java,,,Recipes for adopting [JSpecify](https://jspecify.dev/) nullability annotations.,Basic building blocks for transforming Java code.,, maven,org.openrewrite.recipe:rewrite-migrate-java,com.google.guava.InlineGuavaMethods,Inline `guava` methods annotated with `@InlineMe`,Automatically generated recipes to inline method calls based on `@InlineMe` annotations discovered in the type table.,66,,,Guava,Google,,,,,, maven,org.openrewrite.recipe:rewrite-migrate-java,org.openrewrite.scala.migrate.UpgradeScala_2_12,Migrate to Scala 2.12.+,Upgrade the Scala version for compatibility with newer Java versions.,2,,,Migrate,Scala,,,,,, diff --git a/src/test/java/org/openrewrite/java/migrate/jspecify/JSpecifyBestPracticesTest.java b/src/test/java/org/openrewrite/java/migrate/jspecify/JSpecifyBestPracticesTest.java index 4a80f44ec0..e232d504aa 100644 --- a/src/test/java/org/openrewrite/java/migrate/jspecify/JSpecifyBestPracticesTest.java +++ b/src/test/java/org/openrewrite/java/migrate/jspecify/JSpecifyBestPracticesTest.java @@ -32,7 +32,80 @@ class JSpecifyBestPracticesTest implements RewriteTest { public void defaults(RecipeSpec spec) { spec .recipeFromResource("/META-INF/rewrite/jspecify.yml", "org.openrewrite.java.jspecify.JSpecifyBestPractices") - .parser(JavaParser.fromJavaVersion().classpath("jsr305", "jakarta.annotation-api", "annotations", "spring-core", "micronaut-core")); + .parser(JavaParser.fromJavaVersion().classpath("jsr305", "jspecify", "jakarta.annotation-api", "annotations", "spring-core", "micronaut-core")); + } + + @Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/934") + @Test + void existingJSpecifyArrayOfNullableElementsNotAffected() { + rewriteRun( + mavenProject("foo", + srcMainJava( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Legacy { + @Nullable + public String[] bar() { + return null; + } + } + """, + """ + import org.jspecify.annotations.Nullable; + + class Legacy { + public String @Nullable[] bar() { + return null; + } + } + """ + ), + //language=java + java( + """ + import org.jspecify.annotations.Nullable; + + class Modern { + @Nullable String[] arrayOfNullableStrings; + + public @Nullable String[] bar() { + return arrayOfNullableStrings; + } + + public void baz(@Nullable String[] a) { + } + } + """ + ) + ), + //language=xml + pomXml( + """ + + 4.0.0 + com.example.foobar + foobar-core + 1.0.0 + + + javax.annotation + javax.annotation-api + 1.3.2 + + + org.jspecify + jspecify + 1.0.0 + + + + """ + ) + ) + ); } @DocumentExample diff --git a/src/test/java/org/openrewrite/java/migrate/jspecify/MoveAnnotationToArrayTypeTest.java b/src/test/java/org/openrewrite/java/migrate/jspecify/MoveAnnotationToArrayTypeTest.java new file mode 100644 index 0000000000..ce6ca2002b --- /dev/null +++ b/src/test/java/org/openrewrite/java/migrate/jspecify/MoveAnnotationToArrayTypeTest.java @@ -0,0 +1,251 @@ +/* + * Copyright 2026 the original author or authors. + *

+ * Licensed under the Moderne Source Available License (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + *

+ * https://docs.moderne.io/licensing/moderne-source-available-license + *

+ * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.openrewrite.java.migrate.jspecify; + +import org.junit.jupiter.api.Test; +import org.openrewrite.DocumentExample; +import org.openrewrite.Issue; +import org.openrewrite.java.JavaParser; +import org.openrewrite.test.RecipeSpec; +import org.openrewrite.test.RewriteTest; + +import static org.openrewrite.java.Assertions.java; + +@Issue("https://github.com/openrewrite/rewrite-migrate-java/issues/934") +class MoveAnnotationToArrayTypeTest implements RewriteTest { + + @Override + public void defaults(RecipeSpec spec) { + spec + .recipe(new MoveAnnotationToArrayType("javax.annotation.*ull*")) + .parser(JavaParser.fromJavaVersion().classpath("jsr305", "jspecify")); + } + + @DocumentExample + @Test + void moveNullableToArrayReturnType() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + @Nullable + public byte[] bar() { + return null; + } + } + """, + """ + import javax.annotation.Nullable; + + class Foo { + public byte @Nullable[] bar() { + return null; + } + } + """ + ) + ); + } + + @Test + void moveNullableToArrayParameter() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + public void baz(@Nullable byte[] a) { + } + } + """, + """ + import javax.annotation.Nullable; + + class Foo { + public void baz(byte @Nullable[] a) { + } + } + """ + ) + ); + } + + @Test + void moveNullableToArrayField() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + @Nullable + public byte[] data; + } + """, + """ + import javax.annotation.Nullable; + + class Foo { + public byte @Nullable[] data; + } + """ + ) + ); + } + + @Test + void moveNullableToObjectArrayReturnType() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + @Nullable + public String[] bar() { + return null; + } + } + """, + """ + import javax.annotation.Nullable; + + class Foo { + public String @Nullable[] bar() { + return null; + } + } + """ + ) + ); + } + + @Test + void noChangeForNonArrayReturnType() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + @Nullable + public String bar() { + return null; + } + } + """ + ) + ); + } + + @Test + void noChangeForNonArrayParameter() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + public void baz(@Nullable String a) { + } + } + """ + ) + ); + } + + @Test + void multiDimensionalArray() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + + class Foo { + @Nullable + public String[][] bar() { + return null; + } + } + """, + """ + import javax.annotation.Nullable; + + class Foo { + public String @Nullable[][] bar() { + return null; + } + } + """ + ) + ); + } + + @Test + void moveNullableToNestedClassArrayField() { + rewriteRun( + //language=java + java( + """ + import javax.annotation.Nullable; + import java.util.Map; + + class Foo { + @Nullable + public Map.Entry[] entries; + } + """, + """ + import javax.annotation.Nullable; + import java.util.Map; + + class Foo { + public Map.Entry @Nullable[] entries; + } + """ + ) + ); + } + + @Test + void noChangeForPreExistingJSpecifyAnnotation() { + rewriteRun( + //language=java + java( + """ + import org.jspecify.annotations.Nullable; + + class Foo { + @Nullable + public String[] bar() { + return null; + } + } + """ + ) + ); + } +}