Skip to content

Commit 60b0be9

Browse files
SONARJAVA-5931 Upgrade S5128 to support the Jakarta package (#5410)
1 parent 487577a commit 60b0be9

6 files changed

Lines changed: 102 additions & 28 deletions

File tree

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"ruleKey": "S1172",
33
"hasTruePositives": true,
4-
"falseNegatives": 31,
4+
"falseNegatives": 32,
55
"falsePositives": 0
6-
}
6+
}
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
package checks;
2+
3+
import jakarta.validation.Constraint;
4+
import jakarta.validation.ConstraintValidator;
5+
import jakarta.validation.ConstraintValidatorContext;
6+
import jakarta.validation.Valid;
7+
import java.util.List;
8+
9+
@Constraint(validatedBy = ConstraintChecker.class)
10+
@interface WellNamed {
11+
}
12+
13+
class ConstraintChecker implements ConstraintValidator<WellNamed, Occupant> {
14+
@Override
15+
public boolean isValid(Occupant occupant, ConstraintValidatorContext constraintValidatorContext) {
16+
return occupant.name != null;
17+
}
18+
}
19+
20+
@WellNamed
21+
class Occupant {
22+
public String name;
23+
}
24+
25+
class Floor {
26+
private Id id; // Noncompliant {{Add missing "@Valid" on "id" to validate it with "Bean Validation".}}
27+
// ^^
28+
29+
private List<Occupant> occupants; // Noncompliant {{Add missing "@Valid" on "occupants" to validate it with "Bean Validation".}}
30+
// ^^^^^^^^^^^^^^
31+
32+
@Valid
33+
private List<Occupant> validatedOccupants; // Compliant
34+
35+
// Preferred style as of Bean Validation 2.0
36+
private List<@Valid Occupant> validatedOccupants2; // Compliant
37+
38+
public void ignore(Occupant occupant) { // Noncompliant {{Add missing "@Valid" on "occupant" to validate it with "Bean Validation".}}
39+
// ^^^^^^^^
40+
}
41+
42+
public void validate(@Valid Occupant occupant) { // Compliant
43+
}
44+
}
45+
46+
class Id {
47+
@jakarta.validation.constraints.NotNull
48+
private String name;
49+
}

java-checks/src/test/files/checks/MissingBeanValidationCheck.java renamed to java-checks-test-sources/default/src/main/java/checks/MissingBeanValidationCheckJavaxSample.java

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
package checks;
2+
13
import java.util.List;
24
import javax.validation.Constraint;
35
import javax.validation.ConstraintValidator;
@@ -10,7 +12,7 @@ class User {
1012
private String name;
1113
}
1214

13-
@Constraint
15+
@Constraint(validatedBy = CheckMyConstraint.class)
1416
@interface MyConstraint {
1517
}
1618

@@ -19,12 +21,12 @@ class Department {
1921
private int hierarchyLevel;
2022
}
2123

22-
public class CheckMyConstraint implements ConstraintValidator<MyConstraint, Department>
23-
{
24+
class CheckMyConstraint implements ConstraintValidator<MyConstraint, Department> {
2425
public void initialize(MyConstraint constraintAnnotation) {
2526
}
2627

2728
public boolean isValid(Department bean, ConstraintValidatorContext context) { // Compliant - @Valid inside constraint validators would be nonsense
29+
return true;
2830
}
2931
}
3032

@@ -52,16 +54,13 @@ class CompliantGroup {
5254
private List<User> members; // Compliant
5355
}
5456

55-
class CompliantUserSelection {
56-
private List<@Valid User> contents; // Compliant; preferred syntax as of Java 8
57-
}
58-
5957
class NonCompliantService {
6058
public void login(User user) { // Noncompliant {{Add missing "@Valid" on "user" to validate it with "Bean Validation".}}
6159
}
6260

6361
public List<User> list(Department department) { // Noncompliant {{Add missing "@Valid" on "department" to validate it with "Bean Validation".}}
6462
// ^^^^^^^^^^
63+
return List.of();
6564
}
6665
}
6766

@@ -75,6 +74,7 @@ private void updateLastLoginDate(User user) { // Compliant - Bean Validation doe
7574
}
7675

7776
public List<User> list(@Valid Department department) { // Compliant
77+
return List.of();
7878
}
7979
}
8080

@@ -103,6 +103,6 @@ public void eat(Orange orange) { // Compliant
103103
}
104104

105105
class Building {
106-
static class Size<T> { }
106+
static class Size<T> {
107+
}
107108
}
108-

java-checks/src/main/java/org/sonar/java/checks/MissingBeanValidationCheck.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020
import java.util.Collections;
2121
import java.util.List;
2222
import java.util.Optional;
23+
import java.util.Set;
24+
import java.util.stream.Collectors;
2325
import java.util.stream.Stream;
2426
import org.sonar.check.Rule;
2527
import org.sonar.plugins.java.api.IssuableSubscriptionVisitor;
@@ -36,8 +38,9 @@
3638

3739
@Rule(key = "S5128")
3840
public class MissingBeanValidationCheck extends IssuableSubscriptionVisitor {
39-
private static final String JAVAX_VALIDATION_VALID = "javax.validation.Valid";
40-
private static final String JAVAX_VALIDATION_CONSTRAINT = "javax.validation.Constraint";
41+
private static final Set<String> JSR380_VALID_ANNOTATIONS = Set.of("javax.validation.Valid", "jakarta.validation.Valid");
42+
private static final Set<String> JSR380_CONSTRAINTS = Set.of("javax.validation.Constraint", "jakarta.validation.Constraint");
43+
private static final Set<String> JSR380_CONSTRAINT_VALIDATORS = Set.of("javax.validation.ConstraintValidator", "jakarta.validation.ConstraintValidator");
4144

4245
@Override
4346
public List<Tree.Kind> nodesToVisit() {
@@ -74,7 +77,7 @@ private static boolean isExcluded(MethodTree methodTree) {
7477

7578
private static boolean isInConstraintValidator(MethodTree methodTree) {
7679
Symbol.TypeSymbol enclosingClass = methodTree.symbol().enclosingClass();
77-
return enclosingClass != null && enclosingClass.type().isSubtypeOf("javax.validation.ConstraintValidator");
80+
return enclosingClass != null && JSR380_CONSTRAINT_VALIDATORS.stream().anyMatch(enclosingClass.type()::isSubtypeOf);
7881
}
7982

8083
private static Optional<String> getIssueMessage(VariableTree variable) {
@@ -85,14 +88,16 @@ private static Optional<String> getIssueMessage(VariableTree variable) {
8588
}
8689

8790
private static boolean validationEnabled(VariableTree variable) {
88-
if (variable.symbol().metadata().isAnnotatedWith(JAVAX_VALIDATION_VALID)) {
89-
return true;
90-
}
91-
return typeArgumentAnnotations(variable).anyMatch(annotation -> annotation.is(JAVAX_VALIDATION_VALID));
91+
return isAnnotatedWithAny(variable.symbol().metadata(), JSR380_VALID_ANNOTATIONS) ||
92+
!Collections.disjoint(typeArgumentAnnotations(variable), JSR380_VALID_ANNOTATIONS);
9293
}
9394

94-
private static Stream<Type> typeArgumentAnnotations(VariableTree variable) {
95-
return typeArgumentTypeTrees(variable).flatMap(type -> type.annotations().stream()).map(ExpressionTree::symbolType);
95+
private static Set<String> typeArgumentAnnotations(VariableTree variable) {
96+
return typeArgumentTypeTrees(variable)
97+
.flatMap(type -> type.annotations().stream())
98+
.map(ExpressionTree::symbolType)
99+
.map(Type::fullyQualifiedName)
100+
.collect(Collectors.toSet());
96101
}
97102

98103
private static Stream<TypeTree> typeArgumentTypeTrees(VariableTree variable) {
@@ -132,6 +137,10 @@ private static Stream<SymbolMetadata.AnnotationInstance> fieldAnnotationInstance
132137
}
133138

134139
private static boolean isConstraintAnnotation(SymbolMetadata.AnnotationInstance annotationInstance) {
135-
return annotationInstance.symbol().metadata().isAnnotatedWith(JAVAX_VALIDATION_CONSTRAINT);
140+
return isAnnotatedWithAny(annotationInstance.symbol().metadata(), JSR380_CONSTRAINTS);
141+
}
142+
143+
private static boolean isAnnotatedWithAny(SymbolMetadata metadata, Set<String> annotations) {
144+
return annotations.stream().anyMatch(metadata::isAnnotatedWith);
136145
}
137146
}

java-checks/src/test/java/org/sonar/java/checks/MissingBeanValidationCheckTest.java

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,18 +19,34 @@
1919
import org.junit.jupiter.api.Test;
2020
import org.sonar.java.checks.verifier.CheckVerifier;
2121

22+
import static org.sonar.java.checks.verifier.TestUtils.mainCodeSourcesPath;
23+
2224
class MissingBeanValidationCheckTest {
2325

2426
@Test
25-
void test() {
27+
void testWithJavax() {
28+
CheckVerifier.newVerifier()
29+
.onFile(mainCodeSourcesPath("checks/MissingBeanValidationCheckJavaxSample.java"))
30+
.withCheck(new MissingBeanValidationCheck())
31+
.verifyIssues();
32+
CheckVerifier.newVerifier()
33+
.onFile(mainCodeSourcesPath("checks/MissingBeanValidationCheckJavaxSample.java"))
34+
.withCheck(new MissingBeanValidationCheck())
35+
.withoutSemantic()
36+
.verifyNoIssues();
37+
}
38+
39+
@Test
40+
void testWithJakarta() {
2641
CheckVerifier.newVerifier()
27-
.onFile("src/test/files/checks/MissingBeanValidationCheck.java")
42+
.onFile(mainCodeSourcesPath("checks/MissingBeanValidationCheckJakartaSample.java"))
2843
.withCheck(new MissingBeanValidationCheck())
2944
.verifyIssues();
3045
CheckVerifier.newVerifier()
31-
.onFile("src/test/files/checks/MissingBeanValidationCheck.java")
46+
.onFile(mainCodeSourcesPath("checks/MissingBeanValidationCheckJakartaSample.java"))
3247
.withCheck(new MissingBeanValidationCheck())
3348
.withoutSemantic()
3449
.verifyNoIssues();
3550
}
51+
3652
}

sonar-java-plugin/src/main/resources/org/sonar/l10n/java/rules/java/S5128.html

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
<h2>Why is this an issue?</h2>
2-
<p><code>Bean Validation</code> as per defined by JSR 380 can be triggered programmatically or also executed by the <code>Bean Validation</code>
2+
<p><code>Bean Validation</code> as defined by JSR 380 can be triggered programmatically or also executed by the <code>Bean Validation</code>
33
providers. However something should tell the <code>Bean Validation</code> provider that a variable must be validated otherwise no validation will
4-
happen. This can be achieved by annotating a variable with <code>javax.validation.Valid</code> and unfortunally it’s easy to forget to add this
5-
annotation on complex Beans.</p>
4+
happen. This can be achieved by annotating a variable with <code>javax.validation.Valid</code> or <code>jakarta.validation.Valid</code> and is
5+
unfortunately easy to forget on complex Beans.</p>
66
<p>Not annotating a variable with <code>@Valid</code> means <code>Bean Validation</code> will not be triggered for this variable, but readers may
77
overlook this omission and assume the variable will be validated.</p>
8-
<p>This rule will run by default on all <code>Class</code>'es and therefore can generate a lot of noise. This rule should be restricted to run only on
8+
<p>This rule will run by default on all <code>Class</code>'es and can therefore generate a lot of noise. It should be restricted to run only on
99
certain layers. For this reason, the "Restrict Scope of Coding Rules" feature should be used to check for missing <code>@Valid</code> annotations only
1010
on some packages of the application.</p>
1111
<h3>Noncompliant code example</h3>

0 commit comments

Comments
 (0)