Skip to content

Conversation

@jmle
Copy link
Collaborator

@jmle jmle commented Oct 1, 2025

Fixes https://issues.redhat.com/browse/MTA-6195 (konveyor/analyzer-lsp#901)

API tests PR: 351

Summary by CodeRabbit

  • New Features

    • Accepts configurable query input including qualified (dotted) queries and applies qualification-aware matching to include annotation targets.
  • Bug Fixes

    • Analysis tolerates and logs parse/compile issues and handles both source and compiled contexts without failing.
  • Refactor

    • Qualification checks now consider the specific code element context for more accurate matching; pattern matching behavior refined.
  • Chores

    • Improved lifecycle cleanup of analysis resources and updated platform references.

@coderabbitai
Copy link

coderabbitai bot commented Oct 1, 2025

Walkthrough

Adds dot-qualified query support for annotations: AnnotationSymbolProvider now implements WithQuery with a settable query, resolves/parses source or class units for qualified queries, uses CustomASTVisitor (new ANNOTATION handling) to match annotations, and updates qualification checks across symbol providers and the SymbolProvider interface.

Changes

Cohort / File(s) Summary
Annotation symbol provider (query + AST parsing)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java
Implements WithQuery, adds private String query and public void setQuery(String), uses annotation.getPrimaryElement() for kind/location, adds dot-qualified query flow that resolves source/class working copies, configures ASTParser with bindings, invokes CustomASTVisitor (ANNOTATION) for matching, logs parser problems, and discards/closes working copies.
AST visitor: annotation matching
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java
Adds ANNOTATION to QueryLocation; implements visit(MarkerAnnotation), visit(NormalAnnotation), and visit(SingleMemberAnnotation) which route to shared annotation handling that resolves bindings, derives fully-qualified names (with erasure/fallback), sets symbolMatches on match, and logs fallbacks/errors.
Method/constructor qualification context changes
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/ConstructorCallSymbolProvider.java, java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/MethodCallSymbolProvider.java
Calls to queryQualificationMatches now include the matched element (mod or e) so qualification checks consider the element context before guarded AST parsing/visitor steps.
Qualification logic (interface)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java
Default method signature changed to queryQualificationMatches(String query, IJavaElement matchedElement, ICompilationUnit unit, Location location); adds an early-exit check using matchedElement name (equals / startsWith / regex), and changes parentheses removal to query.replaceAll("\\(.*\\)", "").
Query pattern selection tweak
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/SampleDelegateCommandHandler.java
getPatternSingleQuery full-match now requires both ? and * absent; on-demand import regex builds by replacing unescaped * with .*.
Platform target updates
java-analyzer-bundle.tp/java-analyzer-bundle.target
Downgraded referenced JDT.LS and LSP4J versions (JDT.LS -> 1.35.0, LSP4J -> 0.22.0) and added a clarifying comment about the chosen JDT.LS bits.

Sequence Diagram(s)

sequenceDiagram
  autonumber
  participant Caller
  participant ASP as AnnotationSymbolProvider
  participant Annotation
  participant Unit as Source/Class Unit
  participant Parser as ASTParser
  participant Visitor as CustomASTVisitor

  Caller->>ASP: setQuery(query)
  Caller->>ASP: requestSymbols(annotation)
  ASP->>Annotation: annotation.getPrimaryElement()
  ASP->>ASP: determine kind & location

  alt query contains dot (qualified)
    ASP->>Unit: resolve source or class-file working copy
    ASP->>Parser: configure with bindings
    Parser->>Visitor: parse & visit (ANNOTATION)
    Visitor-->>ASP: symbolMatches?
    note right of Parser #f9f5f0: parse problems logged (non-fatal)
    ASP->>Unit: discard/close working copy
    alt symbolMatches
      ASP-->>Caller: emit symbol
    else
      ASP-->>Caller: skip symbol
    end
  else (non-qualified)
    ASP->>ASP: compare annotationQuery or accept symbol
    alt matches
      ASP-->>Caller: emit symbol
    else
      ASP-->>Caller: skip symbol
    end
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Possibly related PRs

Suggested reviewers

  • eemcmullan
  • shawn-hurley
  • pranavgaikwad

Poem

I twitch my whiskers at a dotted clue,
I sniff the AST where annotations grew.
I visit nodes and match each name,
Close the copy — tidy the game.
A rabbit hops; the symbols came through. 🐰

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title Check ✅ Passed The pull request title "Fix annotation matching" is directly related to the primary changes in the changeset. The most significant modifications involve refactoring AnnotationSymbolProvider to extend its public interface with WithQuery support and adding comprehensive annotation handling to CustomASTVisitor with new visit methods for different annotation types. These core changes are explicitly focused on fixing annotation matching functionality to resolve the linked issues (MTA-6195 and analyzer-lsp#901). While the changeset also includes supporting modifications to qualification matching logic (SymbolProvider) and other providers, these are secondary changes that facilitate the main annotation matching fix. The title is specific, clear, and accurately summarizes the primary objective of the pull request.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a072cb0 and a712b23.

📒 Files selected for processing (1)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)
  • CustomASTVisitor (22-192)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer
🔇 Additional comments (4)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (4)

9-9: LGTM!

The imports and interface additions appropriately support the new qualified query handling and AST parsing functionality.

Also applies to: 14-20, 26-26, 29-29, 32-32


38-46: LGTM!

Storing the annotatable reference and setting the location earlier are appropriate changes that support the subsequent qualified query logic.


58-77: LGTM with note on problem handling.

The AST parser setup and visitor usage are correct. The code logs compilation problems but continues processing, which is appropriate as indicated by the comment at line 63 that bindings may return null under tests with problems.


124-127: LGTM!

The setQuery method correctly implements the WithQuery interface requirement.

@eemcmullan
Copy link
Contributor

Looks like the CI failures are legitimate? The missing tags are from https://github.com/konveyor/rulesets/blob/main/default/generated/technology-usage/34-database-technology-usage.windup.yaml#L63. After a quick look at tackle-testapp, looks like these tags should be present.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)

49-94: Guard working-copy lifecycle before cleanup.

When no working copy is created, unit can be null or a primary CU. Unconditionally calling discardWorkingCopy()/close() throws (or NPEs). Track whether a working copy was opened and only discard/close in that case, skipping cleanup when unit stayed null or primary.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between a712b23 and 0690648.

📒 Files selected for processing (2)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (3 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)
  • CustomASTVisitor (25-274)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0690648 and 8f41b4a.

📒 Files selected for processing (1)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer
🔇 Additional comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)

94-94: FQN construction is correct.

The past review concern about duplicated annotation names (e.g., com.example.MyAnnotation.MyAnnotation) has been properly addressed. The code now uses only declaringClass.getQualifiedName() without appending binding.getName().

this.symbolMatches = true;
return false;
} else {
logInfo("method fqn " + fullyQualifiedName + " did not match with " + query);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Fix copy-paste errors in log messages.

The log messages reference "method" and "MethodInvocation" but should reference "annotation" since this is the annotation visitor:

  • Line 100: "method fqn" → "annotation fqn"
  • Line 105: "MethodInvocation" → "Annotation"
  • Line 111: "MethodInvocation" → "Annotation"

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
In
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java
around lines 100, 105 and 111, fix copy-paste errors in the log messages that
incorrectly reference "method" and "MethodInvocation": change the message at
line 100 from "method fqn" to "annotation fqn", and replace occurrences of
"MethodInvocation" at lines 105 and 111 with "Annotation" so the logs correctly
refer to annotations in this visitor.

@dymurray dymurray added the cherry-pick/release-0.8 This PR should be cherry-picked to release-0.8 branch label Oct 3, 2025
classes.add(ResolvedSourceMethod.class);
classes.add(ResolvedSourceField.class);
classes.add(ResolvedSourceType.class);
if (matchesAnnotationQuery(match, classes)) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

if (binding != null) {
// get fqn
ITypeBinding declaringClass = binding.getAnnotationType();
if (declaringClass != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

@jmle jmle closed this Oct 8, 2025
@jmle jmle reopened this Oct 8, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)

111-126: Fix copy‑paste log messages in annotation visitor.

Messages should reference “annotation/Annotation”, not “method/MethodInvocation”.

-                    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);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 9fd2bcb and cb5a3ba.

📒 Files selected for processing (1)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (3 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer
🔇 Additional comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)

71-85: Good consolidation via shared Annotation path.

Routing Marker/Normal/SingleMemberAnnotation to a single handler reduces duplication. LGTM.

@jmle jmle closed this Oct 8, 2025
@jmle jmle reopened this Oct 8, 2025
@jmle jmle force-pushed the fix-annotation-matching branch from cb5a3ba to 90ea336 Compare October 9, 2025 18:13
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)

115-127: Fix annotation log messages.

Log lines still say “method fqn” / “MethodInvocation”, which is misleading in the annotation visitor. Please rename them to “annotation fqn” / “Annotation” so diagnostics stay accurate.

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);
📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between cb5a3ba and 90ea336.

📒 Files selected for processing (3)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/SampleDelegateCommandHandler.java (1 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/SampleDelegateCommandHandler.java
🧰 Additional context used
🧬 Code graph analysis (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)
  • CustomASTVisitor (27-260)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer

Comment on lines 47 to 93
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, 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)) {
symbols.add(symbol);
}
} else {
symbols.add(symbol);
}
}
}
unit.discardWorkingCopy();
unit.close();
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard compilation unit resolution before parsing.

If the match comes from an element without source and class-file ancestors (e.g., binary with no attached source), unit stays null. We then call queryQualificationMatches(this.query, unit, …) and later unit.discardWorkingCopy()/close(), which blows up with a NullPointerException. Even when unit is a primary compilation unit, unconditionally discarding the working copy is illegal. Please short-circuit when unit is null and only dispose when we actually created a working copy. Also guard this.query before invoking .contains.

Apply this diff:

-                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, unit, location)) {
+                if (this.query != null && this.query.contains(".")) {
+                    ICompilationUnit unit = (ICompilationUnit) annotationElement.getAncestor(IJavaElement.COMPILATION_UNIT);
+                    boolean isWorkingCopy = false;
+                    if (unit == null) {
+                        IClassFile cls = (IClassFile) annotationElement.getAncestor(IJavaElement.CLASS_FILE);
+                        if (cls != null) {
+                            unit = cls.getWorkingCopy(new WorkingCopyOwnerImpl(), null);
+                            isWorkingCopy = true;
+                        }
+                    }
+                    if (unit != null && this.queryQualificationMatches(this.query, 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)) {
                                     symbols.add(symbol);
                                 }
                             } else {
                                 symbols.add(symbol);
                             }
                         }
                     }
-                    unit.discardWorkingCopy();
-                    unit.close();
+                    if (unit != null) {
+                        if (isWorkingCopy) {
+                            unit.discardWorkingCopy();
+                        }
+                        unit.close();
+                    }
                 } else {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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, 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)) {
symbols.add(symbol);
}
} else {
symbols.add(symbol);
}
}
}
unit.discardWorkingCopy();
unit.close();
if (this.query != null && this.query.contains(".")) {
// First try to get compilation unit for source files
ICompilationUnit unit = (ICompilationUnit) annotationElement.getAncestor(IJavaElement.COMPILATION_UNIT);
boolean isWorkingCopy = false;
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);
isWorkingCopy = true;
}
}
if (unit != null && this.queryQualificationMatches(this.query, 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)) {
symbols.add(symbol);
}
} else {
symbols.add(symbol);
}
}
}
if (unit != null) {
if (isWorkingCopy) {
unit.discardWorkingCopy();
}
unit.close();
}
} else {
// ...rest of the else-branch...
🤖 Prompt for AI Agents
In
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java
around lines 47 to 93, guard against nulls and improper disposal: first check
that this.query is non-null before calling this.query.contains(...); after
trying to obtain a compilation unit from the element, short-circuit and skip
parsing/visiting if unit is null (so you don't call queryQualificationMatches or
create an AST on a null unit); track whether you created a working copy (only
call discardWorkingCopy() and close() when a working copy was actually created),
and avoid unconditionally discarding/closing primary compilation units to
prevent illegal operations. Ensure all null checks occur before any use of unit
so no NPEs occur.

@aufi aufi closed this Oct 10, 2025
@aufi aufi reopened this Oct 10, 2025
@aufi aufi closed this Oct 10, 2025
@aufi aufi reopened this Oct 10, 2025
@aufi aufi closed this Oct 10, 2025
@aufi aufi reopened this Oct 10, 2025
@aufi aufi closed this Oct 10, 2025
@aufi aufi reopened this Oct 10, 2025
@aufi aufi closed this Oct 10, 2025
@aufi aufi reopened this Oct 10, 2025
@aufi aufi closed this Oct 10, 2025
@aufi aufi reopened this Oct 10, 2025
jmle and others added 5 commits October 20, 2025 13:24
Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
…rt and queries like (A|B|C)

Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>
Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>
@jmle jmle force-pushed the fix-annotation-matching branch from 90ea336 to 3c2c2d1 Compare October 20, 2025 15:05
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (4)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2)

79-89: Extract duplicate annotation query matching logic.

The annotation query matching logic (checking annotationQuery null, creating the classes list, calling matchesAnnotationQuery, and conditionally adding symbols) is duplicated in both the qualified (lines 79-89) and non-qualified (lines 95-105) branches.

Apply this diff to extract the duplicate logic:

+   private void addSymbolIfMatches(List<SymbolInformation> symbols, SymbolInformation symbol, SearchMatch 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);
+           }
+       } else {
+           symbols.add(symbol);
+       }
+   }

    // In the qualified branch:
    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)) {
-               symbols.add(symbol);
-           }
-       } else {
-           symbols.add(symbol);
-       }
+       addSymbolIfMatches(symbols, symbol, match);
    }

    // In the non-qualified branch:
-   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);
-   }
+   addSymbolIfMatches(symbols, symbol, match);

Based on learnings


47-93: Critical: Address working copy lifecycle bug and NPE risks.

This code has three critical issues previously flagged:

  1. NPE risk: Line 47 calls this.query.contains(".") without null check; line 57 may pass null unit to queryQualificationMatches.
  2. Working copy lifecycle bug: Lines 92-93 unconditionally call discardWorkingCopy() and close() on unit, but this is only valid when a working copy was created at line 54. When unit comes from line 49, these calls are incorrect.
  3. Missing guard: If unit is null after line 56, line 57 will cause issues.

Apply this diff:

-            if (this.query.contains(".")) {
+            if (this.query != null && this.query.contains(".")) {
                 // First try to get compilation unit for source files
                 ICompilationUnit unit = (ICompilationUnit) annotationElement.getAncestor(IJavaElement.COMPILATION_UNIT);
+                boolean isWorkingCopy = false;
                 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);
+                        isWorkingCopy = true;
                     }
                 }
-                if (this.queryQualificationMatches(this.query, annotationElement, unit, location)) {
+                if (unit != null && this.queryQualificationMatches(this.query, 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)) {
                                 symbols.add(symbol);
                             }
                         } else {
                             symbols.add(symbol);
                         }
                     }
+                    if (isWorkingCopy) {
+                        unit.discardWorkingCopy();
+                        unit.close();
+                    }
                 }
-                unit.discardWorkingCopy();
-                unit.close();
             } else {

Based on learnings

java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (2)

111-111: Fix copy-paste errors in log messages.

The log messages at lines 111, 116, and 122 incorrectly 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);

Based on learnings


86-94: Add proper annotation filtering to prevent false positives in annotation matching.

The code currently lacks position-based filtering for annotation nodes. Unlike MethodInvocation and ConstructorInvocation which use shouldVisit() for position-based verification, the visit(Annotation) method only checks location type without verifying the annotation node is at the actual search match position. This causes all annotations of a matching type to be flagged as matches, not just the one at the search offset.

Add the helper method and apply the check as suggested:

+    private boolean shouldVisitAnnotation(Annotation node) {
+        org.eclipse.jdt.core.dom.Name name = node.getTypeName();
+        if (name == null) {
+            return false;
+        }
+        int start = name.getStartPosition();
+        int end = start + name.getLength();
+        int m = this.match.getOffset();
+        return m >= start && m < end;
+    }
+
     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;
         }
+        if (!shouldVisitAnnotation(node)) {
+            return true;
+        }
         try {
🧹 Nitpick comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/SampleDelegateCommandHandler.java (1)

379-380: Good improvement to wildcard-to-regex conversion.

The negative lookbehind correctly handles cases where the query already contains .*, preventing double conversion. This is a clear improvement over using the raw query as a regex pattern.

For additional robustness, consider escaping other regex metacharacters in the query before replacing wildcards:

-// when creating the regex, replace * with .*
-Pattern regex = Pattern.compile(query.replaceAll("(?<!\\.)\\*", ".*"));
+// when creating the regex, escape special chars then replace * with .*
+String escaped = Pattern.quote(query).replace("\\*", "\\E.*\\Q");
+Pattern regex = Pattern.compile(escaped.replaceAll("^\\\\Q|\\\\E$", ""));

This would prevent characters like . from being interpreted as regex metacharacters, though the current implementation works fine for well-formed package names.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 90ea336 and 3c2c2d1.

📒 Files selected for processing (7)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/SampleDelegateCommandHandler.java (2 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/ConstructorCallSymbolProvider.java (1 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (3 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/MethodCallSymbolProvider.java (1 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java (2 hunks)
  • java-analyzer-bundle.tp/java-analyzer-bundle.target (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)
  • CustomASTVisitor (27-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer
🔇 Additional comments (6)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/SampleDelegateCommandHandler.java (1)

165-165: Correct fix for wildcard detection logic.

The change from OR to AND is correct. The previous logic would incorrectly trigger full-match mode if a query contained only one wildcard type (e.g., foo* would match because it doesn't contain ?). The new logic correctly uses pattern matching whenever any wildcard (? or *) is present.

java-analyzer-bundle.tp/java-analyzer-bundle.target (2)

67-67: LSP4J 0.22.0 downgrade is required by JDT.LS 1.35.0.

JDT.LS 1.35.0 specifies LSP4J 0.22.0 in its target platform configuration, making this downgrade necessary for dependency compatibility rather than optional. The change is correct and justified.


5-10: Verify the justification for both dependency downgrades: JDT.LS (1.40.0 → 1.35.0) and LSP4J (0.23.1 → 0.22.0).

The downgrades lack documented justification in the PR. LSP4J 0.23.1 includes breaking API changes, which could explain the LSP4J downgrade, but no PR comments clarify this. JDT.LS 1.40.0 added Java 23 support and other features without documented regressions.

Confirm:

  • Are these downgrades required to fix the annotation matching issues?
  • Do the PR CI failures relate to these versions, or are they separate concerns?
  • Have specific compatibility issues been identified that prevent using the newer versions?

Without this context, these changes appear to work around issues rather than fix root causes, potentially masking underlying bugs in annotation matching logic.

java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/ConstructorCallSymbolProvider.java (1)

56-56: LGTM!

The call to queryQualificationMatches correctly passes the mod element as the second argument, aligning with the updated signature in SymbolProvider.

java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/MethodCallSymbolProvider.java (1)

56-56: LGTM!

The call to queryQualificationMatches correctly passes the e element as the second argument, aligning with the updated signature in SymbolProvider.

java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java (1)

201-201: The regex change is correct and intentional.

The codebase contains explicit OR pattern handling in SampleDelegateCommandHandler.java (lines 81-94) that detects and processes queries with pipes in parentheses, such as java.io.((FileWriter|FileReader|PrintStream|File|PrintWriter|RandomAccessFile))*. The regex change from \\(.*\\) to \\([^|]*\\) intentionally preserves these pipe-containing parentheses so they can be parsed by the OR pattern logic, while still removing simple parameter-style parentheses like (String) or (*). This is the correct behavior.

Comment on lines +209 to +216
// 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;
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

Guard against null matchedElement and fix backwards regex match.

Two issues:

  1. matchedElement could be null (no prior null check), causing NPEs at lines 211-213.
  2. Line 213 has the match backwards: queryQualification.matches(matchedElement.getElementName()) treats the query as a string and the element name as a regex pattern, which is the opposite of the intended logic.

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
In
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java
around lines 209 to 216, guard against a null matchedElement and correct the
regex match direction: first check matchedElement != null before accessing
getElementName(), and replace
queryQualification.matches(matchedElement.getElementName()) with
matchedElement.getElementName().matches(queryQualification) (or otherwise
null-safe call) so the element name is matched against the query pattern; keep
the existing equality/startsWith checks but ensure all getElementName()
invocations occur only after the null check.

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java (2)

224-226: String comparisons use != "" and may NPE on location.

Use isEmpty() and guard location.

-        if (queryQualification != "" && location.getUri().contains(queryQualification.replaceAll("\\.", "/"))) {
+        if (!queryQualification.isEmpty() && location != null
+                && location.getUri().contains(queryQualification.replaceAll("\\.", "/"))) {
             return true;
         }
...
-                    if (packageQueryQualification!= "" && packageDecl.getElementName().matches(packageQueryQualification)) {
+                    if (!packageQueryQualification.isEmpty()
+                            && packageDecl.getElementName().matches(packageQueryQualification)) {
                         return true;
                     }

Also applies to: 231-233


243-247: Reversed/duplicated regex checks on imports.

Only the import string should be matched against the query pattern.

-                    // import can be absolute like java.io.paths.FileReader
-                    if (query.matches(importElement)) {
-                        return true;
-                    }
-                    if (importElement.matches(query)) {
-                        return true;
-                    }
+                    // import can be absolute like java.io.paths.FileReader
+                    if (importElement.matches(query)) {
+                        return true;
+                    }
♻️ Duplicate comments (2)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java (1)

209-216: Null-guard matchedElement and fix reversed regex.

Accessing matchedElement without a null-check can NPE. Also the regex is backwards; the element name should match the pattern.

Apply:

-        // 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;
-        }
+        // an element need not be imported if it is referenced by FQN
+        if (matchedElement != null && !queryQualification.isEmpty()) {
+            String name = matchedElement.getElementName();
+            if (name.equals(queryQualification)
+                    || name.startsWith(queryQualification + ".")
+                    || name.matches(queryQualification)) {
+                return true;
+            }
+        }
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)

47-56: Critical: guard query/unit and fix working‑copy lifecycle; also sanitize query for AST.

NPE risks on this.query.contains, unit usage, and unconditional discardWorkingCopy()/close(). Pass a sanitized query to the AST visitor so annotation FQNs (no params) match.

-                if (this.query.contains(".")) {
+                if (this.query != null && this.query.contains(".")) {
                     // First try to get compilation unit for source files
-                    ICompilationUnit unit = (ICompilationUnit) annotationElement.getAncestor(IJavaElement.COMPILATION_UNIT);
+                    ICompilationUnit unit = (ICompilationUnit) annotationElement.getAncestor(IJavaElement.COMPILATION_UNIT);
+                    boolean isWorkingCopy = false;
                     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);
+                            unit = cls.getWorkingCopy(new WorkingCopyOwnerImpl(), null);
+                            isWorkingCopy = true;
                         }
                     }
-                    if (this.queryQualificationMatches(this.query.replaceAll("\\(([A-Za-z_][A-Za-z0-9_]*(\\|[A-Za-z_][A-Za-z0-9_]*)*)\\)", ".*"), annotationElement, unit, location)) {
+                    String sanitizedQuery = this.query.replaceAll("\\(([A-Za-z_][A-Za-z0-9_]*(\\|[A-Za-z_][A-Za-z0-9_]*)*)\\)", ".*");
+                    if (unit != null && this.queryQualificationMatches(sanitizedQuery, 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);
+                        CustomASTVisitor visitor = new CustomASTVisitor(sanitizedQuery, match, QueryLocation.ANNOTATION);
                         // Under tests, resolveConstructorBinding will return null if there are problems
-                    unit.discardWorkingCopy();
-                    unit.close();
+                    if (unit != null && isWorkingCopy) {
+                        unit.discardWorkingCopy();
+                        unit.close();
+                    }

Also applies to: 57-63, 92-93

🧹 Nitpick comments (2)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java (1)

192-197: Avoid throwing/catching NPE for null unit.

Guard unit before calling makeConsistent.

-        try {
-            unit.makeConsistent(null);
+        try {
+            if (unit != null) {
+                unit.makeConsistent(null);
+            }
         } catch(Exception e) {
-            logInfo("unable to make unit consistant, will still try as could be class file in a jar" + e);
+            logInfo("unable to make unit consistent, proceeding: " + e);
         }
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)

79-89: DRY the duplicated annotationQuery filter into a helper.

Extract once and call in both branches.

+    private void addSymbolIfMatches(List<SymbolInformation> symbols, SymbolInformation symbol, SearchMatch 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);
+            }
+        } else {
+            symbols.add(symbol);
+        }
+    }
@@
-                        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)) {
-                                    symbols.add(symbol);
-                                }
-                            } else {
-                                symbols.add(symbol);
-                            }
-                        }
+                        if (visitor.symbolMatches()) {
+                            addSymbolIfMatches(symbols, symbol, 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);
-                        }
-                    } else {
-                        symbols.add(symbol);
-                    }
+                    addSymbolIfMatches(symbols, symbol, match);

Also applies to: 95-105

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 3c2c2d1 and d1b35c4.

📒 Files selected for processing (2)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2 hunks)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/SymbolProvider.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)
  • CustomASTVisitor (27-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

♻️ Duplicate comments (2)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2)

47-47: Add null check for query before calling .contains().

If this.query is null when this line executes, a NullPointerException will be thrown. Add a null guard before checking if it contains a dot.

Apply this diff:

-                if (this.query.contains(".")) {
+                if (this.query != null && this.query.contains(".")) {

57-61: Add null check for unit before qualification matching and AST parsing.

If annotationElement has neither a COMPILATION_UNIT nor a CLASS_FILE ancestor (or if getWorkingCopy returns null), unit will be null. Proceeding to call queryQualificationMatches and subsequently creating an ASTParser with a null source will cause a NullPointerException.

Apply this diff:

                    }
-                   if (this.queryQualificationMatches(this.query.replaceAll("\\(([A-Za-z_][A-Za-z0-9_]*(\\|[A-Za-z_][A-Za-z0-9_]*)*)\\)", ".*"), annotationElement, unit, location)) {
+                   if (unit != null && 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());
🧹 Nitpick comments (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)

79-89: Extract duplicate annotation query matching logic.

The annotation query matching block (checking annotationQuery != null, building the class list with ResolvedSourceMethod, ResolvedSourceField, and ResolvedSourceType, and calling matchesAnnotationQuery) is duplicated in both the qualified (lines 79-89) and non-qualified (lines 97-107) branches.

Note: This issue was previously raised in past review comments but remains unaddressed.

Apply this diff to extract the common logic:

+   private boolean shouldAddSymbol(SearchMatch match) {
+       if (annotationQuery != null) {
+           List<Class<? extends SourceRefElement>> classes = new ArrayList<>();
+           classes.add(ResolvedSourceMethod.class);
+           classes.add(ResolvedSourceField.class);
+           classes.add(ResolvedSourceType.class);
+           return matchesAnnotationQuery(match, classes);
+       }
+       return true;
+   }
+
    @Override
    public List<SymbolInformation> get(SearchMatch match) throws CoreException {
        List<SymbolInformation> symbols = new ArrayList<>();
        try {
            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(annotationElement));
                symbol.setContainerName(annotation.getParent().getElementName());
                Location location = getLocation(annotationElement, match);
                symbol.setLocation(location);
                if (this.query.contains(".")) {
                    // ... [compilation unit resolution and AST parsing]
                        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)) {
-                                   symbols.add(symbol);
-                               }
-                           } else {
+                           if (shouldAddSymbol(match)) {
                                symbols.add(symbol);
                            }
                        }
                    }
                    if (unit != null && unit.isWorkingCopy())  {
                        unit.discardWorkingCopy();
                        unit.close();
                    }
                } else {
-                   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 {
+                   if (shouldAddSymbol(match)) {
                        symbols.add(symbol);
                    }
                }
            }

Also applies to: 97-107

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d1b35c4 and daf8d09.

📒 Files selected for processing (1)
  • java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2 hunks)
🧰 Additional context used
🧬 Code graph analysis (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (1)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/CustomASTVisitor.java (1)
  • CustomASTVisitor (27-256)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Build tackle2-addon-analyzer
🔇 Additional comments (2)
java-analyzer-bundle.core/src/main/java/io/konveyor/tackle/core/internal/symbol/AnnotationSymbolProvider.java (2)

29-32: LGTM! Query interface implementation is correct.

The addition of the WithQuery interface, the query field, and the setQuery method properly enables query-based filtering for annotations.

Also applies to: 126-129


92-95: Good improvement on working copy lifecycle management.

The addition of unit.isWorkingCopy() check properly ensures that only working copies created via getWorkingCopy() (line 54) are discarded, while primary compilation units obtained directly from ancestors (line 49) are not. This resolves the lifecycle issue raised in previous reviews.

@jmle jmle merged commit 4ca22e2 into konveyor:main Oct 21, 2025
8 checks passed
github-actions bot pushed a commit that referenced this pull request Oct 21, 2025
* tmp

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Working:

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Move in check for annotated classes

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* tmp

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix stuff

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Final fix

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix shouldVisit

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix condition

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Undo

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* make sure that qualification matches considers fqn usage without import and queries like (A|B|C)

Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>

* fix a minor bug in processing regex

Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>

* Fix regex

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix potential npe

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

---------

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>
Co-authored-by: Pranav Gaikwad <pgaikwad@redhat.com>
Signed-off-by: Cherry Picker <noreply@github.com>
jmle added a commit that referenced this pull request Oct 22, 2025
* 🐛 Fix annotation matching (#149)

* tmp

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Working:

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Move in check for annotated classes

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* tmp

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix stuff

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Final fix

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix shouldVisit

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix condition

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Undo

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* make sure that qualification matches considers fqn usage without import and queries like (A|B|C)

Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>

* fix a minor bug in processing regex

Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>

* Fix regex

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Fix potential npe

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

---------

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>
Co-authored-by: Pranav Gaikwad <pgaikwad@redhat.com>
Signed-off-by: Cherry Picker <noreply@github.com>

* 🐛 Fixing annotation searching by getting the FQDN from the compliation. (#163)

* Fixing annotation searching by getting the FQDN from the compliation.

* Falls back to trying to use the code actions to get the FQDN

Signed-off-by: Shawn Hurley <shawn@hurley.page>

* Couple of fixes

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

* Respect annotated feature

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

---------

Signed-off-by: Shawn Hurley <shawn@hurley.page>
Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Co-authored-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>

---------

Signed-off-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Signed-off-by: Pranav Gaikwad <pgaikwad@redhat.com>
Signed-off-by: Cherry Picker <noreply@github.com>
Signed-off-by: Shawn Hurley <shawn@hurley.page>
Co-authored-by: Juan Manuel Leflet Estrada <jleflete@redhat.com>
Co-authored-by: Pranav Gaikwad <pgaikwad@redhat.com>
Co-authored-by: Shawn Hurley <shawn@hurley.page>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

cherry-pick/release-0.8 This PR should be cherry-picked to release-0.8 branch

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants