First batch of reconcilers migrated to JDT

This commit is contained in:
aboyko
2023-08-07 23:31:30 -04:00
parent d13cea2efe
commit 231ad105a4
27 changed files with 1881 additions and 205 deletions

View File

@@ -12,8 +12,6 @@ package org.springframework.ide.vscode.commons.rewrite.java;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.nio.channels.ClosedByInterruptException;
import java.nio.file.Files;
import java.nio.file.Path;
@@ -39,6 +37,7 @@ import org.openrewrite.java.JavaParser;
import org.openrewrite.java.JavaParser.Builder;
import org.openrewrite.java.JavaParsingException;
import org.openrewrite.java.JavaVisitor;
import org.openrewrite.java.RemoveUnusedImports;
import org.openrewrite.java.UpdateSourcePositions;
import org.openrewrite.java.marker.JavaSourceSet;
import org.openrewrite.java.tree.J;
@@ -377,27 +376,6 @@ public class ORAstUtils {
return fqName;
}
@SuppressWarnings("unchecked")
private static List<TreeVisitor<?, ExecutionContext>> getAfterVisitors(TreeVisitor<?, ExecutionContext> visitor) {
try {
Method m = TreeVisitor.class.getDeclaredMethod("getAfterVisit");
m.setAccessible(true);
return (List<TreeVisitor<?, ExecutionContext>>) m.invoke(visitor);
} catch (Exception e) {
return Collections.emptyList();
}
}
private static void makeVisitorNonTopLevel(TreeVisitor<?, ExecutionContext> visitor) {
try {
Field f = TreeVisitor.class.getDeclaredField("afterVisit");
f.setAccessible(true);
f.set(visitor, new ArrayList<>());
} catch (Exception e) {
// ignore
}
}
public static Recipe nodeRecipe(JavaVisitor<ExecutionContext> v, Predicate<J> condition) {
return new NodeRecipe((JavaVisitor<ExecutionContext>) v, condition);
}
@@ -415,8 +393,13 @@ public class ORAstUtils {
this.visitor = treeVisitor;
this.condition = condition;
}
@Override
public List<Recipe> getRecipeList() {
return List.of(new RemoveUnusedImports());
}
@Override
public String getDisplayName() {
return "";
}
@@ -430,12 +413,7 @@ public class ORAstUtils {
if (tree instanceof J) {
J t = (J) tree;
if (condition.test(t)) {
makeVisitorNonTopLevel(visitor);
t = (J) visitor.visit(t, ctx, getCursor());
for (TreeVisitor<?, ExecutionContext> v : getAfterVisitors(visitor)) {
doAfterVisit(v);
}
return t;
return (J) visitor.visit(t, ctx, getCursor());
}
}
return super.visit(tree, ctx);

View File

@@ -210,7 +210,17 @@ public class BootJavaConfig implements InitializingBean {
if (problem != null && problem.getCategory() != null && problem.getCategory().getToggle() != null) {
Toggle toggle = problem.getCategory().getToggle();
String s = settings.getString((toggle.getPreferenceKey()).split("\\."));
return s == null || s.isEmpty() ? toggle.getDefaultValue() : Toggle.Option.valueOf(s);
try {
return s == null || s.isEmpty() ? toggle.getDefaultValue() : Toggle.Option.valueOf(s);
} catch (IllegalArgumentException e) {
// handle backward compatibility case of 'true'/'false'
Boolean b = Boolean.valueOf(s);
if (b == null) {
throw e;
} else {
return b.booleanValue() ? Toggle.Option.ON : Toggle.Option.OFF;
}
}
}
} catch (Exception e) {
log.error("", e);

View File

@@ -51,14 +51,14 @@ public class RewriteConfig {
return new SpringBootUpgrade(server, recipeRepo, projectFinder);
}
@ConditionalOnBean(RewriteRecipeRepository.class)
@Bean RewriteReconciler rewriteJavaReconciler(RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache, SimpleLanguageServer server, BootJavaConfig config) {
return new RewriteReconciler(
recipeRepo,
cuCache,
server.getQuickfixRegistry(),
config
);
}
// @ConditionalOnBean(RewriteRecipeRepository.class)
// @Bean RewriteReconciler rewriteJavaReconciler(RewriteRecipeRepository recipeRepo, RewriteCompilationUnitCache cuCache, SimpleLanguageServer server, BootJavaConfig config) {
// return new RewriteReconciler(
// recipeRepo,
// cuCache,
// server.getQuickfixRegistry(),
// config
// );
// }
}

View File

@@ -55,8 +55,7 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyA
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolAddOnInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.BeanMethodNotPublicReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
import org.springframework.ide.vscode.boot.java.utils.DocumentDescriptor;
import org.springframework.ide.vscode.boot.java.utils.SpringFactoriesIndexer;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexer;
@@ -102,6 +101,7 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex {
@Autowired IndexCache cache;
@Autowired FutureProjectFinder futureProjectFinder;
@Autowired SpringMetamodelIndex springIndex;
@Autowired JdtReconciler jdtReconciler;
private static final String QUERY_PARAM_LOCATION_PREFIX = "locationPrefix:";
@@ -246,11 +246,9 @@ public class SpringSymbolIndex implements InitializingBean, SpringIndex {
namespaceHandler.put("http://www.springframework.org/schema/beans", new SpringIndexerXMLNamespaceHandlerBeans());
springIndexerXML = new SpringIndexerXML(handler, namespaceHandler, this.cache, projectFinder());
List<AnnotationReconciler> reconcilers = new ArrayList<>();
reconcilers.add(new BeanMethodNotPublicReconciler(server.getQuickfixRegistry()));
BiFunction<AtomicReference<TextDocument>, BiConsumer<String, Diagnostic>, IProblemCollector> problemCollectorFactory = (docRef, aggregator) -> server.createProblemCollector(docRef, aggregator);
springIndexerJava = new SpringIndexerJava(handler, specificProviders, this.cache, projectFinder(), server.getProgressService(), reconcilers, problemCollectorFactory, config.getJavaValidationSettingsJson());
springIndexerJava = new SpringIndexerJava(handler, specificProviders, this.cache, projectFinder(), server.getProgressService(), jdtReconciler, problemCollectorFactory, config.getJavaValidationSettingsJson());
factoriesIndexer = new SpringFactoriesIndexer(handler, cache);

View File

@@ -36,6 +36,7 @@ public class Annotations {
public static final String SPRING_PUT_MAPPING = "org.springframework.web.bind.annotation.PutMapping";
public static final String SPRING_DELETE_MAPPING = "org.springframework.web.bind.annotation.DeleteMapping";
public static final String SPRING_PATCH_MAPPING = "org.springframework.web.bind.annotation.PatchMapping";
public static final String SPRING_BOOT_TEST = "org.springframework.boot.test.context.SpringBootTest";
public static final String CONDITIONAL_ON_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnBean";
public static final String CONDITIONAL_ON_MISSING_BEAN = "org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean";

View File

@@ -0,0 +1,153 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import java.lang.reflect.Modifier;
import java.net.URI;
import java.util.Iterator;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.openrewrite.java.spring.boot2.AddConfigurationAnnotationIfBeansPresent;
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.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
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 AddConfigurationIfBeansPresentReconciler implements JdtAstReconciler {
private static final String ID = AddConfigurationAnnotationIfBeansPresent.class.getName();
private static final String PROBLEM_LABEL = "'@Configuration' is missing on a class defining Spring Beans";
private static final String FIX_LABEL = "Add missing '@Configuration' annotations over classes";
private QuickfixRegistry quickfixRegistry;
public AddConfigurationIfBeansPresentReconciler(QuickfixRegistry quickfixRegistry) {
this.quickfixRegistry = quickfixRegistry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration classDecl) {
if (isApplicableClass(cu, classDecl)) {
SimpleName nameAst = classDecl.getName();
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL,
nameAst.getStartPosition(), nameAst.getLength());
RewriteQuickFixUtils.setRewriteFixes(quickfixRegistry, problem,
List.of(new FixDescriptor(ID, List.of(docUri.toASCIIString()),
RewriteQuickFixUtils.buildLabel(FIX_LABEL, RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE),
new FixDescriptor(ID, List.of(docUri.toASCIIString()),
RewriteQuickFixUtils.buildLabel(FIX_LABEL, RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT)));
problemCollector.accept(problem);
}
return true;
}
});
}
private static boolean isApplicableClass(CompilationUnit cu, TypeDeclaration classDecl) {
if (classDecl.isInterface()) {
return false;
}
if (Modifier.isAbstract(classDecl.getModifiers())) {
return false;
}
boolean isStatic = Modifier.isStatic(classDecl.getModifiers());
if (!isStatic) {
// no static keyword? check if it is top level class in the CU
for (ASTNode p = classDecl.getParent(); p != cu && p != null; p = p.getParent()) {
if (p instanceof TypeDeclaration) {
return false;
}
}
}
// check if '@Configuration' is already over the class
for (Iterator<?> itr = classDecl.modifiers().iterator(); itr.hasNext();) {
Object mod = itr.next();
if (mod instanceof Annotation) {
Annotation a = (Annotation) mod;
ITypeBinding aType = a.resolveTypeBinding();
if (aType != null && AnnotationHierarchies.isSubtypeOf(a, Annotations.CONFIGURATION)) {
// Found '@Configuration' annotation
return false;
}
}
}
// No '@Configuration' present. Check if any methods have '@Bean' annotation
for (MethodDeclaration m : classDecl.getMethods()) {
if (isBeanMethod(m)) {
return true;
}
}
return false;
}
private static boolean isBeanMethod(MethodDeclaration m) {
for (Iterator<?> itr = m.modifiers().iterator(); itr.hasNext();) {
Object mod = itr.next();
if (mod instanceof Annotation) {
Annotation a = (Annotation) mod;
ITypeBinding aType = a.resolveTypeBinding();
if (aType != null && AnnotationHierarchies.isSubtypeOf(a, Annotations.BEAN)) {
// Found '@Bean' annotation
return true;
}
}
}
return false;
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-context");
return version != null && version.compareTo(new Version(3, 0, 0, null)) >= 0;
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.MISSING_CONFIGURATION_ANNOTATION;
}
}

View File

@@ -0,0 +1,138 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import java.net.URI;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.SpelProblemType;
import org.springframework.ide.vscode.boot.java.handlers.SpelExpressionReconciler;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
public class AnnotationNodeReconciler implements JdtAstReconciler {
public static final String SPRING_CACHEABLE = "org.springframework.cache.annotation.Cacheable";
public static final String SPRING_CACHE_EVICT = "org.springframework.cache.annotation.CacheEvict";
public static final String SPRING_EVENT_LISTENER = "org.springframework.context.event.EventListener";
public static final String SPRING_PRE_AUTHORIZE = "org.springframework.security.access.prepost.PreAuthorize";
public static final String SPRING_PRE_FILTER = "org.springframework.security.access.prepost.PreFilter";
public static final String SPRING_POST_AUTHORIZE = "org.springframework.security.access.prepost.PostAuthorize";
public static final String SPRING_POST_FILTER= "org.springframework.security.access.prepost.PostFilter";
public static final String SPRING_CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression";
private final SpelExpressionReconciler spelExpressionReconciler;
private final AnnotationReconciler[] reconcilers;
public AnnotationNodeReconciler(BootJavaConfig config) {
this.spelExpressionReconciler = new SpelExpressionReconciler();
this.reconcilers = new AnnotationReconciler[] {
new AnnotationParamReconciler(Annotations.VALUE, null, "#{", "}", spelExpressionReconciler),
new AnnotationParamReconciler(Annotations.VALUE, "value", "#{", "}", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHEABLE, "key", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHEABLE, "condition", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHEABLE, "unless", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "key", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "condition", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_EVENT_LISTENER, "condition", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_FILTER, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_FILTER, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_FILTER, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_FILTER, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler),
};
config.addListener(evt -> this.spelExpressionReconciler.setEnabled(config.isSpelExpressionValidationEnabled()));
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
visitAnnotation(project, docUri, node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node) {
try {
visitAnnotation(project, docUri, node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
try {
visitAnnotation(project, docUri, node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
});
}
private void visitAnnotation(IJavaProject project, URI docUri, Annotation node, IProblemCollector problemCollector) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null) {
for (int i = 0; i < reconcilers.length; i++) {
reconcilers[i].visit(project, docUri, node, typeBinding, problemCollector);
}
}
}
@Override
public boolean isApplicable(IJavaProject project) {
return true;
}
@Override
public ProblemType getProblemType() {
return SpelProblemType.JAVA_SPEL_EXPRESSION_SYNTAX;
}
}

View File

@@ -10,6 +10,7 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.reconcilers;
import java.net.URI;
import java.util.List;
import java.util.Set;
@@ -24,7 +25,6 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchie
import org.springframework.ide.vscode.boot.java.handlers.Reconciler;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.text.IDocument;
/**
* @author Martin Lippert
@@ -46,7 +46,7 @@ public class AnnotationParamReconciler implements AnnotationReconciler {
this.reconciler = reconciler;
}
public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector) {
public void visit(IJavaProject project, URI docUri, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector) {
if (node instanceof SingleMemberAnnotation) {
visitSingleMemberAnnotation((SingleMemberAnnotation) node, typeBinding, problemCollector);
} else if (node instanceof NormalAnnotation) {

View File

@@ -10,14 +10,15 @@
*******************************************************************************/
package org.springframework.ide.vscode.boot.java.reconcilers;
import java.net.URI;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public interface AnnotationReconciler {
void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector);
void visit(IJavaProject project, URI docUri, Annotation node, ITypeBinding typeBinding, IProblemCollector problemCollector);
}

View File

@@ -0,0 +1,171 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.stream.Collectors;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.Assignment;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ExpressionStatement;
import org.eclipse.jdt.core.dom.FieldAccess;
import org.eclipse.jdt.core.dom.FieldDeclaration;
import org.eclipse.jdt.core.dom.IVariableBinding;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.SimpleName;
import org.eclipse.jdt.core.dom.ThisExpression;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.eclipse.jdt.core.dom.VariableDeclarationFragment;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
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.ConvertAutowiredFieldIntoConstructorParameter;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public class AutowiredFieldIntoConstructorParameterReconciler implements JdtAstReconciler {
private static final String LABEL = "Convert @Autowired field into Constructor Parameter";
private static final String ID = ConvertAutowiredFieldIntoConstructorParameter.class.getName();
private QuickfixRegistry registry;
public AutowiredFieldIntoConstructorParameterReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
Path sourceFile = Paths.get(docUri);
// Check if source file belongs to non-test java sources folder
if (IClasspathUtil.getProjectJavaSourceFoldersWithoutTests(project.getClasspath())
.anyMatch(f -> sourceFile.startsWith(f.toPath()))) {
AtomicBoolean completeAstRequired = new AtomicBoolean(false);
cu.accept(new ASTVisitor() {
@Override
public boolean visit(FieldDeclaration field) {
if (field.fragments().size() == 1) {
Annotation annotation = RewriteQuickFixUtils.findAnnotation(field, Annotations.AUTOWIRED,
false);
if (annotation != null && field.getParent() instanceof TypeDeclaration) {
TypeDeclaration typeDecl = (TypeDeclaration) field.getParent();
List<MethodDeclaration> constructors = Arrays.stream(typeDecl.getMethods())
.filter(c -> c.isConstructor()).collect(Collectors.toList());
VariableDeclarationFragment variableDeclarationFragment = (VariableDeclarationFragment) field
.fragments().get(0);
String fieldName = variableDeclarationFragment.getName().getIdentifier();
if (constructors.isEmpty()) {
problemCollector.accept(createProblem(cu, field, fieldName, docUri));
} else if (constructors.size() == 1) {
if (!isCompleteAst) {
completeAstRequired.set(true);
return false;
}
if (!isAssigningField(constructors.get(0), variableDeclarationFragment.resolveBinding(),
fieldName)) {
problemCollector.accept(createProblem(cu, field, fieldName, docUri));
}
} else {
List<MethodDeclaration> autowiredConstructors = constructors.stream()
.filter(constr -> RewriteQuickFixUtils.findAnnotation(constr,
Annotations.AUTOWIRED, true) != null)
.limit(2).collect(Collectors.toList());
if (autowiredConstructors.size() == 1) {
if (!isCompleteAst) {
completeAstRequired.set(true);
return false;
} else if (!isAssigningField(autowiredConstructors.get(0),
variableDeclarationFragment.resolveBinding(), fieldName)) {
problemCollector.accept(createProblem(cu, field, fieldName, docUri));
}
}
}
}
}
return true;
}
});
if (completeAstRequired.get()) {
throw new RequiredCompleteAstException();
}
}
}
private ReconcileProblemImpl createProblem(CompilationUnit cu, FieldDeclaration field, String fieldName,
URI docUri) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), LABEL, field.getStartPosition(),
field.getLength());
TypeDeclaration typeDecl = (TypeDeclaration) field.getParent();
String typeFqName = (cu.getPackage() != null && cu.getPackage().getName() != null
? cu.getPackage().getName().getFullyQualifiedName() + "."
: "") + typeDecl.getName().getFullyQualifiedName();
RewriteQuickFixUtils.setRewriteFixes(registry, problem,
List.of(new FixDescriptor(ID, List.of(docUri.toASCIIString()), LABEL)
.withRangeScope(RewriteQuickFixUtils.createOpenRewriteRange(cu, typeDecl))
.withParameters(Map.of("classFqName", typeFqName, "fieldName", fieldName))
.withRecipeScope(RecipeScope.NODE)));
return problem;
}
private static boolean isAssigningField(MethodDeclaration c, IVariableBinding binding, String fieldName) {
for (Object n : c.getBody().statements()) {
if (n instanceof ExpressionStatement && ((ExpressionStatement) n).getExpression() instanceof Assignment) {
Assignment assignment = (Assignment) ((ExpressionStatement) n).getExpression();
if (assignment.getLeftHandSide() instanceof FieldAccess) {
FieldAccess fa = (FieldAccess) assignment.getLeftHandSide();
if (fieldName.equals(fa.getName().getIdentifier()) && fa.getExpression() instanceof ThisExpression) {
return true;
}
}
if (assignment.getLeftHandSide() instanceof SimpleName) {
SimpleName simpleName = (SimpleName) assignment.getLeftHandSide();
if (fieldName.equals(simpleName.getIdentifier()) && binding.isEqualTo(simpleName.resolveBinding())) {
return true;
}
}
}
}
return false;
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(2, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.JAVA_CONSTRUCTOR_PARAMETER_INJECTION;
}
}

View File

@@ -11,14 +11,19 @@
package org.springframework.ide.vscode.boot.java.reconcilers;
import java.lang.reflect.Field;
import java.net.URI;
import java.util.List;
import java.util.UUID;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.IMethodBinding;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.MethodDeclaration;
import org.eclipse.jdt.core.dom.Modifier;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.openrewrite.java.spring.BeanMethodsNotPublic;
import org.openrewrite.marker.Range;
import org.slf4j.Logger;
@@ -33,14 +38,13 @@ import org.springframework.ide.vscode.commons.languageserver.quickfix.Quickfix.Q
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.IProblemCollector;
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.RecipeCodeActionDescriptor;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
import org.springframework.ide.vscode.commons.util.BadLocationException;
import org.springframework.ide.vscode.commons.util.text.IDocument;
public class BeanMethodNotPublicReconciler implements AnnotationReconciler {
public class BeanMethodNotPublicReconciler implements JdtAstReconciler {
private static final Logger log = LoggerFactory.getLogger(BeanMethodNotPublicReconciler.class);
@@ -53,34 +57,28 @@ public class BeanMethodNotPublicReconciler implements AnnotationReconciler {
this.quickfixRegistry = quickfixRegistry;
}
@Override
public void visit(IJavaProject project, IDocument doc, Annotation node, ITypeBinding typeBinding,
IProblemCollector problemCollector) {
if (Annotations.BEAN.equals(typeBinding.getQualifiedName()) && node.getParent() instanceof MethodDeclaration) {
private void visitAnnotation(IJavaProject project, CompilationUnit cu, URI docUri, Annotation node, IProblemCollector problemCollector) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null && Annotations.BEAN.equals(typeBinding.getQualifiedName()) && node.getParent() instanceof MethodDeclaration) {
MethodDeclaration method = (MethodDeclaration) node.getParent();
Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
if (version.getMajor() >= 2) {
IMethodBinding methodBinding = method.resolveBinding();
if (isNotOverridingPublicMethod(methodBinding)) {
ReconcileProblemImpl problem = ((List<?>)method.modifiers()).stream()
.filter(Modifier.class::isInstance)
.map(Modifier.class::cast)
.filter(modifier -> modifier.isPublic())
.findFirst()
.map(modifier -> new ReconcileProblemImpl(
Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD, Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD.getLabel(),
modifier.getStartPosition(), modifier.getLength()))
.orElse(new ReconcileProblemImpl(
Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD, Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD.getLabel(),
method.getName().getStartPosition(), method.getName().getLength()));
IMethodBinding methodBinding = method.resolveBinding();
if (isNotOverridingPublicMethod(methodBinding)) {
ReconcileProblemImpl problem = ((List<?>)method.modifiers()).stream()
.filter(Modifier.class::isInstance)
.map(Modifier.class::cast)
.filter(modifier -> modifier.isPublic())
.findFirst()
.map(modifier -> new ReconcileProblemImpl(
getProblemType(), Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD.getLabel(),
modifier.getStartPosition(), modifier.getLength()))
.orElse(new ReconcileProblemImpl(
getProblemType(), Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD.getLabel(),
method.getName().getStartPosition(), method.getName().getLength()));
addQuickFixes(doc, problem, method);
problemCollector.accept(problem);
}
addQuickFixes(cu, docUri, problem, method);
problemCollector.accept(problem);
}
}
}
@@ -101,26 +99,20 @@ public class BeanMethodNotPublicReconciler implements AnnotationReconciler {
return !isOverriding(methodBinding) && (methodBinding.getModifiers() & Modifier.PUBLIC) != 0;
}
private void addQuickFixes(IDocument doc, ReconcileProblemImpl problem, MethodDeclaration method) {
private void addQuickFixes(CompilationUnit cu, URI docUri, ReconcileProblemImpl problem, MethodDeclaration method) {
if (quickfixRegistry != null) {
FixDescriptor fix1 = new FixDescriptor(ID, List.of(doc.getUri()), LABEL)
.withRecipeScope(RecipeScope.NODE);
FixDescriptor fix1 = new FixDescriptor(ID, List.of(docUri.toASCIIString()), LABEL)
.withRecipeScope(RecipeScope.NODE)
.withRangeScope(RewriteQuickFixUtils.createOpenRewriteRange(cu, method));
try {
Range methodRange = createOpenRewriteRange(doc, method);
fix1 = fix1.withRangeScope(methodRange);
}
catch (BadLocationException e) {
log.warn("bad location happened while calculating method range for " + method.toString(), e);
}
Range methodRange = RewriteQuickFixUtils.createOpenRewriteRange(cu, method);
fix1 = fix1.withRangeScope(methodRange);
FixDescriptor fix2 = new FixDescriptor(ID, List.of(doc.getUri()), RecipeCodeActionDescriptor.buildLabel(LABEL, RecipeScope.FILE))
FixDescriptor fix2 = new FixDescriptor(ID, List.of(docUri.toASCIIString()), RecipeCodeActionDescriptor.buildLabel(LABEL, RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE);
FixDescriptor fix3 = new FixDescriptor(ID, List.of(doc.getUri()), RecipeCodeActionDescriptor.buildLabel(LABEL, RecipeScope.PROJECT))
FixDescriptor fix3 = new FixDescriptor(ID, List.of(docUri.toASCIIString()), RecipeCodeActionDescriptor.buildLabel(LABEL, RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT);
@@ -136,21 +128,49 @@ public class BeanMethodNotPublicReconciler implements AnnotationReconciler {
}
}
private Range createOpenRewriteRange(IDocument doc, MethodDeclaration method) throws BadLocationException {
int startOffset = method.getStartPosition();
int endOffset = method.getStartPosition() + method.getLength();
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) {
cu.accept(new ASTVisitor() {
int startLine = doc.getLineOfOffset(startOffset);
int endLine = doc.getLineOfOffset(endOffset);
int startColumn = startOffset - doc.getLineOffset(startLine);
int endColumn = endOffset - doc.getLineOffset(endLine);
Range.Position startPosition = new Range.Position(startOffset, startLine, startColumn);
Range.Position endPosition = new Range.Position(endOffset, endLine, endColumn);
return new Range(UUID.randomUUID(), startPosition, endPosition);
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
visitAnnotation(project, cu, docUri, node, problemCollector);
} catch (Exception e) {
}
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node) {
try {
visitAnnotation(project, cu, docUri, node, problemCollector);
} catch (Exception e) {
}
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
try {
visitAnnotation(project, cu, docUri, node, problemCollector);
} catch (Exception e) {
}
return super.visit(node);
}
});
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, SpringProjectUtil.SPRING_BOOT);
return version != null && version.getMajor() >= 2;
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.JAVA_PUBLIC_BEAN_METHOD;
}
}

View File

@@ -0,0 +1,109 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
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;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.SimpleType;
import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblemImpl;
public class Boot3NotSupportedTypeReconciler implements JdtAstReconciler {
private static final List<String> TYPE_FQNAMES = List.of(
"org.springframework.web.multipart.commons.CommonsMultipartResolver",
"java.lang.SecurityManager",
"java.security.AccessControlException"
);
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(ImportDeclaration node) {
String fqName = node.getName().getFullyQualifiedName();
if (TYPE_FQNAMES.contains(fqName)) {
problemCollector.accept(createProblem(fqName, node.getName().getStartPosition(), node.getName().getLength()));
}
return super.visit(node);
}
@Override
public boolean visit(SimpleType node) {
String fqName = processType(cu, node.getName().getFullyQualifiedName());
if (fqName != null) {
problemCollector.accept(createProblem(fqName, node.getStartPosition(), node.getLength()));
}
return super.visit(node);
}
});
}
private String processType(CompilationUnit cu, String name) {
if (TYPE_FQNAMES.contains(name)) {
return name;
} else {
for (String fqName : createFqNamesFromWildcardImports(cu, name)) {
if (TYPE_FQNAMES.contains(fqName)) {
return fqName;
}
}
}
return null;
}
private List<String> createFqNamesFromWildcardImports(CompilationUnit cu, String name) {
List<String> fqNames = new ArrayList<>();
for (Object im : cu.imports()) {
ImportDeclaration importDecl = (ImportDeclaration) im;
if (importDecl.isOnDemand()) {
fqNames.add(importDecl.getName().getFullyQualifiedName() + "." + name);
}
}
return fqNames;
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(3, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return Boot3JavaProblemType.JAVA_TYPE_NOT_SUPPORTED;
}
private static String createLabel(String type) {
StringBuilder sb = new StringBuilder();
sb.append("'");
sb.append(type);
sb.append("' not supported as of Spring Boot 3");
return sb.toString();
}
private ReconcileProblemImpl createProblem(String type, int offset, int length) {
return new ReconcileProblemImpl(getProblemType(), createLabel(type), offset, length);
}
}

View File

@@ -0,0 +1,28 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import java.net.URI;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ProblemType;
public interface JdtAstReconciler {
void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) throws RequiredCompleteAstException;
boolean isApplicable(IJavaProject project);
ProblemType getProblemType();
}

View File

@@ -11,25 +11,20 @@
package org.springframework.ide.vscode.boot.java.reconcilers;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.MarkerAnnotation;
import org.eclipse.jdt.core.dom.NormalAnnotation;
import org.eclipse.jdt.core.dom.SingleMemberAnnotation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.ide.vscode.boot.app.BootJavaConfig;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.handlers.SpelExpressionReconciler;
import org.springframework.ide.vscode.boot.java.utils.CompilationUnitCache;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
@@ -54,44 +49,24 @@ public class JdtReconciler implements JavaReconciler {
public static final String SPRING_CONDITIONAL_ON_EXPRESSION = "org.springframework.boot.autoconfigure.condition.ConditionalOnExpression";
private final CompilationUnitCache compilationUnitCache;
private final AnnotationReconciler[] reconcilers;
private final JdtAstReconciler[] reconcilers;
private final SpelExpressionReconciler spelExpressionReconciler;
private BootJavaConfig config;
public JdtReconciler(CompilationUnitCache compilationUnitCache, QuickfixRegistry quickfixRegistry, BootJavaConfig config) {
this.compilationUnitCache = compilationUnitCache;
this.config = config;
config.addListener(evt -> setSpelExpressionSyntaxValidationEnabled(config.isSpelExpressionValidationEnabled()));
this.spelExpressionReconciler = new SpelExpressionReconciler();
this.reconcilers = new AnnotationReconciler[] {
new AnnotationParamReconciler(Annotations.VALUE, null, "#{", "}", spelExpressionReconciler),
new AnnotationParamReconciler(Annotations.VALUE, "value", "#{", "}", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHEABLE, "key", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHEABLE, "condition", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHEABLE, "unless", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "key", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CACHE_EVICT, "condition", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_EVENT_LISTENER, "condition", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_AUTHORIZE, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_FILTER, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_PRE_FILTER, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_AUTHORIZE, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_FILTER, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_POST_FILTER, "value", "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, null, "", "", spelExpressionReconciler),
new AnnotationParamReconciler(SPRING_CONDITIONAL_ON_EXPRESSION, "value", "", "", spelExpressionReconciler),
new BeanMethodNotPublicReconciler(quickfixRegistry)
this.reconcilers = new JdtAstReconciler[] {
new AnnotationNodeReconciler(config),
new BeanMethodNotPublicReconciler(quickfixRegistry),
new AddConfigurationIfBeansPresentReconciler(quickfixRegistry),
new AutowiredFieldIntoConstructorParameterReconciler(quickfixRegistry),
new Boot3NotSupportedTypeReconciler(),
new NoAutowiredOnConstructorReconciler(quickfixRegistry),
new WebSecurityConfigurerAdapterReconciler(quickfixRegistry)
};
}
@@ -105,65 +80,54 @@ public class JdtReconciler implements JavaReconciler {
URI uri = URI.create(doc.getUri());
compilationUnitCache.withCompilationUnit(project, uri, cu -> {
if (cu != null) {
reconcileAST(project, doc, cu, problemCollector);
try {
reconcile(project, URI.create(doc.getUri()), cu, problemCollector, true);
} catch (RequiredCompleteAstException e) {
log.error("Unexpected incomplete AST", e);
}
}
log.info("reconciling (JDT): " + doc.getUri() + " done in " + (System.currentTimeMillis() - s) + "ms");
return null;
});
}
private void reconcileAST(IJavaProject project, IDocument doc, CompilationUnit cu, IProblemCollector problemCollector) {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(SingleMemberAnnotation node) {
try {
visitAnnotation(project, doc, node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
@Override
public boolean visit(NormalAnnotation node) {
try {
visitAnnotation(project, doc, node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
@Override
public boolean visit(MarkerAnnotation node) {
try {
visitAnnotation(project, doc, node, problemCollector);
}
catch (Exception e) {
}
return super.visit(node);
}
});
}
protected void visitAnnotation(IJavaProject project, IDocument doc, Annotation node, IProblemCollector problemCollector) {
ITypeBinding typeBinding = node.resolveTypeBinding();
if (typeBinding != null) {
for (int i = 0; i < reconcilers.length; i++) {
reconcilers[i].visit(project, doc, node, typeBinding, problemCollector);
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector, boolean isCompleteAst) throws RequiredCompleteAstException {
for (JdtAstReconciler reconciler : getApplicableReconcilers(project)) {
try {
reconciler.reconcile(project, docUri, cu, problemCollector, isCompleteAst);
} catch (RequiredCompleteAstException e) {
throw e;
} catch (Exception e) {
log.error("", e);
}
}
}
private List<JdtAstReconciler> getApplicableReconcilers(IJavaProject project) {
List<JdtAstReconciler> applicableReconcilers = new ArrayList<>(reconcilers.length);
for (JdtAstReconciler r : reconcilers) {
switch (config.getProblemApplicability(r.getProblemType())) {
case ON:
if (SpringProjectUtil.isBootProject(project)) {
applicableReconcilers.add(r);
}
break;
case OFF:
break;
default: // AUTO
if (r.isApplicable(project)) {
applicableReconcilers.add(r);
}
}
}
return applicableReconcilers;
}
@Override
public Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs, Runnable incrementProgress) {
if (config.isRewriteReconcileEnabled()) {
}
public Map<IDocument, Collection<ReconcileProblem>> reconcile(IJavaProject project, List<TextDocument> docs,
Runnable incrementProgress) {
return Collections.emptyMap();
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import static org.springframework.ide.vscode.commons.java.SpringProjectUtil.springBootVersionGreaterOrEqual;
import java.net.URI;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.Annotation;
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.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
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 ID = NoAutowiredOnConstructor.class.getName();
private static final String LABEL = "Remove Unnecessary @Autowired";
private QuickfixRegistry registry;
public NoAutowiredOnConstructorReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration typeDecl) {
Path sourceFile = Paths.get(docUri);
if (IClasspathUtil.getProjectJavaSourceFoldersWithoutTests(project.getClasspath())
.anyMatch(f -> sourceFile.startsWith(f.toPath()))) {
int constructorCount = 0;
MethodDeclaration constructor = null;
for (MethodDeclaration method : typeDecl.getMethods()) {
if (method.isConstructor()) {
constructorCount++;
if (constructorCount > 1) {
return super.visit(typeDecl);
} else {
constructor = method;
}
}
}
if (constructor != null) {
Annotation autowiredAnnotation = RewriteQuickFixUtils.findAnnotation(constructor,
Annotations.AUTOWIRED, false);
if (autowiredAnnotation != null) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), LABEL,
autowiredAnnotation.getStartPosition(), autowiredAnnotation.getLength());
RewriteQuickFixUtils.setRewriteFixes(registry, problem,
List.of(new FixDescriptor(ID, List.of(docUri.toASCIIString()), LABEL)
.withRecipeScope(RecipeScope.NODE)
.withRangeScope(RewriteQuickFixUtils.createOpenRewriteRange(cu, typeDecl))));
problemCollector.accept(problem);
}
}
}
return super.visit(typeDecl);
}
});
}
@Override
public boolean isApplicable(IJavaProject project) {
return springBootVersionGreaterOrEqual(2, 0, 0).test(project);
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.JAVA_AUTOWIRED_CONSTRUCTOR;
}
}

View File

@@ -0,0 +1,17 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
public class RequiredCompleteAstException extends Exception {
private static final long serialVersionUID = 1L;
}

View File

@@ -0,0 +1,93 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import java.util.Collection;
import java.util.Iterator;
import org.eclipse.jdt.core.dom.ASTNode;
import org.eclipse.jdt.core.dom.Annotation;
import org.eclipse.jdt.core.dom.BodyDeclaration;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.openrewrite.Tree;
import org.openrewrite.marker.Range;
import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchies;
import org.springframework.ide.vscode.boot.java.rewrite.RewriteRefactorings;
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 RewriteQuickFixUtils {
public static Range createOpenRewriteRange(CompilationUnit cu, ASTNode node) {
int startOffset = node.getStartPosition();
int startLine = cu.getLineNumber(startOffset);
int startColumn = cu.getColumnNumber(startOffset);
int endOffset = startOffset + node.getLength() - 1;
int endLine = cu.getLineNumber(endOffset);
int endColumn = cu.getColumnNumber(endOffset);
Range.Position startPosition = new Range.Position(startOffset, startLine, startColumn);
Range.Position endPosition = new Range.Position(endOffset, endLine, endColumn);
return new Range(Tree.randomId(), startPosition, endPosition);
}
public static QuickfixType getRewriteQuickFixType(QuickfixRegistry registry) {
return registry.getQuickfixType(RewriteRefactorings.REWRITE_RECIPE_QUICKFIX);
}
public static void setRewriteFixes(QuickfixRegistry registry, ReconcileProblemImpl problem, Collection<FixDescriptor> fixDescritptors) {
QuickfixType quickFixType = getRewriteQuickFixType(registry);
for (FixDescriptor f : fixDescritptors) {
problem.addQuickfix(new QuickfixData<>(quickFixType, f, f.getLabel()));
}
}
public static String buildLabel(String label, RecipeScope s) {
switch (s) {
case FILE:
return label + " in file";
case PROJECT:
return label + " in project";
default:
return label;
}
}
public static Annotation findAnnotation(BodyDeclaration decl, String annotationFqType, boolean includeMetaHierarchy) {
for (Iterator<?> itr = decl.modifiers().iterator(); itr.hasNext();) {
Object mod = itr.next();
if (mod instanceof Annotation) {
Annotation a = (Annotation) mod;
ITypeBinding aType = a.resolveTypeBinding();
if (aType != null && (
(includeMetaHierarchy && AnnotationHierarchies.isSubtypeOf(a, annotationFqType)) || (!includeMetaHierarchy && annotationFqType.equals(aType.getQualifiedName()))
)) {
return (Annotation) mod;
}
}
}
return null;
}
public static ITypeBinding getDeepErasureType(ITypeBinding type) {
for (; type != type.getErasure(); type = type.getErasure()) {}
return type;
}
}

View File

@@ -0,0 +1,149 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers;
import java.net.URI;
import java.util.List;
import org.eclipse.jdt.core.dom.ASTVisitor;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.ITypeBinding;
import org.eclipse.jdt.core.dom.ImportDeclaration;
import org.eclipse.jdt.core.dom.SimpleType;
import org.eclipse.jdt.core.dom.Type;
import org.eclipse.jdt.core.dom.TypeDeclaration;
import org.openrewrite.java.spring.security5.WebSecurityConfigurerAdapter;
import org.springframework.ide.vscode.boot.java.Annotations;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.java.SpringProjectUtil;
import org.springframework.ide.vscode.commons.java.Version;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.IProblemCollector;
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.RecipeCodeActionDescriptor;
import org.springframework.ide.vscode.commons.rewrite.config.RecipeScope;
import org.springframework.ide.vscode.commons.rewrite.java.FixDescriptor;
public class WebSecurityConfigurerAdapterReconciler implements JdtAstReconciler {
private static final String ID = WebSecurityConfigurerAdapter.class.getName();
private static final String WEB_SECURITY_CONFIGURER_ADAPTER = "WebSecurityConfigurerAdapter";
private static final String FQN_WEB_SECURITY_CONFIGURER_ADAPTER = "org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter";
private static final String PROBLEM_LABEL = "Class extends 'WebSecurityConfigurerAdapter' which is removed in Spring-Security 6.x";
private static final String FIX_LABEL = "Refactor class into a Configuration bean not extending 'WebSecurityConfigurerAdapter'";
private static final String STUB_WEB_SECURITY_CONFIG_ADAPTER = """
package org.springframework.security.config.annotation.web.configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.WebSecurityConfigurer;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.builders.WebSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
public abstract class WebSecurityConfigurerAdapter {
public void init(WebSecurity web) throws Exception {}
public AuthenticationManager authenticationManagerBean() throws Exception { return null; }
public UserDetailsService userDetailsServiceBean() throws Exception { return null; }
protected void configure(HttpSecurity http) throws Exception {}
public void configure(WebSecurity web) throws Exception {}
protected void configure(AuthenticationManagerBuilder auth) throws Exception {}
}
""";
private QuickfixRegistry registry;
public WebSecurityConfigurerAdapterReconciler(QuickfixRegistry registry) {
this.registry = registry;
}
@Override
public void reconcile(IJavaProject project, URI docUri, CompilationUnit cu, IProblemCollector problemCollector,
boolean isCompleteAst) throws RequiredCompleteAstException {
cu.accept(new ASTVisitor() {
@Override
public boolean visit(TypeDeclaration typeDecl) {
Type type = typeDecl.getSuperclassType();
if (isWebSecurityConfigurerAdapter(cu, type)) {
ReconcileProblemImpl problem = new ReconcileProblemImpl(getProblemType(), PROBLEM_LABEL, type.getStartPosition(), type.getLength());
if (RewriteQuickFixUtils.findAnnotation(typeDecl, Annotations.CONFIGURATION, true) != null) {
ITypeBinding resolveBinding = type.resolveBinding();
String[] typeStubs = resolveBinding == null || resolveBinding.isRecovered() ? new String[] { STUB_WEB_SECURITY_CONFIG_ADAPTER } : new String[0];
String uri = docUri.toASCIIString();
RewriteQuickFixUtils.setRewriteFixes(registry, problem, List.of(
new FixDescriptor(ID, List.of(uri),
RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.FILE))
.withRecipeScope(RecipeScope.FILE)
.withTypeStubs(typeStubs),
new FixDescriptor(ID, List.of(uri),
RecipeCodeActionDescriptor.buildLabel(FIX_LABEL, RecipeScope.PROJECT))
.withRecipeScope(RecipeScope.PROJECT)
.withTypeStubs(typeStubs))
);
}
problemCollector.accept(problem);
}
return super.visit(typeDecl);
}
});
}
private static boolean isWebSecurityConfigurerAdapter(CompilationUnit cu, Type type) {
if (type.isSimpleType()) {
String simpleName = ((SimpleType) type).getName().getFullyQualifiedName();
if (FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals(simpleName)) {
return true;
} else if (WEB_SECURITY_CONFIGURER_ADAPTER.equals(simpleName)) {
// check imports
for (Object im : cu.imports()) {
ImportDeclaration importDecl = (ImportDeclaration) im;
if (importDecl.isOnDemand()) {
if (FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals(importDecl.getName().getFullyQualifiedName() + "." + WEB_SECURITY_CONFIGURER_ADAPTER)) {
return true;
}
} else if (FQN_WEB_SECURITY_CONFIGURER_ADAPTER.equals(importDecl.getName().getFullyQualifiedName())) {
return true;
}
}
}
}
return false;
}
@Override
public boolean isApplicable(IJavaProject project) {
Version version = SpringProjectUtil.getDependencyVersion(project, "spring-security-config");
return version != null && version.compareTo(new Version(5, 7, 0, null)) >= 0 && version.compareTo(new Version(6, 1, 0, null)) < 0;
}
@Override
public ProblemType getProblemType() {
return Boot2JavaProblemType.WEB_SECURITY_CONFIGURER_ADAPTER;
}
}

View File

@@ -202,7 +202,8 @@ public class RewriteRefactorings implements CodeActionResolver, QuickfixHandler
if (j != null) {
Range range = j.getMarkers().findFirst(Range.class).orElse(null);
if (range != null) {
return d.getRangeScope().getStart().getOffset() <= range.getStart().getOffset() && range.getEnd().getOffset() <= d.getRangeScope().getEnd().getOffset();
// Rewrite range end offset is up to not including hence -1
return d.getRangeScope().getStart().getOffset() <= range.getStart().getOffset() && range.getEnd().getOffset() - 1 <= d.getRangeScope().getEnd().getOffset();
}
}
return false;

View File

@@ -58,8 +58,9 @@ import org.springframework.ide.vscode.boot.java.annotations.AnnotationHierarchyA
import org.springframework.ide.vscode.boot.java.beans.CachedBean;
import org.springframework.ide.vscode.boot.java.handlers.EnhancedSymbolInformation;
import org.springframework.ide.vscode.boot.java.handlers.SymbolProvider;
import org.springframework.ide.vscode.boot.java.reconcilers.AnnotationReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.CachedDiagnostics;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtReconciler;
import org.springframework.ide.vscode.commons.java.IClasspath;
import org.springframework.ide.vscode.commons.java.IClasspathUtil;
import org.springframework.ide.vscode.commons.java.IJavaProject;
@@ -99,7 +100,7 @@ public class SpringIndexerJava implements SpringIndexer {
private final SymbolHandler symbolHandler;
private final AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders;
private final List<AnnotationReconciler> reconcilers;
private final JdtReconciler reconciler;
private final IndexCache cache;
private final JavaProjectFinder projectFinder;
private final ProgressService progressService;
@@ -114,12 +115,12 @@ public class SpringIndexerJava implements SpringIndexer {
public SpringIndexerJava(SymbolHandler symbolHandler, AnnotationHierarchyAwareLookup<SymbolProvider> symbolProviders, IndexCache cache,
JavaProjectFinder projectFimder, ProgressService progressService, List<AnnotationReconciler> reconcilers,
JavaProjectFinder projectFimder, ProgressService progressService, JdtReconciler jdtReconciler,
BiFunction<AtomicReference<TextDocument>, BiConsumer<String, Diagnostic>, IProblemCollector> problemCollectorCreator,
JsonObject validationSeveritySettings) {
this.symbolHandler = symbolHandler;
this.symbolProviders = symbolProviders;
this.reconcilers = reconcilers;
this.reconciler = jdtReconciler;
this.cache = cache;
this.projectFinder = projectFimder;
this.progressService = progressService;
@@ -641,6 +642,13 @@ public class SpringIndexerJava implements SpringIndexer {
}
});
// reconciling
try {
reconciler.reconcile(context.getProject(), URI.create(context.getDocURI()), context.getCu(), context.getProblemCollector(), context.getPass() == SCAN_PASS.TWO);
} catch (RequiredCompleteAstException e) {
context.getNextPassFiles().add(context.getFile());
}
dependencyTracker.update(context.getFile(), context.getDependencies());;
}
@@ -686,11 +694,6 @@ public class SpringIndexerJava implements SpringIndexer {
}
}
// reconciling
for (AnnotationReconciler reconciler : this.reconcilers) {
reconciler.visit(context.getProject(), context.getDocRef().get(), node, typeBinding, context.getProblemCollector());
}
}
else {
log.debug("type binding not around: " + context.getDocURI() + " - " + node.toString());
@@ -715,7 +718,7 @@ public class SpringIndexerJava implements SpringIndexer {
return null;
}
private ASTParser createParser(IJavaProject project, boolean ignoreMethodBodies) throws Exception {
public static ASTParser createParser(IJavaProject project, boolean ignoreMethodBodies) throws Exception {
String[] classpathEntries = getClasspathEntries(project);
String[] sourceEntries = getSourceEntries(project);
@@ -733,7 +736,7 @@ public class SpringIndexerJava implements SpringIndexer {
return parser;
}
private String[] getClasspathEntries(IJavaProject project) throws Exception {
private static String[] getClasspathEntries(IJavaProject project) throws Exception {
IClasspath classpath = project.getClasspath();
Stream<File> classpathEntries = IClasspathUtil.getAllBinaryRoots(classpath).stream();
return classpathEntries
@@ -742,7 +745,7 @@ public class SpringIndexerJava implements SpringIndexer {
.toArray(String[]::new);
}
private String[] getSourceEntries(IJavaProject project) throws Exception {
private static String[] getSourceEntries(IJavaProject project) throws Exception {
IClasspath classpath = project.getClasspath();
Stream<File> sourceEntries = IClasspathUtil.getSourceFolders(classpath);
return sourceEntries

View File

@@ -0,0 +1,224 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.fail;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.Boot2JavaProblemType;
import org.springframework.ide.vscode.boot.java.reconcilers.AutowiredFieldIntoConstructorParameterReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class AutowiredFieldIntoConstructorParameterReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "autowiredfieldtest";
}
@Override
protected String getProjectName() {
return "test-spring-indexing";
}
protected JdtAstReconciler getReconciler() {
return new AutowiredFieldIntoConstructorParameterReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void noConstructors() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
String a;
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.JAVA_CONSTRUCTOR_PARAMETER_INJECTION, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("@Autowired\n\tString a;", markedStr);
assertEquals(1, problem.getQuickfixes().size());
}
@Test
void singleSimpleConstructorsNoMethodBodies() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
String a;
A() {
}
}
""";
try {
reconcile("A.java", source, false);
fail("Should require complete AST. Exception must be thrown.");
} catch (RequiredCompleteAstException e) {
}
}
@Test
void singleSimpleConstructorsWithMethodBodies() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
String a;
A() {
}
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.JAVA_CONSTRUCTOR_PARAMETER_INJECTION, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("@Autowired\n\tString a;", markedStr);
assertEquals(1, problem.getQuickfixes().size());
}
@Test
void constrctorAssignsField() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
String a;
A() {
this.a = "qq"
}
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(0, problems.size());
}
@Test
void autowiredConstructor() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
String a;
A(int y) {
}
@Autowired
A() {
}
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.JAVA_CONSTRUCTOR_PARAMETER_INJECTION, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("@Autowired\n\tString a;", markedStr);
assertEquals(1, problem.getQuickfixes().size());
}
@Test
void autowiredConstructorAssigningField() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
String a;
A(int y) {
}
@Autowired
A() {
this.a = "qq"
}
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, true);
assertEquals(0, problems.size());
}
}

View File

@@ -0,0 +1,101 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertFalse;
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Comparator;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jdt.core.dom.CompilationUnit;
import org.eclipse.jdt.core.dom.FileASTRequestor;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.RequiredCompleteAstException;
import org.springframework.ide.vscode.boot.java.utils.SpringIndexerJava;
import org.springframework.ide.vscode.boot.java.value.test.ValueSpelExpressionValidationTest.TestProblemCollector;
import org.springframework.ide.vscode.commons.java.IJavaProject;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
import org.springframework.ide.vscode.project.harness.ProjectsHarness;
public abstract class BaseReconcilerTest {
protected IJavaProject project;
abstract protected String getFolder();
abstract protected String getProjectName();
abstract protected JdtAstReconciler getReconciler();
private Path createFile(String name, String content) throws IOException {
Path filePath = Paths.get(project.getLocationUri()).resolve("src/main/java").resolve(getFolder()).resolve(name);
Files.createDirectories(filePath.getParent());
Files.createFile(filePath);
Files.write(filePath, content.getBytes(StandardCharsets.UTF_8));
return filePath;
}
private void clearTestFiles() throws IOException, URISyntaxException {
Path projectPath = Paths.get(getClass().getResource("/test-projects/" + getProjectName()).toURI());
Path toRemove = projectPath.resolve("src/main/java").resolve(getFolder());
if (Files.exists(toRemove)) {
Files.walk(toRemove).sorted(Comparator.reverseOrder()).forEach(path -> {
try {
Files.delete(path);
} catch (IOException e) {
e.printStackTrace();
}
});
}
assertFalse(Files.exists(toRemove));
}
void setup() throws Exception {
clearTestFiles();
project = ProjectsHarness.INSTANCE.mavenProject(getProjectName());
}
void tearDown() throws Exception {
clearTestFiles();
}
List<ReconcileProblem> reconcile(String fileName, String source, boolean isCompleteAst) throws Exception {
Path path = createFile(fileName, source);
TestProblemCollector problemCollector = new TestProblemCollector();
AtomicBoolean requiredCompleteAst = new AtomicBoolean(false);
SpringIndexerJava.createParser(project, !isCompleteAst).createASTs(new String[] { path.toFile().toString() }, null, new String[0], new FileASTRequestor() {
@Override
public void acceptAST(String sourceFilePath, CompilationUnit cu) {
try {
getReconciler().reconcile(project, path.toUri(), cu, problemCollector, isCompleteAst);
} catch (RequiredCompleteAstException e) {
requiredCompleteAst.set(true);
}
}
}, null);
if (requiredCompleteAst.get()) {
throw new RequiredCompleteAstException();
}
return problemCollector.getCollectedProblems();
}
}

View File

@@ -0,0 +1,152 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ide.vscode.boot.java.Boot3JavaProblemType;
import org.springframework.ide.vscode.boot.java.reconcilers.Boot3NotSupportedTypeReconciler;
import org.springframework.ide.vscode.boot.java.reconcilers.JdtAstReconciler;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class Boot3NotSupportedTypeReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "boot3notsupportedtypes";
}
@Override
protected String getProjectName() {
return "test-spring-indexing";
}
@Override
protected JdtAstReconciler getReconciler() {
return new Boot3NotSupportedTypeReconciler();
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void imports() throws Exception {
String source = """
package example.demo;
import org.springframework.web.multipart.commons.CommonsMultipartResolver;
class A {
CommonsMultipartResolver a;
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot3JavaProblemType.JAVA_TYPE_NOT_SUPPORTED, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("org.springframework.web.multipart.commons.CommonsMultipartResolver", markedStr);
assertEquals(0, problem.getQuickfixes().size());
}
@Test
void fqType() throws Exception {
String source = """
package example.demo;
class A {
org.springframework.web.multipart.commons.CommonsMultipartResolver a;
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot3JavaProblemType.JAVA_TYPE_NOT_SUPPORTED, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("org.springframework.web.multipart.commons.CommonsMultipartResolver", markedStr);
assertEquals(0, problem.getQuickfixes().size());
}
@Test
void importWithWildcard() throws Exception {
String source = """
package example.demo;
import org.springframework.web.multipart.commons.*;
class A {
CommonsMultipartResolver a;
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot3JavaProblemType.JAVA_TYPE_NOT_SUPPORTED, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("CommonsMultipartResolver", markedStr);
assertEquals(0, problem.getQuickfixes().size());
}
@Test
void importWithWildcardNoProblem() throws Exception {
String source = """
package example.demo;
import org.springframework.web.multipart.*;
class A {
CommonsMultipartResolver a;
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(0, problems.size());
}
}

View File

@@ -0,0 +1,103 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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;
public class NoAutowiredOnConstructorReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "noautowiredonconstructor";
}
@Override
protected String getProjectName() {
return "test-spring-validations";
}
@Override
protected JdtAstReconciler getReconciler() {
return new NoAutowiredOnConstructorReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void singleConstructors() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
A() {};
}
""";
List<ReconcileProblem> 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());
}
@Test
void multipleConstructors() throws Exception {
String source = """
package example.demo;
import org.springframework.beans.factory.annotation.Autowired;
class A {
@Autowired
A() {};
A(int k) {}
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(0, problems.size());
}
}

View File

@@ -0,0 +1,155 @@
/*******************************************************************************
* Copyright (c) 2023 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.reconcilers.test;
import static org.junit.jupiter.api.Assertions.assertEquals;
import java.util.List;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
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.WebSecurityConfigurerAdapterReconciler;
import org.springframework.ide.vscode.commons.languageserver.quickfix.QuickfixRegistry;
import org.springframework.ide.vscode.commons.languageserver.reconcile.ReconcileProblem;
public class WebSecurityConfigurerAdapterReconcilerTest extends BaseReconcilerTest {
@Override
protected String getFolder() {
return "websecurityconfigurer";
}
@Override
protected String getProjectName() {
return "test-spring-indexing";
}
protected JdtAstReconciler getReconciler() {
return new WebSecurityConfigurerAdapterReconciler(new QuickfixRegistry());
}
@BeforeEach
void setup() throws Exception {
super.setup();
}
@AfterEach
void tearDown() throws Exception {
super.tearDown();
}
@Test
void typeImport() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.context.annotation.Configuration;
@Configuration
class A extends WebSecurityConfigurerAdapter {
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.WEB_SECURITY_CONFIGURER_ADAPTER, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("WebSecurityConfigurerAdapter", markedStr);
assertEquals(2, problem.getQuickfixes().size());
}
@Test
void typeImportWithWildCard() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.configuration.*;
import org.springframework.context.annotation.Configuration;
@Configuration
class A extends WebSecurityConfigurerAdapter {
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.WEB_SECURITY_CONFIGURER_ADAPTER, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("WebSecurityConfigurerAdapter", markedStr);
assertEquals(2, problem.getQuickfixes().size());
}
@Test
void fqType() throws Exception {
String source = """
package example.demo;
import org.springframework.context.annotation.Configuration;
@Configuration
class A extends org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter {
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.WEB_SECURITY_CONFIGURER_ADAPTER, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter", markedStr);
assertEquals(2, problem.getQuickfixes().size());
}
@Test
void notConfigBean() throws Exception {
String source = """
package example.demo;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
class A extends WebSecurityConfigurerAdapter {
}
""";
List<ReconcileProblem> problems = reconcile("A.java", source, false);
assertEquals(1, problems.size());
ReconcileProblem problem = problems.get(0);
assertEquals(Boot2JavaProblemType.WEB_SECURITY_CONFIGURER_ADAPTER, problem.getType());
String markedStr = source.substring(problem.getOffset(), problem.getOffset() + problem.getLength());
assertEquals("WebSecurityConfigurerAdapter", markedStr);
assertEquals(0, problem.getQuickfixes().size());
}
}

View File

@@ -347,7 +347,7 @@ public class ValueSpelExpressionValidationTest {
problems.add(problem);
}
protected List<ReconcileProblem> getCollectedProblems() {
public List<ReconcileProblem> getCollectedProblems() {
return problems;
}

View File

@@ -39,6 +39,10 @@
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>