From 800d377e4c936444b32267ee3232ccd55462cb6c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 5 Aug 2026 11:17:33 +0200 Subject: [PATCH 1/2] Add a centralized list of assignment kinds to Tree.Kind --- .../org/sonar/plugins/java/api/tree/Tree.java | 23 +++++++++++++++++++ .../sonar/plugins/java/api/tree/TreeTest.java | 8 +++++++ 2 files changed, 31 insertions(+) diff --git a/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/Tree.java b/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/Tree.java index 199297047d1..a82959ba821 100644 --- a/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/Tree.java +++ b/java-frontend/src/main/java/org/sonar/plugins/java/api/tree/Tree.java @@ -795,6 +795,29 @@ enum Kind implements GrammarRuleKey { IMPLICIT_CLASS ); + /** + * The {@link Kind}s of all assignment expressions: the simple {@link Kind#ASSIGNMENT} and the eleven compound assignment + * operators. All of these are backed by {@link AssignmentExpressionTree}. + * + *

Use this as the single source of truth when a visitor or predicate must handle every assignment structure, instead of + * hand-listing the kinds. In particular, it can be returned directly from {@code nodesToVisit()} of a subscription visitor that + * needs to visit all assignments.

+ */ + public static final List ASSIGNMENT_KINDS = List.of( + ASSIGNMENT, + MULTIPLY_ASSIGNMENT, + DIVIDE_ASSIGNMENT, + REMAINDER_ASSIGNMENT, + PLUS_ASSIGNMENT, + MINUS_ASSIGNMENT, + LEFT_SHIFT_ASSIGNMENT, + RIGHT_SHIFT_ASSIGNMENT, + UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, + AND_ASSIGNMENT, + XOR_ASSIGNMENT, + OR_ASSIGNMENT + ); + final Class associatedInterface; Kind(Class associatedInterface) { diff --git a/java-frontend/src/test/java/org/sonar/plugins/java/api/tree/TreeTest.java b/java-frontend/src/test/java/org/sonar/plugins/java/api/tree/TreeTest.java index f52fdd80e0b..ed0808a7837 100644 --- a/java-frontend/src/test/java/org/sonar/plugins/java/api/tree/TreeTest.java +++ b/java-frontend/src/test/java/org/sonar/plugins/java/api/tree/TreeTest.java @@ -36,4 +36,12 @@ void class_kinds_contains_exactly_all_kinds_backed_by_class_tree() { assertThat(Tree.Kind.CLASS_KINDS).containsExactlyInAnyOrder(expected); } + @Test + void assignment_kinds_contains_exactly_all_kinds_backed_by_assignment_expression_tree() { + Tree.Kind[] expected = Arrays.stream(Tree.Kind.values()) + .filter(kind -> kind.getAssociatedInterface() == AssignmentExpressionTree.class) + .toArray(Tree.Kind[]::new); + assertThat(Tree.Kind.ASSIGNMENT_KINDS).containsExactlyInAnyOrder(expected); + } + } From bbf10525c9f7ba7ef075488972b022862e30ed36 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Aur=C3=A9lien=20Coet?= Date: Wed, 5 Aug 2026 11:21:05 +0200 Subject: [PATCH 2/2] Use the assignment kinds list in checks --- .../checks/StaticFieldUpateCheckSample.java | 1 + .../AssignmentInSubExpressionCheck.java | 16 +--------------- .../checks/ExpressionComplexityCheck.java | 14 +------------- .../java/checks/StaticFieldUpateCheck.java | 19 +++---------------- .../VolatileVariablesOperationsCheck.java | 19 ++++--------------- .../unused/UnusedPrivateFieldCheck.java | 19 ++----------------- 6 files changed, 12 insertions(+), 76 deletions(-) diff --git a/java-checks-test-sources/default/src/main/java/checks/StaticFieldUpateCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/StaticFieldUpateCheckSample.java index e3f9d571e6b..d91c7e788a6 100644 --- a/java-checks-test-sources/default/src/main/java/checks/StaticFieldUpateCheckSample.java +++ b/java-checks-test-sources/default/src/main/java/checks/StaticFieldUpateCheckSample.java @@ -13,6 +13,7 @@ public void nonCompliantAssignments() { staticValue = value + 1; // Noncompliant {{Make the enclosing method "static" or remove this set.}} // ^^^^^^^^^^^ staticValue += value; // Noncompliant {{Make the enclosing method "static" or remove this set.}} + staticValue >>= value; // Noncompliant {{Make the enclosing method "static" or remove this set.}} staticValue++; // Noncompliant {{Make the enclosing method "static" or remove this set.}} ++staticValue; // Noncompliant {{Make the enclosing method "static" or remove this set.}} StaticFieldUpateCheckSample.staticValue++; // Noncompliant {{Make the enclosing method "static" or remove this set.}} diff --git a/java-checks/src/main/java/org/sonar/java/checks/AssignmentInSubExpressionCheck.java b/java-checks/src/main/java/org/sonar/java/checks/AssignmentInSubExpressionCheck.java index df3790d9e4e..94049d79864 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/AssignmentInSubExpressionCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/AssignmentInSubExpressionCheck.java @@ -39,20 +39,6 @@ @Rule(key = "S1121") public class AssignmentInSubExpressionCheck extends BaseTreeVisitor implements JavaFileScanner { - private static final Kind[] ASSIGNMENT_EXPRESSIONS = new Kind[]{ - Kind.AND_ASSIGNMENT, - Kind.ASSIGNMENT, - Kind.DIVIDE_ASSIGNMENT, - Kind.LEFT_SHIFT_ASSIGNMENT, - Kind.RIGHT_SHIFT_ASSIGNMENT, - Kind.MINUS_ASSIGNMENT, - Kind.MULTIPLY_ASSIGNMENT, - Kind.OR_ASSIGNMENT, - Kind.PLUS_ASSIGNMENT, - Kind.REMAINDER_ASSIGNMENT, - Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, - Kind.XOR_ASSIGNMENT}; - private JavaFileScannerContext context; @Override @@ -71,7 +57,7 @@ public void visitAnnotation(AnnotationTree annotationTree) { @Override public void visitLambdaExpression(LambdaExpressionTree lambdaExpressionTree) { //skip lambda if body is an assignment - if(!lambdaExpressionTree.body().is(ASSIGNMENT_EXPRESSIONS)) { + if(!(lambdaExpressionTree.body() instanceof AssignmentExpressionTree)) { super.visitLambdaExpression(lambdaExpressionTree); } } diff --git a/java-checks/src/main/java/org/sonar/java/checks/ExpressionComplexityCheck.java b/java-checks/src/main/java/org/sonar/java/checks/ExpressionComplexityCheck.java index 7e7dd2795d6..4826a51b587 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/ExpressionComplexityCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/ExpressionComplexityCheck.java @@ -55,7 +55,7 @@ public void setContext(JavaFileScannerContext context) { @Override public List nodesToVisit() { - return ListUtils.concat(Tree.Kind.CLASS_KINDS, List.of( + return ListUtils.concat(Tree.Kind.CLASS_KINDS, Tree.Kind.ASSIGNMENT_KINDS, List.of( Tree.Kind.POSTFIX_INCREMENT, Tree.Kind.POSTFIX_DECREMENT, Tree.Kind.PREFIX_INCREMENT, @@ -92,18 +92,6 @@ public List nodesToVisit() { Tree.Kind.TYPE_CAST, Tree.Kind.INSTANCE_OF, Tree.Kind.PARENTHESIZED_EXPRESSION, - Tree.Kind.ASSIGNMENT, - Tree.Kind.MULTIPLY_ASSIGNMENT, - Tree.Kind.DIVIDE_ASSIGNMENT, - Tree.Kind.REMAINDER_ASSIGNMENT, - Tree.Kind.PLUS_ASSIGNMENT, - Tree.Kind.MINUS_ASSIGNMENT, - Tree.Kind.LEFT_SHIFT_ASSIGNMENT, - Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, - Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, - Tree.Kind.AND_ASSIGNMENT, - Tree.Kind.XOR_ASSIGNMENT, - Tree.Kind.OR_ASSIGNMENT, Tree.Kind.INT_LITERAL, Tree.Kind.LONG_LITERAL, Tree.Kind.FLOAT_LITERAL, diff --git a/java-checks/src/main/java/org/sonar/java/checks/StaticFieldUpateCheck.java b/java-checks/src/main/java/org/sonar/java/checks/StaticFieldUpateCheck.java index ea171599de0..bc63f291098 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/StaticFieldUpateCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/StaticFieldUpateCheck.java @@ -40,19 +40,6 @@ @Rule(key = "S2696") public class StaticFieldUpateCheck extends AbstractInSynchronizeChecker { - private static final Kind[] ASSIGNMENT_EXPRESSIONS = new Kind[]{ - Kind.AND_ASSIGNMENT, - Kind.ASSIGNMENT, - Kind.DIVIDE_ASSIGNMENT, - Kind.LEFT_SHIFT_ASSIGNMENT, - Kind.MINUS_ASSIGNMENT, - Kind.MULTIPLY_ASSIGNMENT, - Kind.OR_ASSIGNMENT, - Kind.PLUS_ASSIGNMENT, - Kind.REMAINDER_ASSIGNMENT, - Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, - Kind.XOR_ASSIGNMENT}; - private static final Kind[] UNARY_EXPRESSIONS = new Kind[]{ Kind.POSTFIX_DECREMENT, Kind.POSTFIX_INCREMENT, @@ -65,7 +52,7 @@ public class StaticFieldUpateCheck extends AbstractInSynchronizeChecker { public List nodesToVisit() { ArrayList nodesToVisit = new ArrayList<>(super.nodesToVisit()); nodesToVisit.add(Kind.STATIC_INITIALIZER); - nodesToVisit.addAll(Arrays.asList(ASSIGNMENT_EXPRESSIONS)); + nodesToVisit.addAll(Kind.ASSIGNMENT_KINDS); nodesToVisit.addAll(Arrays.asList(UNARY_EXPRESSIONS)); return nodesToVisit; } @@ -80,8 +67,8 @@ public void visitNode(Tree tree) { } else if (tree.is(Kind.STATIC_INITIALIZER)) { withinStaticMethod.push(true); } else if (isInInstanceMethod() && !hasAnyParentStatic() && !hasAnyParentSync()) { - if (tree.is(ASSIGNMENT_EXPRESSIONS)) { - checkVariableModification(((AssignmentExpressionTree) tree).variable()); + if (tree instanceof AssignmentExpressionTree assignment) { + checkVariableModification(assignment.variable()); } else if (tree.is(UNARY_EXPRESSIONS)) { checkVariableModification(((UnaryExpressionTree) tree).expression()); } diff --git a/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java b/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java index 76dd5e722ac..2693852b653 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/VolatileVariablesOperationsCheck.java @@ -37,6 +37,7 @@ import org.sonar.plugins.java.api.tree.Modifier; import org.sonar.plugins.java.api.tree.Tree; import org.sonar.plugins.java.api.tree.UnaryExpressionTree; +import org.sonarsource.analyzer.commons.collections.ListUtils; @Rule(key = "S3078") public class VolatileVariablesOperationsCheck extends IssuableSubscriptionVisitor { @@ -45,24 +46,12 @@ public class VolatileVariablesOperationsCheck extends IssuableSubscriptionVisito @Override public List nodesToVisit() { - return List.of( + return ListUtils.concat(Tree.Kind.ASSIGNMENT_KINDS, List.of( Tree.Kind.PREFIX_DECREMENT, Tree.Kind.PREFIX_INCREMENT, Tree.Kind.POSTFIX_DECREMENT, - Tree.Kind.POSTFIX_INCREMENT, - Tree.Kind.ASSIGNMENT, - Tree.Kind.PLUS_ASSIGNMENT, - Tree.Kind.MINUS_ASSIGNMENT, - Tree.Kind.MULTIPLY_ASSIGNMENT, - Tree.Kind.DIVIDE_ASSIGNMENT, - Tree.Kind.REMAINDER_ASSIGNMENT, - Tree.Kind.LEFT_SHIFT_ASSIGNMENT, - Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, - Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, - Tree.Kind.AND_ASSIGNMENT, - Tree.Kind.XOR_ASSIGNMENT, - Tree.Kind.OR_ASSIGNMENT - ); + Tree.Kind.POSTFIX_INCREMENT + )); } @Override diff --git a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java index 6b65de2a075..ef38a5bfb7f 100644 --- a/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java +++ b/java-checks/src/main/java/org/sonar/java/checks/unused/UnusedPrivateFieldCheck.java @@ -72,20 +72,6 @@ public class UnusedPrivateFieldCheck extends IssuableSubscriptionVisitor { "lombok.AllArgsConstructor" ); - private static final Tree.Kind[] ASSIGNMENT_KINDS = { - Tree.Kind.ASSIGNMENT, - Tree.Kind.MULTIPLY_ASSIGNMENT, - Tree.Kind.DIVIDE_ASSIGNMENT, - Tree.Kind.REMAINDER_ASSIGNMENT, - Tree.Kind.PLUS_ASSIGNMENT, - Tree.Kind.MINUS_ASSIGNMENT, - Tree.Kind.LEFT_SHIFT_ASSIGNMENT, - Tree.Kind.RIGHT_SHIFT_ASSIGNMENT, - Tree.Kind.UNSIGNED_RIGHT_SHIFT_ASSIGNMENT, - Tree.Kind.AND_ASSIGNMENT, - Tree.Kind.XOR_ASSIGNMENT, - Tree.Kind.OR_ASSIGNMENT}; - private final List classes = new ArrayList<>(); private final Map> assignments = new HashMap<>(); private final Set unknownIdentifiers = new HashSet<>(); @@ -248,9 +234,8 @@ private Set getIgnoredAnnotations() { } private void collectAssignment(ExpressionTree expressionTree) { - if (expressionTree.is(ASSIGNMENT_KINDS)) { - AssignmentExpressionTree assignmentExpressionTree = (AssignmentExpressionTree) expressionTree; - ExpressionTree variable = (assignmentExpressionTree).variable(); + if (expressionTree instanceof AssignmentExpressionTree assignmentExpressionTree) { + ExpressionTree variable = assignmentExpressionTree.variable(); IdentifierTree identifier = null; if (variable.is(Tree.Kind.IDENTIFIER)) { identifier = (IdentifierTree) variable;