First batch of reconcilers migrated to JDT
This commit is contained in:
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
// );
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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) {
|
||||
|
||||
@@ -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);
|
||||
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
|
||||
}
|
||||
}
|
||||
@@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
@@ -347,7 +347,7 @@ public class ValueSpelExpressionValidationTest {
|
||||
problems.add(problem);
|
||||
}
|
||||
|
||||
protected List<ReconcileProblem> getCollectedProblems() {
|
||||
public List<ReconcileProblem> getCollectedProblems() {
|
||||
return problems;
|
||||
}
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user