Introduce LexicographicalPermitsListing check#2017
Introduce LexicographicalPermitsListing check#2017mohamedsamehsalah wants to merge 11 commits intomasterfrom
LexicographicalPermitsListing check#2017Conversation
|
Suggested commit message: |
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
1 similar comment
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
|
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
15653b8 to
942753a
Compare
942753a to
fb46fbe
Compare
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
1 similar comment
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
mohamedsamehsalah
left a comment
There was a problem hiding this comment.
@rickie, should be ready for review now 🙏
|
|
||
| @Override | ||
| public Description matchClass(ClassTree tree, VisitorState state) { | ||
| if (!IS_INTERFACE.matches(tree, state)) { |
There was a problem hiding this comment.
@rickie I thought this would be addressed by adding the class G in the unit tests, but I am probably missing something 🤔
There was a problem hiding this comment.
Fixed it in my previous commit :)
I think we can actually also support classes, right?
| .replace(original, SourceCode.treeToString(replacement, state))) | ||
| .reduce(SuggestedFix.Builder::merge) | ||
| .map(SuggestedFix.Builder::build) | ||
| .orElseThrow(() -> new VerifyException("No clauses were provided")); |
There was a problem hiding this comment.
Should this be an IllegalStateException? After all the compiler will complain if no clauses were provided.
|
Looks good. All 13 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
37489d8 to
b4c75c3
Compare
|
Looks good. All 13 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
📝 WalkthroughWalkthroughIntroduces a new BugChecker that validates lexicographic ordering of permitted non-sealed interfaces in Java interface declarations. When permits are unordered, the checker reports a diagnostic and provides an automated fix to reorder them lexicographically. Includes comprehensive test coverage for diagnostic detection and source refactoring scenarios. Changes
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes 🚥 Pre-merge checks | ✅ 1 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (1 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In
`@error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalSealedInterfacePermitsListing.java`:
- Around line 26-31: Update the class Javadoc for
LexicographicalSealedInterfacePermitsListing to accurately describe its scope:
state that the checker verifies the lexicographic ordering of the entire sealed
interface "permits" list (not only "permitted non-sealed interfaces") and that
it flags out-of-order entries in that full permits list; keep the existing
explanation about why sorted ordering helps conflict resolution but change the
wording to reference the entire permits list rather than only non-sealed
permitted interfaces.
| /** | ||
| * A {@link BugChecker} that flags permitted non-sealed interfaces that are not lexicographically | ||
| * sorted. | ||
| * | ||
| * <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict | ||
| * resolution, and can even avoid it if two branches add the same annotation. |
There was a problem hiding this comment.
Clarify the checker’s scope in Javadoc.
The implementation sorts the entire permits list for sealed interfaces, not just “permitted non-sealed interfaces.” Tighten the wording to avoid misleading readers.
✏️ Suggested wording update
- * A {`@link` BugChecker} that flags permitted non-sealed interfaces that are not lexicographically
- * sorted.
+ * A {`@link` BugChecker} that flags sealed interfaces whose {`@code` permits} clause is not
+ * lexicographically sorted.🤖 Prompt for AI Agents
In
`@error-prone-contrib/src/main/java/tech/picnic/errorprone/bugpatterns/LexicographicalSealedInterfacePermitsListing.java`
around lines 26 - 31, Update the class Javadoc for
LexicographicalSealedInterfacePermitsListing to accurately describe its scope:
state that the checker verifies the lexicographic ordering of the entire sealed
interface "permits" list (not only "permitted non-sealed interfaces") and that
it flags out-of-order entries in that full permits list; keep the existing
explanation about why sorted ordering helps conflict resolution but change the
wording to reference the entire permits list rather than only non-sealed
permitted interfaces.
rickie
left a comment
There was a problem hiding this comment.
Didn't write all comments on the PR as I was on the
I added support for also sorting classes. Now we need to rename the check 🤔.
|
|
||
| @Override | ||
| public Description matchClass(ClassTree tree, VisitorState state) { | ||
| if (!IS_INTERFACE.matches(tree, state)) { |
There was a problem hiding this comment.
Fixed it in my previous commit :)
I think we can actually also support classes, right?
| final class LexicographicalSealedInterfacePermitsListingTest { | ||
| @Test | ||
| void identificationIgnoresClasses() { | ||
| CompilationTestHelper.newInstance( |
There was a problem hiding this comment.
I'll add this to the identification test :)
| CompilationTestHelper.newInstance( | ||
| LexicographicalSealedInterfacePermitsListing.class, getClass()) | ||
| .addSourceLines( | ||
| "pkg/A.java", |
There was a problem hiding this comment.
| "pkg/A.java", | |
| "A.java", |
We can omit this :)
| * sorted. | ||
| * | ||
| * <p>The idea behind this checker is that maintaining a sorted sequence simplifies conflict | ||
| * resolution, and can even avoid it if two branches add the same annotation. |
There was a problem hiding this comment.
Let's drop the last (copied 😉) sentence.
|
Looks good. All 10 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
LexicographicalSealedInterfacePermitsListing checkLexicographicalSealedPermitsListing check
|
Looks good. All 10 mutations in this change were killed.
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
rickie
left a comment
There was a problem hiding this comment.
Nice one @mohamedsamehsalah 🚀
Thanks for the beautiful PR :)
a44fe61 to
d2d621d
Compare
Stephan202
left a comment
There was a problem hiding this comment.
I rebased and added a commit. Suggested commit message:
Introduce `LexicographicalPermitsListing` check (#2017)
As this new check is structurally similar to
`LexicographicalAnnotationListing`, the shared logic is factored out and
exposed through `SourceCode#sortTrees`.
While there, optimize both checks for the common case in which no
violation is found, by reducing the number of allocations for compliant
code.
|
|
||
| @Override | ||
| public Description matchClass(ClassTree tree, VisitorState state) { | ||
| List<? extends Tree> originalOrderPermitClauses = tree.getPermitsClause(); |
There was a problem hiding this comment.
We can make this a bit shorter:
| List<? extends Tree> originalOrderPermitClauses = tree.getPermitsClause(); | |
| List<? extends Tree> originalClauses = tree.getPermitsClause(); |
| @Override | ||
| public Description matchClass(ClassTree tree, VisitorState state) { | ||
| List<? extends Tree> originalOrderPermitClauses = tree.getPermitsClause(); | ||
| ImmutableList<? extends Tree> sortedPermitClauses = sort(originalOrderPermitClauses, state); |
There was a problem hiding this comment.
Like in LexicographicalAnnotationListing we can skip the check if there are fewer than two clauses.
Edit actually, perhaps we should use Comparators.isInOrder. Quite some logic is duplicated now, so let's factor this out in a helper method.
| /** | ||
| * A {@link BugChecker} that flags permitted non-sealed interfaces and classes that are not | ||
| * lexicographically sorted. |
There was a problem hiding this comment.
The permitted clause lists permitted direct subtypes; whether those are non-sealed interfaces has nothing to do with it.
| linkType = CUSTOM, | ||
| severity = SUGGESTION, | ||
| tags = STYLE) | ||
| public final class LexicographicalSealedPermitsListing extends BugChecker |
There was a problem hiding this comment.
Let's just call it LexicographicalPermitsListing.
| " class G {}", | ||
| "}") | ||
| .addSourceLines( | ||
| "Foo.java", |
There was a problem hiding this comment.
We generally use one top-level class.
| "import pkg.A.C;", | ||
| "import pkg.A.D;", | ||
| "", | ||
| "// BUG: Diagnostic contains:", |
There was a problem hiding this comment.
Generally we list the non-violating cases first.
| "package pkg;", | ||
| "", | ||
| "import pkg.A.B;", | ||
| "import pkg.A.C;", | ||
| "import pkg.A.D;", |
There was a problem hiding this comment.
We can omit this package statement and the imports.
LexicographicalSealedPermitsListing checkLexicographicalPermitsListing check
|
Mutation testing report by Pitest. Review any surviving mutants by inspecting the line comments under Files changed. |
|
The two reported mutants are unkillable: both branches exist for performance reasons only. |
| public Description matchClass(ClassTree tree, VisitorState state) { | ||
| SuggestedFix fix = | ||
| SourceCode.sortTrees( | ||
| tree.getPermitsClause(), | ||
| comparing(annotation -> SourceCode.treeToString(annotation, state)), | ||
| state); | ||
| return fix.isEmpty() | ||
| ? Description.NO_MATCH | ||
| : describeMatch(tree.getPermitsClause().getFirst(), fix); | ||
| } |
There was a problem hiding this comment.
ClassTrees are way less common than ModifierTrees, so I omitted the if (tree.getPermitsClause().size() < 2) case here.
|
Thanks for review and suggestions @Stephan202 🙏 |



Suggested commit message