Add Nullaway Checkstyle

- Require package-info.java with @NullMarked in every package
- Suppress package checks for tests and modules that haven't been worked on
- Prevent non org.jspecify Nullability imports on enabled modules
- Validate Nullable is before modifiers

Closes gh-18564
This commit is contained in:
Robert Winch 2026-01-22 14:58:41 -06:00
parent d7fbf3673a
commit 1b3cf72fc9
No known key found for this signature in database
2 changed files with 72 additions and 5 deletions

View File

@ -15,6 +15,9 @@
<suppress files="BCrypt\.java|BCryptTests\.java" checks=".*"/>
<suppress files="org[\\/]springframework[\\/]security[\\/]core[\\/]ComparableVersion\.java" checks=".*"/>
<!-- Suppress sun.misc.Unsafe in this class (we should eventually remove its usage but it is unrelated to nullability imports) -->
<suppress files="StaticFinalReflectionUtils\.java" checks="IllegalImport" id="bannedNullabilityImports"/>
<!-- Method Visibility that we can't reduce -->
<suppress files="AbstractAclVoterTests\.java" checks="SpringMethodVisibility"/>
<suppress files="AnnotationParameterNameDiscovererTests\.java" checks="SpringMethodVisibility"/>
@ -49,4 +52,46 @@
<!-- Ignore String.toUpperCase() and String.toLowerCase() checks in tests -->
<suppress files="[\\/]src[\\/]test[\\/]" checks="RegexpSinglelineJava" id="toLowerCaseWithoutLocale"/>
<suppress files="[\\/]src[\\/]test[\\/]" checks="RegexpSinglelineJava" id="toUpperCaseWithoutLocale"/>
<!-- Suppress @NullMarked check for all files that are NOT package-info.java -->
<suppress files=".*(?&lt;!package-info)\.java$" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<!-- Suppress package-info.java and @NullMarked checks for test sources -->
<suppress files="[\\/]src[\\/]test[\\/]" checks="JavadocPackage"/>
<suppress files="[\\/]src[\\/]test[\\/].*package-info\.java$" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<!-- Suppress nullability checks for modules that don't have JSpecify nullability applied yet -->
<suppress files="access[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="access[\\/]" checks="JavadocPackage"/>
<suppress files="access[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="aspects[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="aspects[\\/]" checks="JavadocPackage"/>
<suppress files="aspects[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="config[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="config[\\/]" checks="JavadocPackage"/>
<suppress files="config[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="itest[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="itest[\\/]" checks="JavadocPackage"/>
<suppress files="itest[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="ldap[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="ldap[\\/]" checks="JavadocPackage"/>
<suppress files="ldap[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="oauth2[\\/]oauth2-authorization-server[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="oauth2[\\/]oauth2-authorization-server[\\/]" checks="JavadocPackage"/>
<suppress files="oauth2[\\/]oauth2-authorization-server[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="oauth2[\\/]oauth2-client[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="oauth2[\\/]oauth2-client[\\/]" checks="JavadocPackage"/>
<suppress files="oauth2[\\/]oauth2-client[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="oauth2[\\/]oauth2-core[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="oauth2[\\/]oauth2-core[\\/]" checks="JavadocPackage"/>
<suppress files="oauth2[\\/]oauth2-core[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="oauth2[\\/]oauth2-jose[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="oauth2[\\/]oauth2-jose[\\/]" checks="JavadocPackage"/>
<suppress files="oauth2[\\/]oauth2-jose[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="oauth2[\\/]oauth2-resource-server[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="oauth2[\\/]oauth2-resource-server[\\/]" checks="JavadocPackage"/>
<suppress files="oauth2[\\/]oauth2-resource-server[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
<suppress files="saml2[\\/]saml2-service-provider[\\/]" checks="IllegalImport" id="bannedNullabilityImports"/>
<suppress files="saml2[\\/]saml2-service-provider[\\/]" checks="JavadocPackage"/>
<suppress files="saml2[\\/]saml2-service-provider[\\/].*package-info\.java" checks="RegexpMultiline" id="requireNullMarkedInPackageInfo"/>
</suppressions>

View File

@ -11,6 +11,15 @@
<property name="headerFile" value="${config_loc}/header.txt" />
<property name="fileExtensions" value="java" />
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck" /><!-- Require package-info.java -->
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpMultilineCheck">
<property name="id" value="requireNullMarkedInPackageInfo"/>
<property name="format" value="@NullMarked\s+package"/>
<property name="minimum" value="1"/>
<property name="maximum" value="1"/>
<property name="message" value="package-info.java must include @NullMarked annotation before the package declaration"/>
<property name="fileExtensions" value="java"/>
</module>
<module name="io.spring.javaformat.checkstyle.SpringChecks">
<property name="excludes" value="io.spring.javaformat.checkstyle.check.SpringHeaderCheck" />
<property name="excludes" value="com.puppycrawl.tools.checkstyle.checks.javadoc.JavadocPackageCheck" />
@ -47,10 +56,23 @@
value="String.toUpperCase() should be String.toUpperCase(Locale.ROOT) or String.toUpperCase(Locale.ENGLISH)"/>
<property name="ignoreComments" value="true"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="id" value="bannedImports"/>
<property name="regexp" value="true"/>
<property name="illegalPkgs" value="org.jetbrains.annotations, reactor.util.annotation, javax.annotation"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="id" value="bannedImports"/>
<property name="regexp" value="true"/>
<property name="illegalPkgs" value="org.jetbrains.annotations, reactor.util.annotation, javax.annotation"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.imports.IllegalImportCheck">
<property name="id" value="bannedNullabilityImports"/>
<property name="regexp" value="true"/>
<!-- Rejects all NonNull, Nonnull, and Nullable types that are NOT in the org.jspecify.annotations package. -->
<property name="illegalClasses" value="^(?!org\.jspecify\.annotations).*(Non[Nn]ull|Nullable)$"/>
</module>
<module name="com.puppycrawl.tools.checkstyle.checks.regexp.RegexpSinglelineJavaCheck">
<property name="id" value="nullableAfterModifiers"/>
<property name="format" value="^\s*(public|protected|private|static|final|synchronized|native|strictfp|abstract)\s+@Nullable\s+(public|protected|private|static|final|synchronized|native|strictfp|abstract)\s"/>
<property name="maximum" value="0"/>
<property name="message" value="@Nullable must appear immediately before the type, after all other modifiers (e.g., 'private final @Nullable Object' not 'private @Nullable final Object')"/>
<property name="ignoreComments" value="true"/>
</module>
</module>
</module>