diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java
deleted file mode 100644
index 24a8bf6f9e..0000000000
--- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoAutowiredOnConstructor.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*
- * Copyright 2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * 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.spring;
-
-import org.openrewrite.ExecutionContext;
-import org.openrewrite.Preconditions;
-import org.openrewrite.Recipe;
-import org.openrewrite.TreeVisitor;
-import org.openrewrite.internal.ListUtils;
-import org.openrewrite.java.AnnotationMatcher;
-import org.openrewrite.java.JavaIsoVisitor;
-import org.openrewrite.java.RemoveAnnotationVisitor;
-import org.openrewrite.java.search.FindAnnotations;
-import org.openrewrite.java.search.UsesType;
-import org.openrewrite.java.tree.J;
-import org.openrewrite.java.tree.Statement;
-
-public class NoAutowiredOnConstructor extends Recipe {
- private static final AnnotationMatcher AUTOWIRED_ANNOTATION_MATCHER =
- new AnnotationMatcher("@org.springframework.beans.factory.annotation.Autowired(true)");
-
- @Override
- public String getDisplayName() {
- return "Remove the `@Autowired` annotation on inferred constructor";
- }
-
- @Override
- public String getDescription() {
- return "Spring can infer an autowired constructor when there is a single constructor on the bean. " +
- "This recipe removes unneeded `@Autowired` annotations on constructors.";
- }
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return Preconditions.check(new UsesType<>("org.springframework.beans.factory.annotation.Autowired", false), new JavaIsoVisitor() {
- @Override
- public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
- J.ClassDeclaration cd = super.visitClassDeclaration(classDecl, ctx);
-
- int constructorCount = 0;
- for (Statement s : cd.getBody().getStatements()) {
- if (isConstructor(s)) {
- constructorCount++;
- if (constructorCount > 1) {
- return cd;
- }
- }
- }
-
- // Lombok can also provide a constructor, so keep `@Autowired` on constructors if found
- if (!FindAnnotations.find(cd, "@lombok.*Constructor").isEmpty()) {
- return cd;
- }
-
- // `@ConfigurationProperties` classes usually use field injection, so keep `@Autowired` on constructors
- if (!FindAnnotations.find(cd, "@org.springframework.boot.context.properties.ConfigurationProperties").isEmpty()) {
- return cd;
- }
-
- return cd.withBody(cd.getBody().withStatements(
- ListUtils.map(cd.getBody().getStatements(), s -> {
- if (!isConstructor(s)) {
- return s;
- }
- maybeRemoveImport("org.springframework.beans.factory.annotation.Autowired");
- return (Statement) new RemoveAnnotationVisitor(AUTOWIRED_ANNOTATION_MATCHER).visit(s, ctx, getCursor());
- })
- ));
- }
- });
- }
-
- private static boolean isConstructor(Statement s) {
- return s instanceof J.MethodDeclaration && ((J.MethodDeclaration) s).isConstructor();
- }
-}
diff --git a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java b/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java
deleted file mode 100644
index 7a3534a86a..0000000000
--- a/headless-services/commons/commons-rewrite/src/main/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterface.java
+++ /dev/null
@@ -1,70 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * 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.spring;
-
-import org.openrewrite.ExecutionContext;
-import org.openrewrite.Preconditions;
-import org.openrewrite.Recipe;
-import org.openrewrite.TreeVisitor;
-import org.openrewrite.java.AnnotationMatcher;
-import org.openrewrite.java.JavaIsoVisitor;
-import org.openrewrite.java.RemoveAnnotationVisitor;
-import org.openrewrite.java.search.UsesType;
-import org.openrewrite.java.tree.J;
-import org.openrewrite.java.tree.JavaType;
-import org.openrewrite.java.tree.TypeUtils;
-
-public class NoRepoAnnotationOnRepoInterface extends Recipe {
-
- private static final String INTERFACE_REPOSITORY = "org.springframework.data.repository.Repository";
- private static final String ANNOTATION_REPOSITORY = "org.springframework.stereotype.Repository";
-
- @Override
- public String getDisplayName() {
- return "Remove unnecessary `@Repository` annotation from Spring Data `Repository` sub-interface";
- }
-
- @Override
- public String getDescription() {
- return "Removes superfluous `@Repository` annotation from Spring Data `Repository` sub-interfaces.";
- }
-
- @Override
- public TreeVisitor, ExecutionContext> getVisitor() {
- return Preconditions.check(new UsesType<>(ANNOTATION_REPOSITORY, false), new JavaIsoVisitor() {
- @Override
- public J.ClassDeclaration visitClassDeclaration(J.ClassDeclaration classDecl, ExecutionContext ctx) {
- J.ClassDeclaration c = super.visitClassDeclaration(classDecl, ctx);
- if (c.getKind() == J.ClassDeclaration.Kind.Type.Interface) {
- boolean hasRepoAnnotation = c.getLeadingAnnotations().stream().anyMatch(annotation -> {
- if (annotation.getArguments() == null || annotation.getArguments().isEmpty() ||
- annotation.getArguments().get(0) instanceof J.Empty) {
- JavaType.FullyQualified type = TypeUtils.asFullyQualified(annotation.getType());
- return type != null && ANNOTATION_REPOSITORY.equals(type.getFullyQualifiedName());
- }
- return false;
- });
- if (hasRepoAnnotation && TypeUtils.isAssignableTo(INTERFACE_REPOSITORY, c.getType())) {
- maybeRemoveImport(ANNOTATION_REPOSITORY);
- return (J.ClassDeclaration) new RemoveAnnotationVisitor(new AnnotationMatcher("@" + ANNOTATION_REPOSITORY))
- .visit(c, ctx, getCursor().getParentOrThrow());
- }
- }
- return c;
- }
- });
- }
-}
diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java
deleted file mode 100644
index aa29076768..0000000000
--- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoAutowiredOnConstructorTest.java
+++ /dev/null
@@ -1,588 +0,0 @@
-/*
- * Copyright 2021 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * 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.spring;
-
-import org.junit.jupiter.api.Test;
-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;
-
-class NoAutowiredOnConstructorTest implements RewriteTest {
-
- @Override
- public void defaults(RecipeSpec spec) {
- spec.recipe(new NoAutowiredOnConstructor())
- .parser(JavaParser.fromJavaVersion().classpath("spring-beans", "spring-boot", "spring-context", "spring-core"));
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void removeLeadingAutowiredAnnotation() {
- //language=java
- rewriteRun(
- java("@org.springframework.stereotype.Component public class TestSourceA {}"),
- java("@org.springframework.stereotype.Component public class TestSourceB {}"),
- java("@org.springframework.stereotype.Component public class TestSourceC {}"),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
-
- @Autowired
- public class TestConfiguration {
- private final TestSourceA testSourceA;
- private TestSourceB testSourceB;
-
- @Autowired
- private TestSourceC testSourceC;
-
- @Autowired
- public TestConfiguration(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
-
- @Autowired
- public void setTestSourceB(TestSourceB testSourceB) {
- this.testSourceB = testSourceB;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Autowired;
-
- @Autowired
- public class TestConfiguration {
- private final TestSourceA testSourceA;
- private TestSourceB testSourceB;
-
- @Autowired
- private TestSourceC testSourceC;
-
- public TestConfiguration(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
-
- @Autowired
- public void setTestSourceB(TestSourceB testSourceB) {
- this.testSourceB = testSourceB;
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void removeLeadingAutowiredAnnotationNoModifiers() {
- //language=java
- rewriteRun(
- java("@org.springframework.stereotype.Component public class TestSourceA {}"),
- java("@org.springframework.stereotype.Component public class TestSourceB {}"),
- java("@org.springframework.stereotype.Component public class TestSourceC {}"),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class TestConfiguration {
- private final TestSourceA testSourceA;
- private TestSourceB testSourceB;
-
- @Autowired
- private TestSourceC testSourceC;
-
- @Autowired
- TestConfiguration(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
-
- @Autowired
- public void setTestSourceB(TestSourceB testSourceB) {
- this.testSourceB = testSourceB;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class TestConfiguration {
- private final TestSourceA testSourceA;
- private TestSourceB testSourceB;
-
- @Autowired
- private TestSourceC testSourceC;
-
- TestConfiguration(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
-
- @Autowired
- public void setTestSourceB(TestSourceB testSourceB) {
- this.testSourceB = testSourceB;
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void removeAutowiredWithMultipleAnnotation() {
- //language=java
- rewriteRun(
- java("@org.springframework.stereotype.Component public class TestSourceA {}"),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos1 {
- private final TestSourceA testSourceA;
-
- @Autowired
- @Deprecated
- @Qualifier
- public AnnotationPos1(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos1 {
- private final TestSourceA testSourceA;
-
- @Deprecated
- @Qualifier
- public AnnotationPos1(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """
- ),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos2 {
- private final TestSourceA testSourceA;
-
- @Deprecated
- @Autowired
- @Qualifier
- public AnnotationPos2(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos2 {
- private final TestSourceA testSourceA;
-
- @Deprecated
- @Qualifier
- public AnnotationPos2(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """
- ),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos3 {
- private final TestSourceA testSourceA;
-
- @Deprecated
- @Qualifier
- @Autowired
- public AnnotationPos3(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos3 {
- private final TestSourceA testSourceA;
-
- @Deprecated
- @Qualifier
- public AnnotationPos3(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void removeAutowiredWithMultipleInLineAnnotation() {
- //language=java
- rewriteRun(
- java("@org.springframework.stereotype.Component public class TestSourceA {}"),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos1 {
- private final TestSourceA testSourceA;
-
- @Autowired @Deprecated @Qualifier
- public AnnotationPos1(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos1 {
- private final TestSourceA testSourceA;
-
- @Deprecated @Qualifier
- public AnnotationPos1(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """
- ),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos2 {
- private final TestSourceA testSourceA;
-
- @Deprecated @Autowired @Qualifier
- public AnnotationPos2(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos2 {
- private final TestSourceA testSourceA;
-
- @Deprecated @Qualifier
- public AnnotationPos2(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """
- ),
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos3 {
- private final TestSourceA testSourceA;
-
- @Deprecated @Qualifier @Autowired
- public AnnotationPos3(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """,
- """
- import org.springframework.beans.factory.annotation.Qualifier;
-
- public class AnnotationPos3 {
- private final TestSourceA testSourceA;
-
- @Deprecated @Qualifier
- public AnnotationPos3(TestSourceA testSourceA) {
- this.testSourceA = testSourceA;
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void oneNamePrefixAnnotation() {
- //language=java
- rewriteRun(
- java(
- """
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @Autowired DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """,
- """
- import javax.sql.DataSource;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void multipleNamePrefixAnnotationsPos1() {
- //language=java
- rewriteRun(
- java(
- """
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @Autowired @Deprecated DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """,
- """
- import javax.sql.DataSource;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @Deprecated DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void multipleNamePrefixAnnotationsPos2() {
- //language=java
- rewriteRun(
- java(
- """
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @SuppressWarnings("") @Autowired @Deprecated DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """,
- """
- import javax.sql.DataSource;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @SuppressWarnings("") @Deprecated DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void multipleNamePrefixAnnotationsPos3() {
- //language=java
- rewriteRun(
- java(
- """
- import javax.sql.DataSource;
- import org.springframework.beans.factory.annotation.Autowired;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @SuppressWarnings("") @Deprecated @Autowired DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """,
- """
- import javax.sql.DataSource;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public @SuppressWarnings("") @Deprecated DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """
- )
- );
- }
-
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/78")
- @Test
- void keepAutowiredAnnotationsWhenMultipleConstructorsExist() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.core.io.Resource;
- import java.io.PrintStream;
-
- public class MyAppResourceService {
- private final Resource someResource;
- private final PrintStream printStream;
-
- public MyAppResourceService(Resource someResource) {
- this.someResource = someResource;
- this.printStream = System.out;
- }
-
- @Autowired
- public MyAppResourceService(Resource someResource, PrintStream printStream) {
- this.someResource = someResource;
- this.printStream = printStream;
- }
- }
- """
- )
- );
- }
-
- @Test
- void optionalAutowiredAnnotations() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import javax.sql.DataSource;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- public DatabaseConfiguration(@Autowired(required = false) DataSource dataSource) {
- }
- }
- """
- )
- );
- }
-
- @Test
- void noAutowiredAnnotations() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.context.annotation.Primary;
- import javax.sql.DataSource;
-
- public class DatabaseConfiguration {
- private final DataSource dataSource;
-
- @Primary
- public DatabaseConfiguration(DataSource dataSource) {
- }
- }
- """
- )
- );
- }
-
- @Test
- void ignoreConfigurationProperties() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.boot.context.properties.ConfigurationProperties;
- import org.springframework.core.env.Environment;
- @ConfigurationProperties
- public class ArchivingWorkflowListenerProperties {
- private final Environment environment;
- @Autowired
- public ArchivingWorkflowListenerProperties(Environment environment) {
- this.environment = environment;
- }
- }
- """
- )
- );
- }
-
- @Test
- @Issue("https://github.com/openrewrite/rewrite-spring/issues/479")
- void ignoreLombokConstructors() {
- //language=java
- rewriteRun(
- java(
- """
- package lombok;
-
- import java.lang.annotation.ElementType;
- import java.lang.annotation.Retention;
- import java.lang.annotation.RetentionPolicy;
- import java.lang.annotation.Target;
-
- @Target({ElementType.TYPE})
- @Retention(RetentionPolicy.SOURCE)
- public @interface NoArgsConstructor {
- }
- """
- ),
- java(
- """
- import lombok.NoArgsConstructor;
- import org.springframework.beans.factory.annotation.Autowired;
- import org.springframework.core.env.Environment;
- @NoArgsConstructor
- public class ArchivingWorkflowListenerProperties {
- private final Environment environment;
- @Autowired
- public ArchivingWorkflowListenerProperties(Environment environment) {
- this.environment = environment;
- }
- }
- """
- )
- );
- }
-}
diff --git a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java b/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java
deleted file mode 100644
index 0d4058aa64..0000000000
--- a/headless-services/commons/commons-rewrite/src/test/java/org/openrewrite/java/spring/NoRepoAnnotationOnRepoInterfaceTest.java
+++ /dev/null
@@ -1,255 +0,0 @@
-/*
- * Copyright 2022 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * 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.spring;
-
-import org.junit.jupiter.api.Test;
-import org.openrewrite.DocumentExample;
-import org.openrewrite.java.JavaParser;
-import org.openrewrite.test.RecipeSpec;
-import org.openrewrite.test.RewriteTest;
-
-import static org.openrewrite.java.Assertions.java;
-
-class NoRepoAnnotationOnRepoInterfaceTest implements RewriteTest {
-
- @Override
- public void defaults(RecipeSpec spec) {
- spec.recipe(new NoRepoAnnotationOnRepoInterface())
- .parser(JavaParser.fromJavaVersion().classpath("spring-context", "spring-beans", "spring-data"));
- }
-
- @DocumentExample
- @Test
- void simpleCase() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.stereotype.Repository;
-
- @Repository
- public interface MyRepo extends org.springframework.data.repository.Repository {
- }
- """,
- """
-
- public interface MyRepo extends org.springframework.data.repository.Repository {
- }
- """
- )
- );
- }
-
- @Test
- void simpleCaseWithNoParameters() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.stereotype.Repository;
-
- @Repository( )
- public interface MyRepo extends org.springframework.data.repository.Repository {
- }
- """,
- """
-
- public interface MyRepo extends org.springframework.data.repository.Repository {
- }
- """
- )
- );
- }
-
- @Test
- void crudRepoClass() {
- //language=java
- rewriteRun(
- java(
- """
- import java.util.Optional;
-
- import org.springframework.data.repository.CrudRepository;
- import org.springframework.stereotype.Repository;
-
- @Repository
- public class MyRepo implements CrudRepository {
-
- @Override
- public S save(S entity) {
- return null;
- }
-
- @Override
- public Iterable saveAll(Iterable entities) {
- return null;
- }
-
- @Override
- public Optional findById(String id) {
- return Optional.empty();
- }
-
- @Override
- public boolean existsById(String id) {
- return false;
- }
-
- @Override
- public Iterable findAll() {
- return null;
- }
-
- @Override
- public Iterable findAllById(Iterable ids) {
- return null;
- }
-
- @Override
- public long count() {
- return 0;
- }
-
- @Override
- public void deleteById(String id) {
- }
-
- @Override
- public void delete(String entity) {
- }
-
- @Override
- public void deleteAllById(Iterable extends String> ids) {
- }
-
- @Override
- public void deleteAll(Iterable extends String> entities) {
- }
-
- @Override
- public void deleteAll() {
- }
-
- }
- """
- )
- );
- }
-
- @Test
- void crudRepoInterface() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.data.repository.CrudRepository;
- import org.springframework.stereotype.Repository;
-
- @Repository
- public interface MyRepo extends CrudRepository {
-
- }
- """,
- """
- import org.springframework.data.repository.CrudRepository;
-
- public interface MyRepo extends CrudRepository {
-
- }
- """
- )
- );
- }
-
- @Test
- void crudRepoInterfaceWithMultipleAnnotations() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.data.repository.CrudRepository;
- import org.springframework.stereotype.Repository;
-
- @Repository
- @Deprecated
- public interface MyRepo extends CrudRepository {
-
- }
- """,
- """
- import org.springframework.data.repository.CrudRepository;
-
- @Deprecated
- public interface MyRepo extends CrudRepository {
-
- }
- """
- )
- );
- }
-
- @Test
- void repoAnnotationWithParameters() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.data.repository.CrudRepository;
- import org.springframework.stereotype.Repository;
-
- @Repository("myRepoBean")
- public interface MyRepo extends CrudRepository {
-
- }
- """
- )
- );
- }
-
- @Test
- void noRepoSubclass() {
- //language=java
- rewriteRun(
- java(
- """
- import java.util.List;
-
- import org.springframework.stereotype.Repository;
-
- @Repository
- public interface MyRepo extends List {
- }
- """
- )
- );
- }
-
- @Test
- void noRepoAnnotation() {
- //language=java
- rewriteRun(
- java(
- """
- import org.springframework.data.repository.CrudRepository;
-
- public interface MyRepo extends CrudRepository {
- }
- """
- )
- );
- }
-
-}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoring.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoring.java
index 326b254758..929e37ebfd 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoring.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoring.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2026 Broadcom
+ * Copyright (c) 2026 Broadcom, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,11 +11,12 @@
package org.springframework.ide.vscode.boot.java.jdt.refactoring;
import org.eclipse.jdt.core.dom.AST;
-import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.Modifier.ModifierKeyword;
+import org.eclipse.jdt.core.dom.NodeFinder;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
@@ -82,16 +83,18 @@ private ModifierKeyword getModifierKeyword(Visibility visibility) {
}
private MethodDeclaration findMethodAtOffset(CompilationUnit cu, int offset) {
- MethodDeclaration[] result = new MethodDeclaration[1];
- cu.accept(new ASTVisitor() {
- @Override
- public boolean visit(MethodDeclaration node) {
- if (node.getStartPosition() == offset) {
- result[0] = node;
+ ASTNode node = NodeFinder.perform(cu, offset, 0);
+ while (node != null) {
+ if (node instanceof MethodDeclaration m) {
+ int start = m.getName().getStartPosition();
+ int end = start + m.getName().getLength();
+ if (offset >= start && offset <= end) {
+ return m;
}
- return result[0] == null; // stop visiting if found
+ return null;
}
- });
- return result[0];
+ node = node.getParent();
+ }
+ return null;
}
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtils.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtils.java
index c667b1110f..78ad8dc79a 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtils.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtils.java
@@ -11,11 +11,24 @@
package org.springframework.ide.vscode.boot.java.jdt.refactoring;
import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Set;
import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.ASTVisitor;
+import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
+import org.eclipse.jdt.core.dom.Name;
+import org.eclipse.jdt.core.dom.QualifiedName;
+import org.eclipse.jdt.core.dom.SimpleName;
+import org.eclipse.jdt.core.dom.StructuralPropertyDescriptor;
import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
import org.eclipse.lsp4j.TextDocumentEdit;
@@ -35,6 +48,137 @@
*/
public final class JdtRefactorUtils {
+ public static void removeImports(CompilationUnit cu, ASTRewrite rewrite, String... fqns) {
+ Set fqnsToCheck = new HashSet<>();
+ Map> fqnToImports = new HashMap<>();
+
+ for (String fqn : fqns) {
+ for (Object importObj : cu.imports()) {
+ ImportDeclaration imp = (ImportDeclaration) importObj;
+ if (!imp.isOnDemand() && imp.getName().getFullyQualifiedName().equals(fqn)) {
+ fqnsToCheck.add(fqn);
+ fqnToImports.computeIfAbsent(fqn, k -> new ArrayList<>()).add(imp);
+ }
+ }
+ }
+
+ if (fqnsToCheck.isEmpty()) {
+ return;
+ }
+
+ Set usedFqns = getUsedTypes(cu, rewrite, fqnsToCheck);
+
+ ListRewrite importsRewrite = null;
+ for (String fqn : fqnsToCheck) {
+ if (!usedFqns.contains(fqn)) {
+ if (importsRewrite == null) {
+ importsRewrite = rewrite.getListRewrite(cu, CompilationUnit.IMPORTS_PROPERTY);
+ }
+ for (ImportDeclaration imp : fqnToImports.get(fqn)) {
+ importsRewrite.remove(imp, null);
+ }
+ }
+ }
+ }
+
+ private static Set getUsedTypes(CompilationUnit cu, ASTRewrite rewrite, Set fqnsToCheck) {
+ Set usedFqns = new HashSet<>();
+
+ cu.accept(new ASTVisitor() {
+
+ private void checkTypeRef(Name node) {
+ if (usedFqns.size() == fqnsToCheck.size()) return; // All found
+
+ if (node == null) return;
+
+ // Get the leftmost qualifier
+ while (node.isQualifiedName()) {
+ node = ((QualifiedName) node).getQualifier();
+ }
+
+ IBinding binding = node.resolveBinding();
+ String fqn = null;
+
+ if (binding instanceof ITypeBinding) {
+ fqn = ((ITypeBinding) binding).getErasure().getQualifiedName();
+ }
+
+ if (fqn != null && fqnsToCheck.contains(fqn) && !usedFqns.contains(fqn)) {
+ if (survivesRewrite(node, rewrite)) {
+ usedFqns.add(fqn);
+ }
+ }
+ }
+
+ @Override
+ public boolean visit(SimpleName node) {
+ // If we get here directly, it might be a static reference
+ if (!isInsideImport(node)) {
+ checkTypeRef(node);
+ }
+ return true;
+ }
+
+ });
+ return usedFqns;
+ }
+
+ private static boolean isInsideImport(ASTNode node) {
+ ASTNode current = node;
+ while (current != null) {
+ if (current instanceof ImportDeclaration) {
+ return true;
+ }
+ current = current.getParent();
+ }
+ return false;
+ }
+
+ private static boolean survivesRewrite(ASTNode node, ASTRewrite rewrite) {
+ ASTNode current = node;
+ while (current != null) {
+ ASTNode parent = current.getParent();
+ if (parent != null) {
+ StructuralPropertyDescriptor prop = current.getLocationInParent();
+ if (prop != null) {
+ if (prop.isChildListProperty()) {
+ ListRewrite listRewrite = rewrite.getListRewrite(parent, (ChildListPropertyDescriptor) prop);
+
+ List> originalList = listRewrite.getOriginalList();
+ List> rewrittenList = listRewrite.getRewrittenList();
+
+ boolean inOriginal = false;
+ for (Object o : originalList) {
+ if (o == current) {
+ inOriginal = true;
+ break;
+ }
+ }
+
+ boolean inRewritten = false;
+ for (Object o : rewrittenList) {
+ if (o == current) {
+ inRewritten = true;
+ break;
+ }
+ }
+
+ if (inOriginal && !inRewritten) {
+ return false; // Removed from list
+ }
+ } else {
+ Object rewrittenNode = rewrite.get(parent, prop);
+ if (rewrittenNode != current) {
+ return false; // Replaced or removed
+ }
+ }
+ }
+ }
+ current = parent;
+ }
+ return true;
+ }
+
/**
* Add an import for the given {@link ClassType} to the compilation unit, unless
* the import is unnecessary.
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/RemoveAnnotationRefactoring.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/RemoveAnnotationRefactoring.java
new file mode 100644
index 0000000000..10b5773976
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/RemoveAnnotationRefactoring.java
@@ -0,0 +1,84 @@
+/*******************************************************************************
+ * Copyright (c) 2026 Broadcom, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VMware, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.ide.vscode.boot.java.jdt.refactoring;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.eclipse.jdt.core.dom.ASTNode;
+import org.eclipse.jdt.core.dom.Annotation;
+import org.eclipse.jdt.core.dom.ChildListPropertyDescriptor;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.ITypeBinding;
+import org.eclipse.jdt.core.dom.NodeFinder;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jdt.core.dom.rewrite.ListRewrite;
+
+/**
+ * A JDT-based refactoring that removes annotations identified by their start positions.
+ *
+ * Pass one or more annotation offsets to remove specific annotations.
+ * When used with a single offset this corresponds to a node-scoped quickfix.
+ * When used with multiple offsets (all occurrences in a file) this corresponds
+ * to a file-scoped "fix all" quickfix.
+ */
+public class RemoveAnnotationRefactoring implements JdtRefactoring {
+
+ private final int[] annotationOffsets;
+
+ /**
+ * @param annotationOffsets start positions of the annotation nodes to remove
+ */
+ public RemoveAnnotationRefactoring(int... annotationOffsets) {
+ this.annotationOffsets = annotationOffsets;
+ }
+
+ @Override
+ public void apply(ASTRewrite rewrite, CompilationUnit cu) {
+ Set fqnsToCheck = new HashSet<>();
+
+ for (int offset : annotationOffsets) {
+ Annotation annotation = findAnnotationAtOffset(cu, offset);
+ if (annotation != null) {
+ ASTNode parent = annotation.getParent();
+ ChildListPropertyDescriptor property = (ChildListPropertyDescriptor) annotation.getLocationInParent();
+ ListRewrite modifiersRewrite = rewrite.getListRewrite(parent, property);
+ modifiersRewrite.remove(annotation, null);
+
+ ITypeBinding binding = annotation.resolveTypeBinding();
+ if (binding != null) {
+ fqnsToCheck.add(binding.getErasure().getQualifiedName());
+ }
+ }
+ }
+
+ if (!fqnsToCheck.isEmpty()) {
+ JdtRefactorUtils.removeImports(cu, rewrite, fqnsToCheck.toArray(new String[fqnsToCheck.size()]));
+ }
+ }
+
+ private static Annotation findAnnotationAtOffset(CompilationUnit cu, int offset) {
+ ASTNode node = NodeFinder.perform(cu, offset, 0);
+ while (node != null) {
+ if (node instanceof Annotation a) {
+ int start = a.getStartPosition();
+ int end = a.getTypeName().getStartPosition() + a.getTypeName().getLength();
+ if (offset >= start && offset <= end) {
+ return a;
+ }
+ return null;
+ }
+ node = node.getParent();
+ }
+ return null;
+ }
+
+}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java
index 7c0495ea81..e10c5785de 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/BeanMethodNotPublicReconciler.java
@@ -12,6 +12,7 @@
import java.lang.reflect.Field;
import java.net.URI;
+import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
@@ -69,8 +70,8 @@ public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUni
return new ASTVisitor() {
- private final List problemOffsets = new java.util.ArrayList<>();
- private final List problems = new java.util.ArrayList<>();
+ private final List problemOffsets = new ArrayList<>();
+ private final List problems = new ArrayList<>();
@Override
public boolean visit(SingleMemberAnnotation node) {
@@ -148,7 +149,7 @@ private void visitAnnotation(IJavaProject project, CompilationUnit cu, URI docUr
method.getName().getStartPosition(), method.getName().getLength()));
addQuickFixes(cu, docUri, problem, method);
- problemOffsets.add(method.getStartPosition());
+ problemOffsets.add(method.getName().getStartPosition());
problems.add(problem);
problemCollector.accept(problem);
@@ -176,7 +177,7 @@ private void addQuickFixes(CompilationUnit cu, URI docUri, ReconcileProblemImpl
if (quickfixType != null) {
String uri = docUri.toASCIIString();
JdtFixDescriptor descriptor = new JdtFixDescriptor(
- new ChangeMethodVisibilityRefactoring(Visibility.PACKAGE_PRIVATE, method.getStartPosition()),
+ new ChangeMethodVisibilityRefactoring(Visibility.PACKAGE_PRIVATE, method.getName().getStartPosition()),
List.of(uri), LABEL);
problem.addQuickfix(new QuickfixData<>(quickfixType, descriptor, LABEL, true));
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java
index 0a4dfd2bb4..267b66a037 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoAutowiredOnConstructorReconciler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2023, 2025 VMware, Inc.
+ * Copyright (c) 2023, 2026 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -22,25 +22,27 @@
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.TypeDeclaration;
-import org.openrewrite.java.spring.NoAutowiredOnConstructor;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
+import org.springframework.ide.vscode.boot.java.jdt.refactoring.JdtFixDescriptor;
+import org.springframework.ide.vscode.boot.java.jdt.refactoring.JdtRefactorings;
+import org.springframework.ide.vscode.boot.java.jdt.refactoring.RemoveAnnotationRefactoring;
import org.springframework.ide.vscode.boot.java.utils.ASTUtils;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
+import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
+import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
-import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
-import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public class NoAutowiredOnConstructorReconciler implements JdtAstReconciler {
private static final String PROBLEM_LABEL = "Unnecessary `@Autowired` annotation";
private static final String FIX_LABEL = "Remove unnecessary `@Autowired` annotation";
- private QuickfixRegistry registry;
+ private final QuickfixRegistry registry;
public NoAutowiredOnConstructorReconciler(QuickfixRegistry registry) {
this.registry = registry;
@@ -97,10 +99,14 @@ public boolean visit(TypeDeclaration typeDecl) {
if (autowiredAnnotation != null) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL,
autowiredAnnotation.getStartPosition(), autowiredAnnotation.getLength());
- ReconcileUtils.setRewriteFixes(registry, problem,
- List.of(new FixDescriptor(NoAutowiredOnConstructor.class.getName(), List.of(docUri.toASCIIString()), FIX_LABEL)
- .withRecipeScope(RecipeScope.NODE)
- .withRangeScope(ReconcileUtils.createOpenRewriteRange(cu, typeDecl, null))));
+ QuickfixType quickfixType = registry.getQuickfixType(JdtRefactorings.JDT_QUICKFIX);
+ if (quickfixType != null) {
+ JdtFixDescriptor fix = new JdtFixDescriptor(
+ new RemoveAnnotationRefactoring(autowiredAnnotation.getStartPosition()),
+ List.of(docUri.toASCIIString()),
+ FIX_LABEL);
+ problem.addQuickfix(new QuickfixData<>(quickfixType, fix, FIX_LABEL, true));
+ }
context.getProblemCollector().accept(problem);
}
}
diff --git a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java
index a9e602fc10..109fb4892a 100644
--- a/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java
+++ b/headless-services/spring-boot-language-server/src/main/java/org/springframework/ide/vscode/boot/java/reconcilers/NoRepoAnnotationReconciler.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2023, 2025 VMware, Inc.
+ * Copyright (c) 2023, 2026 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -13,6 +13,7 @@
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
+import java.util.ArrayList;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
@@ -22,22 +23,25 @@
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.TypeDeclaration;
-import org.openrewrite.java.spring.NoRepoAnnotationOnRepoInterface;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
+import org.springframework.ide.vscode.boot.java.jdt.refactoring.JdtFixDescriptor;
+import org.springframework.ide.vscode.boot.java.jdt.refactoring.JdtRefactorings;
+import org.springframework.ide.vscode.boot.java.jdt.refactoring.RemoveAnnotationRefactoring;
import org.springframework.ide.vscode.commons.java.IJavaProject;
+import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.QuickfixData;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
+import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
-import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
-import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public class NoRepoAnnotationReconciler implements JdtAstReconciler {
private static final String PROBLEM_LABEL = "Unnecessary @Repository";
private static final String FIX_LABEL = "Remove Unnecessary @Repository";
+ private static final String FIX_ALL_LABEL = "Remove all unnecessary @Repository in file";
private static final String INTERFACE_REPOSITORY = "org.springframework.data.repository.Repository";
- private QuickfixRegistry registry;
+ private final QuickfixRegistry registry;
public NoRepoAnnotationReconciler(QuickfixRegistry registry) {
this.registry = registry;
@@ -56,31 +60,30 @@ public Boot2JavaProblemType getProblemType() {
@Override
public ASTVisitor createVisitor(IJavaProject project, URI docUri, CompilationUnit cu, ReconcilingContext context) {
+ List problemOffsets = new ArrayList<>();
+ List problems = new ArrayList<>();
+
return new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration typeDecl) {
if (typeDecl.isInterface()) {
for (Object o : typeDecl.modifiers()) {
- if (o instanceof Annotation) {
- Annotation a = (Annotation) o;
+ if (o instanceof Annotation a) {
if (isApplicableRepoAnnotation(a)) {
ITypeBinding type = typeDecl.resolveBinding();
if (type != null && isRepo(type)) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, a.getStartPosition(), a.getLength());
- String uri = docUri.toASCIIString();
- String id = NoRepoAnnotationOnRepoInterface.class.getName();
- ReconcileUtils.setRewriteFixes(registry, problem, List.of(
-// new FixDescriptor(ID, List.of(uri), FIX_LABEL)
-// .withRangeScope(RewriteQuickFixUtils.createOpenRewriteRange(cu, typeDecl))
-// .withRecipeScope(RecipeScope.NODE),
- new FixDescriptor(id, List.of(uri),
- ReconcileUtils.buildLabel(FIX_LABEL, RecipeScope.FILE))
- .withRecipeScope(RecipeScope.FILE),
- new FixDescriptor(id, List.of(uri),
- ReconcileUtils.buildLabel(FIX_LABEL, RecipeScope.PROJECT))
- .withRecipeScope(RecipeScope.PROJECT)
- ));
+ QuickfixType quickfixType = registry.getQuickfixType(JdtRefactorings.JDT_QUICKFIX);
+ if (quickfixType != null) {
+ JdtFixDescriptor fix = new JdtFixDescriptor(
+ new RemoveAnnotationRefactoring(a.getStartPosition()),
+ List.of(docUri.toASCIIString()),
+ FIX_LABEL);
+ problem.addQuickfix(new QuickfixData<>(quickfixType, fix, FIX_LABEL, true));
+ }
+ problemOffsets.add(a.getStartPosition());
+ problems.add(problem);
context.getProblemCollector().accept(problem);
}
}
@@ -89,6 +92,22 @@ public boolean visit(TypeDeclaration typeDecl) {
}
return super.visit(typeDecl);
}
+
+ @Override
+ public void endVisit(CompilationUnit node) {
+ if (!problemOffsets.isEmpty()) {
+ QuickfixType quickfixType = registry.getQuickfixType(JdtRefactorings.JDT_QUICKFIX);
+ if (quickfixType != null) {
+ JdtFixDescriptor fixAll = new JdtFixDescriptor(
+ new RemoveAnnotationRefactoring(problemOffsets.stream().mapToInt(i -> i).toArray()),
+ List.of(docUri.toASCIIString()),
+ FIX_ALL_LABEL);
+ for (ReconcileProblemImpl problem : problems) {
+ problem.addQuickfix(new QuickfixData<>(quickfixType, fixAll, FIX_ALL_LABEL, false));
+ }
+ }
+ }
+ }
};
}
@@ -108,7 +127,8 @@ private static boolean isApplicableRepoAnnotation(Annotation a) {
}
private static boolean isRepo(ITypeBinding t) {
- if (INTERFACE_REPOSITORY.equals(t.getQualifiedName())) {
+ if (t == null) return false;
+ if (INTERFACE_REPOSITORY.equals(t.getErasure().getQualifiedName())) {
return true;
} else {
for (ITypeBinding st : t.getInterfaces()) {
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/IndexerTestConf.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/IndexerTestConf.java
index 035fc312c0..1b55df01c1 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/IndexerTestConf.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/bootiful/IndexerTestConf.java
@@ -13,7 +13,9 @@
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
+import org.springframework.ide.vscode.boot.app.BootJavaConfig;
import org.springframework.ide.vscode.boot.app.BootLanguageServerParams;
+import org.springframework.ide.vscode.boot.app.ProblemParameterProvider;
import org.springframework.ide.vscode.boot.editor.harness.PropertyIndexHarness;
import org.springframework.ide.vscode.boot.index.cache.IndexCache;
import org.springframework.ide.vscode.boot.index.cache.IndexCacheVoid;
@@ -58,4 +60,8 @@ public class IndexerTestConf {
return SourceLinkFactory.NO_SOURCE_LINKS;
}
+ @Bean ProblemParameterProvider problemParameterProvider(SimpleLanguageServer server) {
+ return new ProblemParameterProvider(new BootJavaConfig(server));
+ }
+
}
\ No newline at end of file
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoringTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoringTest.java
index 73acfe7a55..1557144d9c 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoringTest.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/ChangeMethodVisibilityRefactoringTest.java
@@ -67,7 +67,7 @@ private static int offsetOf(String source, String substring) {
}
@Test
- void publicToPackagePrivate() throws Exception {
+ void publicToPackagePrivate_offsetInsideMethodName() throws Exception {
String source = """
package com.example;
@@ -77,7 +77,8 @@ public void test() {
}
""";
- String result = applyRefactoring(source, Visibility.PACKAGE_PRIVATE, offsetOf(source, "public void test"));
+ // Offset inside the method name "test"
+ String result = applyRefactoring(source, Visibility.PACKAGE_PRIVATE, offsetOf(source, "test") + 1);
assertEquals("""
package com.example;
@@ -89,6 +90,24 @@ void test() {
""", result);
}
+ @Test
+ void ignoreOffsetOutsideMethodName() throws Exception {
+ String source = """
+ package com.example;
+
+ class TestClass {
+ public void test() {
+ }
+ }
+ """;
+
+ // Offset inside the return type "void"
+ String result = applyRefactoring(source, Visibility.PACKAGE_PRIVATE, offsetOf(source, "void"));
+
+ // Should not modify the source
+ assertEquals(source, result);
+ }
+
@Test
void privateToPublic() throws Exception {
String source = """
@@ -100,7 +119,7 @@ private void test() {
}
""";
- String result = applyRefactoring(source, Visibility.PUBLIC, offsetOf(source, "private void test"));
+ String result = applyRefactoring(source, Visibility.PUBLIC, offsetOf(source, "test") + 1);
assertEquals("""
package com.example;
@@ -123,7 +142,7 @@ void test() {
}
""";
- String result = applyRefactoring(source, Visibility.PROTECTED, offsetOf(source, "void test"));
+ String result = applyRefactoring(source, Visibility.PROTECTED, offsetOf(source, "test") + 1);
assertEquals("""
package com.example;
@@ -147,7 +166,7 @@ public void test() {
}
""";
- String result = applyRefactoring(source, Visibility.PRIVATE, offsetOf(source, "@Override"));
+ String result = applyRefactoring(source, Visibility.PRIVATE, offsetOf(source, "test") + 1);
assertEquals("""
package com.example;
@@ -175,8 +194,8 @@ protected void test2() {
""";
String result = applyRefactoring(source, Visibility.PACKAGE_PRIVATE,
- offsetOf(source, "public void test1"),
- offsetOf(source, "protected void test2"));
+ offsetOf(source, "test1") + 1,
+ offsetOf(source, "test2") + 1);
assertEquals("""
package com.example;
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtilsTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtilsTest.java
index 439eb43aed..53a3d7a3d2 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtilsTest.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/JdtRefactorUtilsTest.java
@@ -50,7 +50,9 @@ private static CompilationUnit parseSource(String source) {
ASTParser parser = ASTParser.newParser(AST.JLS25);
parser.setSource(source.toCharArray());
parser.setKind(ASTParser.K_COMPILATION_UNIT);
- parser.setResolveBindings(false);
+ parser.setResolveBindings(true);
+ parser.setEnvironment(new String[0], new String[0], null, true);
+ parser.setUnitName("Test.java");
Map options = JavaCore.getOptions();
JavaCore.setComplianceOptions(JavaCore.VERSION_21, options);
parser.setCompilerOptions(options);
@@ -68,6 +70,16 @@ private static String applyAddImport(String source, String fqn) throws Exception
return doc.get();
}
+ private static String applyRemoveImports(String source, String... fqns) throws Exception {
+ CompilationUnit cu = parseSource(source);
+ ASTRewrite rewrite = ASTRewrite.create(cu.getAST());
+ JdtRefactorUtils.removeImports(cu, rewrite, fqns);
+ Document doc = new Document(source);
+ TextEdit edit = rewrite.rewriteAST(doc, defaultFormatterOptions());
+ edit.apply(doc);
+ return doc.get();
+ }
+
// ========== extractSimpleName ==========
@Test
@@ -398,4 +410,165 @@ void toLspTextDocumentEdit_crossLineEdit_correctLineAndCharacter() throws Except
assertEquals(0, lspEdit.getRange().getStart().getCharacter());
}
+ // ========== removeImports ==========
+
+ @Test
+ void removeImports_unusedImport_removed() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.util.List;
+ import java.util.ArrayList;
+
+ class Foo {
+ List list;
+ }
+ """;
+
+ String result = applyRemoveImports(source, "java.util.ArrayList");
+
+ assertEquals("""
+ package com.example;
+
+ import java.util.List;
+
+ class Foo {
+ List list;
+ }
+ """, result);
+ }
+
+ @Test
+ void removeImports_usedImport_kept() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.util.List;
+ import java.util.ArrayList;
+
+ class Foo {
+ List list = new ArrayList<>();
+ }
+ """;
+
+ String result = applyRemoveImports(source, "java.util.ArrayList");
+
+ assertEquals(source, result);
+ }
+
+ @Test
+ void removeImports_multipleUnusedImports_removed() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.util.List;
+ import java.util.ArrayList;
+ import java.util.Map;
+ import java.util.HashMap;
+
+ class Foo {
+ }
+ """;
+
+ String result = applyRemoveImports(source, "java.util.List", "java.util.ArrayList", "java.util.Map");
+
+ assertEquals("""
+ package com.example;
+
+ import java.util.HashMap;
+
+ class Foo {
+ }
+ """, result);
+ }
+
+ @Test
+ void removeImports_usedInAnnotation_kept() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.lang.annotation.Documented;
+
+ @Documented
+ class Foo {
+ }
+ """;
+
+ String result = applyRemoveImports(source, "java.lang.annotation.Documented");
+
+ assertEquals(source, result);
+ }
+
+ @Test
+ void removeImports_usedAsStaticMethod_kept() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.util.Collections;
+
+ class Foo {
+ void test() {
+ Collections.emptyList();
+ }
+ }
+ """;
+
+ String result = applyRemoveImports(source, "java.util.Collections");
+
+ assertEquals(source, result);
+ }
+
+ @Test
+ void removeImports_onDemandImport_ignored() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.util.*;
+
+ class Foo {
+ }
+ """;
+
+ String result = applyRemoveImports(source, "java.util.List");
+
+ assertEquals(source, result);
+ }
+
+ @Test
+ void removeImports_survivesRewrite_removedIfNodeRemoved() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.util.List;
+ import java.util.ArrayList;
+
+ class Foo {
+ List list;
+ }
+ """;
+
+ CompilationUnit cu = parseSource(source);
+ ASTRewrite rewrite = ASTRewrite.create(cu.getAST());
+
+ // Remove the 'List list;' field declaration
+ org.eclipse.jdt.core.dom.TypeDeclaration typeDecl = (org.eclipse.jdt.core.dom.TypeDeclaration) cu.types().get(0);
+ org.eclipse.jdt.core.dom.FieldDeclaration fieldDecl = typeDecl.getFields()[0];
+ rewrite.remove(fieldDecl, null);
+
+ JdtRefactorUtils.removeImports(cu, rewrite, "java.util.List");
+
+ Document doc = new Document(source);
+ TextEdit edit = rewrite.rewriteAST(doc, defaultFormatterOptions());
+ edit.apply(doc);
+
+ assertEquals("""
+ package com.example;
+
+ import java.util.ArrayList;
+
+ class Foo {
+ }
+ """, doc.get());
+ }
+
}
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/RemoveAnnotationRefactoringTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/RemoveAnnotationRefactoringTest.java
new file mode 100644
index 0000000000..4000ee8b4b
--- /dev/null
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/jdt/refactoring/RemoveAnnotationRefactoringTest.java
@@ -0,0 +1,188 @@
+/*******************************************************************************
+ * Copyright (c) 2026 VMware, Inc.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * https://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ * VMware, Inc. - initial API and implementation
+ *******************************************************************************/
+package org.springframework.ide.vscode.boot.java.jdt.refactoring;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+import java.util.Map;
+
+import org.eclipse.jdt.core.JavaCore;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.rewrite.ASTRewrite;
+import org.eclipse.jface.text.Document;
+import org.eclipse.text.edits.TextEdit;
+import org.junit.jupiter.api.Test;
+
+/**
+ * Unit tests for {@link RemoveAnnotationRefactoring}.
+ */
+class RemoveAnnotationRefactoringTest {
+
+ private static CompilationUnit parseSource(String source) {
+ ASTParser parser = ASTParser.newParser(AST.JLS25);
+ parser.setSource(source.toCharArray());
+ parser.setKind(ASTParser.K_COMPILATION_UNIT);
+ parser.setResolveBindings(true);
+ parser.setEnvironment(new String[0], new String[0], null, true);
+ parser.setUnitName("Test.java");
+ Map options = JavaCore.getOptions();
+ JavaCore.setComplianceOptions(JavaCore.VERSION_21, options);
+ parser.setCompilerOptions(options);
+ return (CompilationUnit) parser.createAST(null);
+ }
+
+ private static String applyRefactoring(String source, int... offsets) throws Exception {
+ CompilationUnit cu = parseSource(source);
+ ASTRewrite rewrite = ASTRewrite.create(cu.getAST());
+ new RemoveAnnotationRefactoring(offsets).apply(rewrite, cu);
+ Document doc = new Document(source);
+ TextEdit edit = rewrite.rewriteAST(doc, JavaCore.getOptions());
+ edit.apply(doc);
+ return doc.get();
+ }
+
+ private static int offsetOf(String source, String substring) {
+ return source.indexOf(substring);
+ }
+
+ @Test
+ void removeMarkerAnnotationFromConstructorAndImport() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.lang.annotation.Documented;
+
+ class MyService {
+ @Documented
+ MyService() {}
+ }
+ """;
+
+ // Offset inside the annotation name
+ String result = applyRefactoring(source, offsetOf(source, "@Documented") + 2);
+
+ assertEquals("""
+ package com.example;
+
+ class MyService {
+ MyService() {}
+ }
+ """, result);
+ }
+
+ @Test
+ void removeMarkerAnnotationButKeepImportIfUsed() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.lang.annotation.Documented;
+
+ class MyService {
+ @Documented
+ MyService() {}
+
+ @Documented
+ MyService(int x) {}
+ }
+ """;
+
+ // Remove only the first annotation
+ String result = applyRefactoring(source, offsetOf(source, "@Documented") + 2);
+
+ assertEquals("""
+ package com.example;
+
+ import java.lang.annotation.Documented;
+
+ class MyService {
+ MyService() {}
+
+ @Documented
+ MyService(int x) {}
+ }
+ """, result);
+ }
+
+ @Test
+ void removeMarkerAnnotationFromTypeDeclaration() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.lang.annotation.Documented;
+ import java.io.Serializable;
+
+ @Documented
+ interface PersonRepo extends Serializable {}
+ """;
+
+ // Offset at the start of the annotation name
+ String result = applyRefactoring(source, offsetOf(source, "@Documented") + 1);
+
+ assertEquals("""
+ package com.example;
+
+ import java.io.Serializable;
+
+ interface PersonRepo extends Serializable {}
+ """, result);
+ }
+
+ @Test
+ void removeBatchAnnotations() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.lang.annotation.Documented;
+ import java.io.Serializable;
+
+ @Documented
+ interface Repo1 extends Serializable {}
+
+ @Documented
+ interface Repo2 extends Serializable {}
+ """;
+
+ String result = applyRefactoring(source,
+ offsetOf(source, "@Documented\ninterface Repo1") + 1,
+ offsetOf(source, "@Documented\ninterface Repo2") + 2);
+
+ assertEquals("""
+ package com.example;
+
+ import java.io.Serializable;
+
+ interface Repo1 extends Serializable {}
+
+ interface Repo2 extends Serializable {}
+ """, result);
+ }
+
+ @Test
+ void ignoreOffsetOutsideAnnotationName() throws Exception {
+ String source = """
+ package com.example;
+
+ import java.lang.annotation.Documented;
+
+ @Documented
+ interface Repo1 {}
+ """;
+
+ // Offset inside the parentheses, which is outside the name node
+ String result = applyRefactoring(source, offsetOf(source, "Repo1"));
+
+ // Should not modify the source
+ assertEquals(source, result);
+ }
+
+}
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoAutowiredOnConstructorReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoAutowiredOnConstructorReconcilerTest.java
index 42eea76a72..098f2138f6 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoAutowiredOnConstructorReconcilerTest.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoAutowiredOnConstructorReconcilerTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2023, 2025 VMware, Inc.
+ * Copyright (c) 2023, 2026 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,114 +11,146 @@
package org.springframework.ide.vscode.boot.java.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import java.io.File;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
-import org.junit.jupiter.api.AfterEach;
+import org.eclipse.lsp4j.Diagnostic;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
+import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
+import org.springframework.ide.vscode.boot.bootiful.IndexerTestConf;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
-import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
-import org.springframework.ide.vscode.boot.java.reconcilers.NoAutowiredOnConstructorReconciler;
-import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
-import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
+import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
+import org.springframework.ide.vscode.commons.util.text.LanguageId;
+import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
+import org.springframework.ide.vscode.languageserver.testharness.Editor;
+import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
+import org.springframework.ide.vscode.project.harness.ProjectsHarness;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
-public class NoAutowiredOnConstructorReconcilerTest extends BaseReconcilerTest {
+@ExtendWith(SpringExtension.class)
+@BootLanguageServerTest
+@Import(IndexerTestConf.class)
+public class NoAutowiredOnConstructorReconcilerTest {
- @Override
- protected String getFolder() {
- return "noautowiredonconstructor";
- }
+ @Autowired private BootLanguageServerHarness harness;
+ @Autowired private JavaProjectFinder projectFinder;
+ @Autowired private SpringSymbolIndex indexer;
- @Override
- protected String getProjectName() {
- return "test-spring-validations";
- }
-
- @Override
- protected JdtAstReconciler getReconciler() {
- return new NoAutowiredOnConstructorReconciler(new QuickfixRegistry());
- }
+ private File directory;
@BeforeEach
- void setup() throws Exception {
- super.setup();
- }
-
- @AfterEach
- void tearDown() throws Exception {
- super.tearDown();
+ public void setup() throws Exception {
+ harness.intialize(null);
+ harness.changeConfiguration("{\"boot-java\": {\"validation\": {\"java\": { \"reconcilers\": true}}}}");
+ directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-validations/").toURI());
+ String projectDir = directory.toURI().toString();
+ projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
+ CompletableFuture initProject = indexer.waitOperation();
+ initProject.get(5, TimeUnit.SECONDS);
}
-
+
@Test
void singleConstructors() throws Exception {
- String source = """
- package example.demo;
-
+ String docUri = directory.toPath()
+ .resolve("src/main/java/org/test/WithAutowiredConstructor.java").toUri().toString();
+
+ Editor editor = harness.newEditor(LanguageId.JAVA, """
+ package org.test;
+
import org.springframework.beans.factory.annotation.Autowired;
-
- class A {
-
+
+ public class WithAutowiredConstructor {
+
+ private final String value;
+
@Autowired
- A() {};
-
+ public WithAutowiredConstructor(String value) {
+ this.value = value;
+ }
+
}
- """;
- List problems = reconcile("A.java", source, false);
-
- assertEquals(1, problems.size());
-
- ReconcileProblem problem = problems.get(0);
-
- assertEquals(Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR, problem.getType());
-
- String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
- assertEquals("@Autowired", markedStr);
-
- assertEquals(1, problem.getQuickfixes().size());
-
+ """, docUri);
+
+ Diagnostic problem = editor.assertProblem("@Autowired");
+ assertNotNull(problem);
+ assertEquals(Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR.getCode(), problem.getCode().getLeft());
+
+ List codeActions = editor.getCodeActions(problem);
+ assertEquals(1, codeActions.size());
+
+ harness.executeCommand(codeActions.get(0).getCommand());
+
+ assertEquals("""
+ package org.test;
+
+ public class WithAutowiredConstructor {
+
+ private final String value;
+
+ public WithAutowiredConstructor(String value) {
+ this.value = value;
+ }
+
+ }
+ """, editor.getRawText());
+
+ editor.assertProblems();
}
@Test
void multipleConstructors() throws Exception {
- String source = """
- package example.demo;
-
+ String docUri = directory.toPath()
+ .resolve("src/main/java/org/test/MultipleConstructors.java").toUri().toString();
+
+ Editor editor = harness.newEditor(LanguageId.JAVA, """
+ package org.test;
+
import org.springframework.beans.factory.annotation.Autowired;
-
- class A {
-
+
+ class MultipleConstructors {
+
@Autowired
- A() {};
-
- A(int k) {}
-
+ MultipleConstructors() {}
+
+ MultipleConstructors(int k) {}
+
}
- """;
- List problems = reconcile("A.java", source, false);
-
- assertEquals(0, problems.size());
+ """, docUri);
+
+ editor.assertProblems();
}
@Test
void multipleConstructorsViaLombok() throws Exception {
- String source = """
- package example.demo;
-
+ String docUri = directory.toPath()
+ .resolve("src/main/java/org/test/LombokConstructors.java").toUri().toString();
+
+ Editor editor = harness.newEditor(LanguageId.JAVA, """
+ package org.test;
+
import org.springframework.beans.factory.annotation.Autowired;
import lombok.RequiredArgsConstructor;
-
+
@RequiredArgsConstructor
- class A {
-
+ class LombokConstructors {
+
@Autowired
- A() {};
-
+ LombokConstructors() {}
+
}
- """;
- List problems = reconcile("A.java", source, false);
-
- assertEquals(0, problems.size());
+ """, docUri);
+
+ editor.assertProblems();
}
}
diff --git a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoRepoAnnotationReconcilerTest.java b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoRepoAnnotationReconcilerTest.java
index a25f1a0f86..a26802df93 100644
--- a/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoRepoAnnotationReconcilerTest.java
+++ b/headless-services/spring-boot-language-server/src/test/java/org/springframework/ide/vscode/boot/java/reconcilers/test/NoRepoAnnotationReconcilerTest.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2023 VMware, Inc.
+ * Copyright (c) 2023, 2026 VMware, Inc.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
@@ -11,124 +11,158 @@
package org.springframework.ide.vscode.boot.java.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNotNull;
+import java.io.File;
import java.util.List;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.TimeUnit;
-import org.junit.jupiter.api.AfterEach;
+import org.eclipse.lsp4j.Diagnostic;
+import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.extension.ExtendWith;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Import;
+import org.springframework.ide.vscode.boot.app.SpringSymbolIndex;
+import org.springframework.ide.vscode.boot.bootiful.BootLanguageServerTest;
+import org.springframework.ide.vscode.boot.bootiful.IndexerTestConf;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
-import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
-import org.springframework.ide.vscode.boot.java.reconcilers.NoRepoAnnotationReconciler;
-import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
-import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
+import org.springframework.ide.vscode.commons.languageserver.java.JavaProjectFinder;
+import org.springframework.ide.vscode.commons.util.text.LanguageId;
+import org.springframework.ide.vscode.languageserver.testharness.CodeAction;
+import org.springframework.ide.vscode.languageserver.testharness.Editor;
+import org.springframework.ide.vscode.project.harness.BootLanguageServerHarness;
+import org.springframework.ide.vscode.project.harness.ProjectsHarness;
+import org.springframework.test.context.junit.jupiter.SpringExtension;
-public class NoRepoAnnotationReconcilerTest extends BaseReconcilerTest {
+@ExtendWith(SpringExtension.class)
+@BootLanguageServerTest
+@Import(IndexerTestConf.class)
+public class NoRepoAnnotationReconcilerTest {
- @Override
- protected String getFolder() {
- return "norepoannotation";
- }
-
- @Override
- protected String getProjectName() {
- return "test-spring-validations";
- }
+ @Autowired private BootLanguageServerHarness harness;
+ @Autowired private JavaProjectFinder projectFinder;
+ @Autowired private SpringSymbolIndex indexer;
- @Override
- protected JdtAstReconciler getReconciler() {
- return new NoRepoAnnotationReconciler(new QuickfixRegistry());
- }
+ private File directory;
@BeforeEach
- void setup() throws Exception {
- super.setup();
- }
-
- @AfterEach
- void tearDown() throws Exception {
- super.tearDown();
+ public void setup() throws Exception {
+ harness.intialize(null);
+ harness.changeConfiguration("{\"boot-java\": {\"validation\": {\"java\": { \"reconcilers\": true}}}}");
+ directory = new File(ProjectsHarness.class.getResource("/test-projects/test-spring-validations/").toURI());
+ String projectDir = directory.toURI().toString();
+ projectFinder.find(new TextDocumentIdentifier(projectDir)).get();
+ CompletableFuture initProject = indexer.waitOperation();
+ initProject.get(5, TimeUnit.SECONDS);
}
-
+
@Test
void sanityTest() throws Exception {
- String source = """
- package example.demo;
-
+ String docUri = directory.toPath()
+ .resolve("src/main/java/org/test/RepoWithUnnecessaryAnnotation.java").toUri().toString();
+
+ Editor editor = harness.newEditor(LanguageId.JAVA, """
+ package org.test;
+
import org.springframework.data.repository.Repository;
-
+
@org.springframework.stereotype.Repository
interface A extends Repository {
-
}
- """;
- List problems = reconcile("A.java", source, false);
-
- assertEquals(1, problems.size());
-
- ReconcileProblem problem = problems.get(0);
-
- assertEquals(Boot2JavaProblemType.JAVA_REPOSITORY, problem.getType());
-
- String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
- assertEquals("@org.springframework.stereotype.Repository", markedStr);
-
- assertEquals(2, problem.getQuickfixes().size());
-
+ """, docUri);
+
+ Diagnostic problem = editor.assertProblem("@org.springframework.stereotype.Repository");
+ assertNotNull(problem);
+ assertEquals(Boot2JavaProblemType.JAVA_REPOSITORY.getCode(), problem.getCode().getLeft());
+
+ List codeActions = editor.getCodeActions(problem);
+ assertEquals(1, codeActions.size());
+
+ harness.executeCommand(codeActions.get(0).getCommand());
+
+ assertEquals("""
+ package org.test;
+
+ import org.springframework.data.repository.Repository;
+
+ interface A extends Repository {
+ }
+ """, editor.getRawText());
+
+ editor.assertProblems();
}
-
+
@Test
void inverseSanityTest() throws Exception {
- String source = """
- package example.demo;
-
+ String docUri = directory.toPath()
+ .resolve("src/main/java/org/test/InverseSanity.java").toUri().toString();
+
+ Editor editor = harness.newEditor(LanguageId.JAVA, """
+ package org.test;
+
import org.springframework.stereotype.Repository;
-
+
@Repository
interface A extends org.springframework.data.repository.Repository {
-
}
- """;
- List problems = reconcile("A.java", source, false);
-
- assertEquals(1, problems.size());
-
- ReconcileProblem problem = problems.get(0);
-
- assertEquals(Boot2JavaProblemType.JAVA_REPOSITORY, problem.getType());
-
- String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
- assertEquals("@Repository", markedStr);
-
- assertEquals(2, problem.getQuickfixes().size());
-
+ """, docUri);
+
+ Diagnostic problem = editor.assertProblem("@Repository");
+ assertNotNull(problem);
+ assertEquals(Boot2JavaProblemType.JAVA_REPOSITORY.getCode(), problem.getCode().getLeft());
+
+ List codeActions = editor.getCodeActions(problem);
+ assertEquals(1, codeActions.size());
+
+ harness.executeCommand(codeActions.get(0).getCommand());
+
+ assertEquals("""
+ package org.test;
+
+ interface A extends org.springframework.data.repository.Repository {
+ }
+ """, editor.getRawText());
+
+ editor.assertProblems();
}
-
+
@Test
void emptyRepoAnnotation() throws Exception {
- String source = """
- package example.demo;
-
+ String docUri = directory.toPath()
+ .resolve("src/main/java/org/test/EmptyRepoAnnotation.java").toUri().toString();
+
+ Editor editor = harness.newEditor(LanguageId.JAVA, """
+ package org.test;
+
import org.springframework.data.repository.Repository;
-
- @org.springframework.stereotype.Repository()
+
+ @org.springframework.stereotype.Repository
interface A extends Repository {
-
}
- """;
- List problems = reconcile("A.java", source, false);
-
- assertEquals(1, problems.size());
-
- ReconcileProblem problem = problems.get(0);
-
- assertEquals(Boot2JavaProblemType.JAVA_REPOSITORY, problem.getType());
-
- String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
- assertEquals("@org.springframework.stereotype.Repository()", markedStr);
-
- assertEquals(2, problem.getQuickfixes().size());
-
+ """, docUri);
+
+ Diagnostic problem = editor.assertProblem("@org.springframework.stereotype.Repository");
+ assertNotNull(problem);
+ assertEquals(Boot2JavaProblemType.JAVA_REPOSITORY.getCode(), problem.getCode().getLeft());
+
+ List codeActions = editor.getCodeActions(problem);
+ assertEquals(1, codeActions.size());
+
+ harness.executeCommand(codeActions.get(0).getCommand());
+
+ assertEquals("""
+ package org.test;
+
+ import org.springframework.data.repository.Repository;
+
+ interface A extends Repository {
+ }
+ """, editor.getRawText());
+
+ editor.assertProblems();
}
}