Skip to content

Commit 7db2bd7

Browse files
committed
fully add jspecify and nullaway. cleanup some code too
1 parent 5734aac commit 7db2bd7

11 files changed

Lines changed: 119 additions & 36 deletions

pom.xml

Lines changed: 45 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,22 @@
99
<packaging>jar</packaging>
1010

1111
<properties>
12-
<maven.compiler.release>25</maven.compiler.release>
12+
<java.version>25</java.version>
1313
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
1414
<checkstyle.version>13.4.2</checkstyle.version>
1515
<junit.version>6.0.3</junit.version>
16+
<errorprone.version>2.49.0</errorprone.version>
17+
<nullaway.version>0.13.2</nullaway.version>
1618
</properties>
1719

1820
<dependencies>
21+
<!-- Source: https://mvnrepository.com/artifact/org.jspecify/jspecify -->
22+
<dependency>
23+
<groupId>org.jspecify</groupId>
24+
<artifactId>jspecify</artifactId>
25+
<version>1.0.0</version>
26+
<scope>compile</scope>
27+
</dependency>
1928
<dependency>
2029
<groupId>com.puppycrawl.tools</groupId>
2130
<artifactId>checkstyle</artifactId>
@@ -35,6 +44,41 @@
3544
<plugin>
3645
<artifactId>maven-compiler-plugin</artifactId>
3746
<version>3.15.0</version>
47+
<configuration>
48+
<source>${java.version}</source>
49+
<target>${java.version}</target>
50+
<annotationProcessorPaths>
51+
<path>
52+
<groupId>com.google.errorprone</groupId>
53+
<artifactId>error_prone_core</artifactId>
54+
<version>${errorprone.version}</version>
55+
</path>
56+
<path>
57+
<groupId>com.uber.nullaway</groupId>
58+
<artifactId>nullaway</artifactId>
59+
<version>${nullaway.version}</version>
60+
</path>
61+
</annotationProcessorPaths>
62+
<compilerArgs>
63+
<arg>-Amapstruct.unmappedTargetPolicy=ERROR</arg>
64+
<arg>-XDcompilePolicy=simple</arg>
65+
<arg>--should-stop=ifError=FLOW</arg>
66+
<!-- @formatter:off -->
67+
<arg>-Xplugin:ErrorProne -XepDisableAllChecks -XepOpt:NullAway:AnnotatedPackages=it.aboutbits.checkstyle -XepOpt:NullAway:JSpecifyMode=true -Xep:NullAway:ERROR -XepOpt:NullAway:ExcludedFieldAnnotations=org.mockito.InjectMocks,org.mockito.Mock</arg>
68+
<!-- @formatter:on -->
69+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.api=ALL-UNNAMED</arg>
70+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.file=ALL-UNNAMED</arg>
71+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.main=ALL-UNNAMED</arg>
72+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.model=ALL-UNNAMED</arg>
73+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.parser=ALL-UNNAMED</arg>
74+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.processing=ALL-UNNAMED</arg>
75+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.tree=ALL-UNNAMED</arg>
76+
<arg>-J--add-exports=jdk.compiler/com.sun.tools.javac.util=ALL-UNNAMED</arg>
77+
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.code=ALL-UNNAMED</arg>
78+
<arg>-J--add-opens=jdk.compiler/com.sun.tools.javac.comp=ALL-UNNAMED</arg>
79+
</compilerArgs>
80+
<fork>true</fork>
81+
</configuration>
3882
</plugin>
3983
<plugin>
4084
<artifactId>maven-surefire-plugin</artifactId>

src/main/java/it/aboutbits/checkstyle/AnnotationNames.java

Lines changed: 15 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@
33
import com.puppycrawl.tools.checkstyle.api.DetailAST;
44
import com.puppycrawl.tools.checkstyle.api.FullIdent;
55
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
6+
import org.jspecify.annotations.NullMarked;
7+
import org.jspecify.annotations.Nullable;
68

9+
@NullMarked
710
final class AnnotationNames {
811

912
private AnnotationNames() {
@@ -13,12 +16,20 @@ private AnnotationNames() {
1316
* Returns the simple name of an ANNOTATION node, regardless of whether the
1417
* source uses an imported name (@Foo) or a fully-qualified name (@a.b.Foo).
1518
*/
16-
static String simpleName(DetailAST annotation) {
17-
var ident = annotation.findFirstToken(TokenTypes.IDENT);
19+
static @Nullable String simpleName(DetailAST annotation) {
20+
return lastIdentIn(annotation);
21+
}
22+
23+
/**
24+
* Returns the text of the last IDENT reachable from {@code node}: the direct
25+
* IDENT child when present, or the last child of the first DOT child otherwise.
26+
*/
27+
static @Nullable String lastIdentIn(DetailAST node) {
28+
var ident = node.findFirstToken(TokenTypes.IDENT);
1829
if (ident != null) {
1930
return ident.getText();
2031
}
21-
var dot = annotation.findFirstToken(TokenTypes.DOT);
32+
var dot = node.findFirstToken(TokenTypes.DOT);
2233
if (dot != null) {
2334
var last = dot.getLastChild();
2435
if (last != null && last.getType() == TokenTypes.IDENT) {
@@ -33,7 +44,7 @@ static String simpleName(DetailAST annotation) {
3344
* the source uses an imported form, or the full dotted name when written
3445
* inline as a fully-qualified annotation.
3546
*/
36-
static String fullName(DetailAST annotation) {
47+
static @Nullable String fullName(DetailAST annotation) {
3748
var child = annotation.findFirstToken(TokenTypes.IDENT);
3849
if (child == null) {
3950
child = annotation.findFirstToken(TokenTypes.DOT);

src/main/java/it/aboutbits/checkstyle/JspecifyAnnotationOrderCheck.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,16 +3,24 @@
33
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
44
import com.puppycrawl.tools.checkstyle.api.DetailAST;
55
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
6+
import org.jspecify.annotations.NullMarked;
7+
import org.jspecify.annotations.NullUnmarked;
68

79
import java.util.LinkedHashSet;
810
import java.util.Set;
911

12+
@NullMarked
1013
public class JspecifyAnnotationOrderCheck extends AbstractCheck {
1114

1215
public static final String MSG_KEY = "jspecify.order.notLast";
1316

14-
private Set<String> closeAnnotations = new LinkedHashSet<>(Set.of("NullMarked", "NullUnmarked"));
17+
private Set<String> closeAnnotations = new LinkedHashSet<>(Set.of(
18+
NullMarked.class.getSimpleName(),
19+
NullUnmarked.class.getSimpleName()
20+
));
1521

22+
// Called by Checkstyle via reflection for <property name="closeAnnotations" value="..."/>
23+
@SuppressWarnings("unused")
1624
public void setCloseAnnotations(String... names) {
1725
var next = new LinkedHashSet<String>();
1826
for (var n : names) {

src/main/java/it/aboutbits/checkstyle/JspecifyInlineTypeUseCheck.java

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,24 @@
44
import com.puppycrawl.tools.checkstyle.api.DetailAST;
55
import com.puppycrawl.tools.checkstyle.api.FullIdent;
66
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
7+
import org.jspecify.annotations.NonNull;
8+
import org.jspecify.annotations.NullMarked;
9+
import org.jspecify.annotations.Nullable;
710

811
import java.util.HashSet;
912
import java.util.Set;
1013

14+
@NullMarked
1115
public class JspecifyInlineTypeUseCheck extends AbstractCheck {
1216

1317
public static final String MSG_KEY = "jspecify.inline.notInline";
1418

15-
private static final String JSPECIFY_PACKAGE = "org.jspecify.annotations";
19+
private static final String JSPECIFY_PACKAGE = Nullable.class.getPackageName();
1620
private static final String JSPECIFY_PACKAGE_DOT = JSPECIFY_PACKAGE + ".";
17-
private static final Set<String> TYPE_USE_ANNOTATIONS = Set.of("Nullable", "NonNull");
21+
private static final Set<String> TYPE_USE_ANNOTATIONS = Set.of(
22+
Nullable.class.getSimpleName(),
23+
NonNull.class.getSimpleName()
24+
);
1825

1926
private Set<String> jspecifySimpleNamesInScope = new HashSet<>();
2027

src/main/java/it/aboutbits/checkstyle/JspecifyMapStructMapperAnnotationCheck.java

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,33 @@
33
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
44
import com.puppycrawl.tools.checkstyle.api.DetailAST;
55
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
6+
import org.jspecify.annotations.NullMarked;
7+
import org.jspecify.annotations.NullUnmarked;
8+
import org.jspecify.annotations.Nullable;
69

710
import java.util.ArrayList;
811

12+
@NullMarked
913
public class JspecifyMapStructMapperAnnotationCheck extends AbstractCheck {
1014

1115
public static final String MSG_KEY = "jspecify.mapstruct.annotation.invalid";
1216

1317
private String mapperAnnotationName = "Mapper";
1418
private String annotateWithAnnotationName = "AnnotateWith";
15-
private String nullUnmarkedAnnotationName = "NullUnmarked";
19+
private String nullUnmarkedAnnotationName = NullUnmarked.class.getSimpleName();
1620

21+
// The three setters below are called by Checkstyle via reflection for the corresponding <property .../> entries.
22+
@SuppressWarnings("unused")
1723
public void setMapperAnnotationName(String name) {
1824
this.mapperAnnotationName = name;
1925
}
2026

27+
@SuppressWarnings("unused")
2128
public void setAnnotateWithAnnotationName(String name) {
2229
this.annotateWithAnnotationName = name;
2330
}
2431

32+
@SuppressWarnings("unused")
2533
public void setNullUnmarkedAnnotationName(String name) {
2634
this.nullUnmarkedAnnotationName = name;
2735
}
@@ -68,7 +76,7 @@ public void visitToken(DetailAST ast) {
6876
logInvalid(ast);
6977
return;
7078
}
71-
var last = annotations.get(annotations.size() - 1);
79+
var last = annotations.getLast();
7280
var secondLast = annotations.get(annotations.size() - 2);
7381
var lastOk = nullUnmarkedAnnotationName.equals(AnnotationNames.simpleName(last));
7482
var secondLastOk = annotateWithAnnotationName.equals(AnnotationNames.simpleName(secondLast))
@@ -97,7 +105,7 @@ private static boolean annotateWithArgumentReferences(DetailAST annotation, Stri
97105
return false;
98106
}
99107

100-
private static String memberValuePairClassLiteralName(DetailAST pair) {
108+
private static @Nullable String memberValuePairClassLiteralName(DetailAST pair) {
101109
var ident = pair.findFirstToken(TokenTypes.IDENT);
102110
if (ident == null || !"value".equals(ident.getText())) {
103111
return null;
@@ -106,26 +114,14 @@ private static String memberValuePairClassLiteralName(DetailAST pair) {
106114
return expr == null ? null : classLiteralName(expr);
107115
}
108116

109-
private static String classLiteralName(DetailAST expr) {
117+
private static @Nullable String classLiteralName(DetailAST expr) {
110118
var dot = expr.findFirstToken(TokenTypes.DOT);
111119
if (dot == null) {
112120
return null;
113121
}
114-
var classLiteral = dot.findFirstToken(TokenTypes.LITERAL_CLASS);
115-
if (classLiteral == null) {
122+
if (dot.findFirstToken(TokenTypes.LITERAL_CLASS) == null) {
116123
return null;
117124
}
118-
var ident = dot.findFirstToken(TokenTypes.IDENT);
119-
if (ident != null) {
120-
return ident.getText();
121-
}
122-
var nestedDot = dot.findFirstToken(TokenTypes.DOT);
123-
if (nestedDot != null) {
124-
var last = nestedDot.getLastChild();
125-
if (last != null && last.getType() == TokenTypes.IDENT) {
126-
return last.getText();
127-
}
128-
}
129-
return null;
125+
return AnnotationNames.lastIdentIn(dot);
130126
}
131127
}

src/main/java/it/aboutbits/checkstyle/JspecifyOnTopLevelTypesCheck.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@
33
import com.puppycrawl.tools.checkstyle.api.AbstractCheck;
44
import com.puppycrawl.tools.checkstyle.api.DetailAST;
55
import com.puppycrawl.tools.checkstyle.api.TokenTypes;
6+
import org.jspecify.annotations.NullMarked;
7+
import org.jspecify.annotations.NullUnmarked;
68

79
import java.util.Set;
810

11+
@NullMarked
912
public class JspecifyOnTopLevelTypesCheck extends AbstractCheck {
1013

1114
public static final String MSG_KEY = "jspecify.toplevel.missing";
1215

13-
private static final Set<String> JSPECIFY_TYPE_ANNOTATIONS = Set.of("NullMarked", "NullUnmarked");
16+
private static final Set<String> JSPECIFY_TYPE_ANNOTATIONS = Set.of(
17+
NullMarked.class.getSimpleName(),
18+
NullUnmarked.class.getSimpleName()
19+
);
1420

1521
@Override
1622
public int[] getDefaultTokens() {

src/test/java/it/aboutbits/checkstyle/CheckTestSupport.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,15 @@
66
import com.puppycrawl.tools.checkstyle.api.AuditEvent;
77
import com.puppycrawl.tools.checkstyle.api.AuditListener;
88

9+
import org.jspecify.annotations.NullMarked;
10+
911
import java.io.File;
1012
import java.util.ArrayList;
1113
import java.util.Collections;
1214
import java.util.List;
1315
import java.util.Map;
1416

17+
@NullMarked
1518
public abstract class CheckTestSupport {
1619

1720
protected List<String> runCheck(

src/test/java/it/aboutbits/checkstyle/JspecifyAnnotationOrderCheckTest.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package it.aboutbits.checkstyle;
22

3+
import org.jspecify.annotations.NullMarked;
34
import org.junit.jupiter.api.Test;
45

56
import java.util.List;
@@ -8,6 +9,7 @@
89
import static org.junit.jupiter.api.Assertions.assertEquals;
910
import static org.junit.jupiter.api.Assertions.assertTrue;
1011

12+
@NullMarked
1113
class JspecifyAnnotationOrderCheckTest extends CheckTestSupport {
1214

1315
private static final String BASE = "/it/aboutbits/checkstyle/jspecifyAnnotationOrder/";
@@ -22,15 +24,15 @@ void jspecifyAnnotationAsLastPasses() throws Exception {
2224
void jspecifyAnnotationFollowedByOtherFailsAndHighlightsCloseAnnotation() throws Exception {
2325
var v = runCheck(JspecifyAnnotationOrderCheck.class, BASE + "BadInMiddle.java");
2426
assertEquals(1, v.size());
25-
assertTrue(v.get(0).contains("NullMarked"), v.toString());
26-
assertTrue(v.get(0).startsWith("6:"), "violation should be reported on the @NullMarked line: " + v);
27+
assertTrue(v.getFirst().contains(NullMarked.class.getSimpleName()), v.toString());
28+
assertTrue(v.getFirst().startsWith("6:"), "violation should be reported on the @NullMarked line: " + v);
2729
}
2830

2931
@Test
3032
void jspecifyAnnotationAsFirstFailsAndHighlightsCloseAnnotation() throws Exception {
3133
var v = runCheck(JspecifyAnnotationOrderCheck.class, BASE + "BadFirst.java");
3234
assertEquals(1, v.size());
33-
assertTrue(v.get(0).startsWith("5:"), "violation should be reported on the @NullMarked line: " + v);
35+
assertTrue(v.getFirst().startsWith("5:"), "violation should be reported on the @NullMarked line: " + v);
3436
}
3537

3638
@Test
@@ -48,7 +50,7 @@ void customCloseAnnotationsArePicked() throws Exception {
4850

4951
var bad = runCheck(JspecifyAnnotationOrderCheck.class, BASE + "CustomCloseInMiddle.java", properties);
5052
assertEquals(1, bad.size());
51-
assertTrue(bad.get(0).contains("Custom"), bad.toString());
53+
assertTrue(bad.getFirst().contains("Custom"), bad.toString());
5254
}
5355

5456
@Test

src/test/java/it/aboutbits/checkstyle/JspecifyInlineTypeUseCheckTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package it.aboutbits.checkstyle;
22

3+
import org.jspecify.annotations.NullMarked;
34
import org.junit.jupiter.api.Test;
45

56
import java.util.List;
67

78
import static org.junit.jupiter.api.Assertions.assertEquals;
89

10+
@NullMarked
911
class JspecifyInlineTypeUseCheckTest extends CheckTestSupport {
1012

1113
private static final String BASE = "/it/aboutbits/checkstyle/jspecifyInlineTypeUse/";

src/test/java/it/aboutbits/checkstyle/JspecifyMapStructMapperAnnotationCheckTest.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package it.aboutbits.checkstyle;
22

3+
import org.jspecify.annotations.NullMarked;
34
import org.junit.jupiter.api.Test;
45

56
import java.util.List;
@@ -8,6 +9,7 @@
89
import static org.junit.jupiter.api.Assertions.assertEquals;
910
import static org.junit.jupiter.api.Assertions.assertTrue;
1011

12+
@NullMarked
1113
class JspecifyMapStructMapperAnnotationCheckTest extends CheckTestSupport {
1214

1315
private static final String BASE = "/it/aboutbits/checkstyle/jspecifyMapStructMapperAnnotation/";
@@ -29,7 +31,7 @@ void mapperWithNamedValueArgumentPasses() throws Exception {
2931
void swappedOrderFails() throws Exception {
3032
var v = runCheck(JspecifyMapStructMapperAnnotationCheck.class, BASE + "BadOrderSwapped.java");
3133
assertEquals(1, v.size());
32-
assertTrue(v.get(0).contains("BadOrderSwapped"), v.toString());
34+
assertTrue(v.getFirst().contains("BadOrderSwapped"), v.toString());
3335
}
3436

3537
@Test
@@ -61,7 +63,7 @@ void customMapperAnnotationNameIsHonored() throws Exception {
6163

6264
var bad = runCheck(JspecifyMapStructMapperAnnotationCheck.class, BASE + "CustomMapperBad.java", properties);
6365
assertEquals(1, bad.size());
64-
assertTrue(bad.get(0).contains("CustomMapperBad"), bad.toString());
66+
assertTrue(bad.getFirst().contains("CustomMapperBad"), bad.toString());
6567
}
6668

6769
@Test
@@ -98,7 +100,7 @@ void wellOrderedAbstractClassMapperPasses() throws Exception {
98100
void abstractClassMapperWithSwappedOrderFails() throws Exception {
99101
var v = runCheck(JspecifyMapStructMapperAnnotationCheck.class, BASE + "BadAbstractMapperOrderSwapped.java");
100102
assertEquals(1, v.size());
101-
assertTrue(v.get(0).contains("BadAbstractMapperOrderSwapped"), v.toString());
103+
assertTrue(v.getFirst().contains("BadAbstractMapperOrderSwapped"), v.toString());
102104
}
103105

104106
@Test

0 commit comments

Comments
 (0)