-
Notifications
You must be signed in to change notification settings - Fork 20
🐛 Fix annotation matching #149
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6102a1e
6b645cf
d0da87c
cafa848
1ab427a
a05bf5f
4e1f545
d15171d
9fca62b
4ec47b6
3c2c2d1
d1b35c4
daf8d09
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,45 +6,107 @@ | |
| import java.util.List; | ||
|
|
||
| import io.konveyor.tackle.core.internal.query.AnnotationQuery; | ||
| import io.konveyor.tackle.core.internal.symbol.CustomASTVisitor.QueryLocation; | ||
|
|
||
| import org.eclipse.core.runtime.CoreException; | ||
| import org.eclipse.jdt.core.IAnnotatable; | ||
| import org.eclipse.jdt.core.IAnnotation; | ||
| import org.eclipse.jdt.core.IClassFile; | ||
| import org.eclipse.jdt.core.ICompilationUnit; | ||
| import org.eclipse.jdt.core.IJavaElement; | ||
| import org.eclipse.jdt.core.compiler.IProblem; | ||
| import org.eclipse.jdt.core.dom.AST; | ||
| import org.eclipse.jdt.core.dom.ASTParser; | ||
| import org.eclipse.jdt.core.dom.CompilationUnit; | ||
| import org.eclipse.jdt.core.search.SearchMatch; | ||
| import org.eclipse.jdt.internal.core.ResolvedSourceField; | ||
| import org.eclipse.jdt.internal.core.ResolvedSourceMethod; | ||
| import org.eclipse.jdt.internal.core.ResolvedSourceType; | ||
| import org.eclipse.jdt.internal.core.SourceRefElement; | ||
| import org.eclipse.lsp4j.Location; | ||
| import org.eclipse.lsp4j.SymbolInformation; | ||
|
|
||
| public class AnnotationSymbolProvider implements SymbolProvider, WithAnnotationQuery { | ||
| public class AnnotationSymbolProvider implements SymbolProvider, WithQuery, WithAnnotationQuery { | ||
|
|
||
| private AnnotationQuery annotationQuery; | ||
| private String query; | ||
|
|
||
| @Override | ||
| public List<SymbolInformation> get(SearchMatch match) throws CoreException { | ||
| List<SymbolInformation> symbols = new ArrayList<>(); | ||
| try { | ||
| IAnnotatable mod = (IAnnotatable) match.getElement(); | ||
| IJavaElement element = (IJavaElement) match.getElement(); | ||
| for (IAnnotation annotation : mod.getAnnotations()) { | ||
| IAnnotatable annotatable = (IAnnotatable) match.getElement(); | ||
| for (IAnnotation annotation : annotatable.getAnnotations()) { | ||
| IJavaElement annotationElement = annotation.getPrimaryElement(); | ||
| SymbolInformation symbol = new SymbolInformation(); | ||
| symbol.setName(annotation.getElementName()); | ||
| symbol.setKind(convertSymbolKind(element)); | ||
| symbol.setKind(convertSymbolKind(annotationElement)); | ||
| symbol.setContainerName(annotation.getParent().getElementName()); | ||
| symbol.setLocation(getLocation(element, match)); | ||
|
|
||
| if (annotationQuery != null) { | ||
| List<Class<? extends SourceRefElement>> classes = new ArrayList<>(); | ||
| classes.add(ResolvedSourceMethod.class); | ||
| classes.add(ResolvedSourceField.class); | ||
| classes.add(ResolvedSourceType.class); | ||
| if (matchesAnnotationQuery(match, classes)) { | ||
| symbols.add(symbol); | ||
| Location location = getLocation(annotationElement, match); | ||
| symbol.setLocation(location); | ||
| if (this.query.contains(".")) { | ||
| // First try to get compilation unit for source files | ||
| ICompilationUnit unit = (ICompilationUnit) annotationElement.getAncestor(IJavaElement.COMPILATION_UNIT); | ||
| if (unit == null) { | ||
| // If not in source, try to get class file for compiled classes | ||
| IClassFile cls = (IClassFile) annotationElement.getAncestor(IJavaElement.CLASS_FILE); | ||
| if (cls != null) { | ||
| unit = cls.getWorkingCopy(new WorkingCopyOwnerImpl(), null); | ||
| } | ||
| } | ||
| if (this.queryQualificationMatches(this.query.replaceAll("\\(([A-Za-z_][A-Za-z0-9_]*(\\|[A-Za-z_][A-Za-z0-9_]*)*)\\)", ".*"), annotationElement, unit, location)) { | ||
| ASTParser astParser = ASTParser.newParser(AST.getJLSLatest()); | ||
| astParser.setSource(unit); | ||
| astParser.setResolveBindings(true); | ||
| CompilationUnit cu = (CompilationUnit) astParser.createAST(null); | ||
| CustomASTVisitor visitor = new CustomASTVisitor(query, match, QueryLocation.ANNOTATION); | ||
| // Under tests, resolveConstructorBinding will return null if there are problems | ||
| IProblem[] problems = cu.getProblems(); | ||
| if (problems != null && problems.length > 0) { | ||
| logInfo("KONVEYOR_LOG: " + "Found " + problems.length + " problems while compiling"); | ||
| int count = 0; | ||
| for (IProblem problem : problems) { | ||
| logInfo("KONVEYOR_LOG: Problem - ID: " + problem.getID() + " Message: " + problem.getMessage()); | ||
| count++; | ||
| if (count >= SymbolProvider.MAX_PROBLEMS_TO_LOG) { | ||
| logInfo("KONVEYOR_LOG: Only showing first " + SymbolProvider.MAX_PROBLEMS_TO_LOG + " problems, " + (problems.length - SymbolProvider.MAX_PROBLEMS_TO_LOG) + " more not displayed"); | ||
| break; | ||
| } | ||
| } | ||
| } | ||
| cu.accept(visitor); | ||
| if (visitor.symbolMatches()) { | ||
| if (annotationQuery != null) { | ||
| List<Class<? extends SourceRefElement>> classes = new ArrayList<>(); | ||
| classes.add(ResolvedSourceMethod.class); | ||
| classes.add(ResolvedSourceField.class); | ||
| classes.add(ResolvedSourceType.class); | ||
| if (matchesAnnotationQuery(match, classes)) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. is there a way to make the matchesAnnotationQuery be like a Go vardic function? |
||
| symbols.add(symbol); | ||
| } | ||
| } else { | ||
| symbols.add(symbol); | ||
| } | ||
jmle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
| } | ||
| if (unit != null && unit.isWorkingCopy()) { | ||
| unit.discardWorkingCopy(); | ||
| unit.close(); | ||
| } | ||
| } else { | ||
| symbols.add(symbol); | ||
| if (annotationQuery != null) { | ||
| List<Class<? extends SourceRefElement>> classes = new ArrayList<>(); | ||
| classes.add(ResolvedSourceMethod.class); | ||
| classes.add(ResolvedSourceField.class); | ||
| classes.add(ResolvedSourceType.class); | ||
| if (matchesAnnotationQuery(match, classes)) { | ||
| symbols.add(symbol); | ||
| } | ||
| } else { | ||
| symbols.add(symbol); | ||
| } | ||
| } | ||
|
|
||
| } | ||
| return symbols; | ||
| } catch (Exception e) { | ||
|
|
@@ -60,4 +122,9 @@ public AnnotationQuery getAnnotationQuery() { | |
| public void setAnnotationQuery(AnnotationQuery annotationQuery) { | ||
| this.annotationQuery = annotationQuery; | ||
| } | ||
|
|
||
| @Override | ||
| public void setQuery(String query) { | ||
| this.query = query; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,14 @@ | |
| import org.eclipse.jdt.core.dom.ASTVisitor; | ||
| import org.eclipse.jdt.core.dom.ClassInstanceCreation; | ||
| import org.eclipse.jdt.core.dom.ConstructorInvocation; | ||
| import org.eclipse.jdt.core.dom.IAnnotationBinding; | ||
| import org.eclipse.jdt.core.dom.IMethodBinding; | ||
| import org.eclipse.jdt.core.dom.ITypeBinding; | ||
| import org.eclipse.jdt.core.dom.Annotation; | ||
| import org.eclipse.jdt.core.dom.MarkerAnnotation; | ||
| import org.eclipse.jdt.core.dom.MethodInvocation; | ||
| import org.eclipse.jdt.core.dom.NormalAnnotation; | ||
| import org.eclipse.jdt.core.dom.SingleMemberAnnotation; | ||
| import org.eclipse.jdt.core.search.SearchMatch; | ||
|
|
||
| /* | ||
|
|
@@ -32,6 +37,7 @@ public class CustomASTVisitor extends ASTVisitor { | |
| public enum QueryLocation { | ||
| METHOD_CALL, | ||
| CONSTRUCTOR_CALL, | ||
| ANNOTATION, | ||
| } | ||
|
|
||
| public CustomASTVisitor(String query, SearchMatch match, QueryLocation location) { | ||
|
|
@@ -62,6 +68,64 @@ private boolean shouldVisit(ASTNode node) { | |
| (node.getStartPosition() + node.getLength()); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean visit(MarkerAnnotation node) { | ||
| return visit((Annotation) node); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean visit(NormalAnnotation node) { | ||
| return visit((Annotation) node); | ||
| } | ||
|
|
||
| @Override | ||
| public boolean visit(SingleMemberAnnotation node) { | ||
| return visit((Annotation) node); | ||
| } | ||
|
|
||
| private boolean visit(Annotation node) { | ||
| // There is a problem with trying to run shouldVisit() here because | ||
| // matches on annotations aren't directly on the annotation node, | ||
| // but on the annotated one (class, method, field, etc). So we can't | ||
| // use shouldVisit() to filter out nodes we don't want to visit. | ||
| // TODO: think of a better way to handle this | ||
| if (this.location != QueryLocation.ANNOTATION) { | ||
| return true; | ||
| } | ||
| try { | ||
| IAnnotationBinding binding = node.resolveAnnotationBinding(); | ||
| if (binding != null) { | ||
| // get fqn | ||
| ITypeBinding declaringClass = binding.getAnnotationType(); | ||
| if (declaringClass != null) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. can we invert and return early so that we don't have nested depth of logic? |
||
| // Handle Erasure results | ||
| if (declaringClass.getErasure() != null) { | ||
| declaringClass = declaringClass.getErasure(); | ||
| } | ||
| String fullyQualifiedName = declaringClass.getQualifiedName(); | ||
| // match fqn with query pattern | ||
| if (fullyQualifiedName.matches(this.query)) { | ||
| this.symbolMatches = true; | ||
jmle marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| return false; | ||
| } else { | ||
| logInfo("method fqn " + fullyQualifiedName + " did not match with " + query); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Fix copy-paste errors in log messages. The log messages reference "method" and "MethodInvocation" but should reference "annotation" since this is the annotation visitor:
Apply this diff: - logInfo("method fqn " + fullyQualifiedName + " did not match with " + query);
+ logInfo("annotation fqn " + fullyQualifiedName + " did not match with " + query);- logInfo("failed to get accurate info for MethodInvocation, falling back");
+ logInfo("failed to get accurate info for Annotation, falling back");- logInfo("KONVEYOR_LOG: error visiting MethodInvocation node: " + e);
+ logInfo("KONVEYOR_LOG: error visiting Annotation node: " + e);Also applies to: 105-105, 111-111 🤖 Prompt for AI Agents |
||
| return true; | ||
| } | ||
| } | ||
| } | ||
| logInfo("failed to get accurate info for MethodInvocation, falling back"); | ||
| // sometimes binding or declaring class cannot be found, usually due to errors | ||
| // in source code. in that case, we will fallback and accept the match | ||
| this.symbolMatches = true; | ||
| return false; | ||
| } catch (Exception e) { | ||
| logInfo("KONVEYOR_LOG: error visiting MethodInvocation node: " + e); | ||
| // this is so that we fallback and don't lose a match when we fail | ||
| this.symbolMatches = true; | ||
| return false; | ||
| } | ||
| } | ||
|
|
||
| /* | ||
| * This is to get information from a MethodInvocation, used for METHOD_CALL | ||
| * we discard a match only when we can tell for sure. otherwise we accept | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -188,7 +188,7 @@ private static void setPosition(Position position, int[] coords) { | |
| * 3. the compilation unit has a package declaration as `konveyor.io.Util` | ||
| * we do this so that we can rule out a lot of matches before going the AST route | ||
| */ | ||
| default boolean queryQualificationMatches(String query, ICompilationUnit unit, Location location) { | ||
| default boolean queryQualificationMatches(String query, IJavaElement matchedElement,ICompilationUnit unit, Location location) { | ||
| // Make sure that the ICompilationUnit is conistant | ||
| try { | ||
| unit.makeConsistent(null); | ||
|
|
@@ -206,6 +206,14 @@ default boolean queryQualificationMatches(String query, ICompilationUnit unit, L | |
| // for a query, java.io.paths.File*, queryQualification is java.io.paths | ||
| queryQualification = query.substring(0, dotIndex); | ||
| } | ||
| // an element need not be imported if its referenced by fqn | ||
| if (!queryQualification.isEmpty() && ( | ||
| matchedElement.getElementName().equals(queryQualification) | ||
| || matchedElement.getElementName().startsWith(queryQualification + ".") | ||
| || queryQualification.matches(matchedElement.getElementName()) | ||
| )) { | ||
| return true; | ||
| } | ||
|
Comment on lines
+209
to
+216
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Guard against null Two issues:
Apply this diff: // an element need not be imported if its referenced by fqn
- if (!queryQualification.isEmpty() && (
- matchedElement.getElementName().equals(queryQualification)
- || matchedElement.getElementName().startsWith(queryQualification + ".")
- || queryQualification.matches(matchedElement.getElementName())
- )) {
+ if (matchedElement != null && !queryQualification.isEmpty() && (
+ matchedElement.getElementName().equals(queryQualification)
+ || matchedElement.getElementName().startsWith(queryQualification + ".")
+ || matchedElement.getElementName().matches(queryQualification)
+ )) {
return true;
}🤖 Prompt for AI Agents |
||
| String packageQueryQualification = ""; | ||
| int packageDotIndex = queryQualification.lastIndexOf('.'); | ||
| if (packageDotIndex > 0) { | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.