Skip to content

Commit 5acb64e

Browse files
committed
Configure Nullabillity checks with NullAway
This commit configures the Errorprone and Nullaway plugins in the build to automatically check for null safety issues in the codebase. Closes gh-1204
1 parent 121add8 commit 5acb64e

File tree

6 files changed

+93
-4
lines changed

6 files changed

+93
-4
lines changed

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ ext {
1010
subprojects {
1111
apply plugin: 'org.springframework.graphql.conventions'
1212
apply plugin: 'org.springframework.graphql.architecture'
13+
apply plugin: 'org.springframework.graphql.nullability'
1314
group = 'org.springframework.graphql'
1415

1516
ext.javadocLinks = [

buildSrc/build.gradle

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ dependencies {
2323

2424
implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:${kotlinVersion}")
2525
implementation("io.spring.javaformat:spring-javaformat-gradle-plugin:${javaFormatVersion}")
26+
implementation("net.ltgt.errorprone:net.ltgt.errorprone.gradle.plugin:${errorProneVersion}")
2627
implementation "com.tngtech.archunit:archunit:1.4.0"
2728
}
2829

@@ -40,6 +41,10 @@ gradlePlugin {
4041
id = "org.springframework.graphql.architecture"
4142
implementationClass = "org.springframework.graphql.build.architecture.ArchitecturePlugin"
4243
}
44+
nullability {
45+
id = "org.springframework.graphql.nullability"
46+
implementationClass = "org.springframework.graphql.build.nullability.NullabilityPlugin"
47+
}
4348
}
4449
}
4550

buildSrc/gradle.properties

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
javaFormatVersion=0.0.43
1+
javaFormatVersion=0.0.43
2+
errorProneVersion=4.2.0

buildSrc/src/main/java/org/springframework/graphql/build/architecture/ArchitectureCheck.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,10 +45,12 @@
4545
import org.gradle.api.tasks.TaskAction;
4646

4747
import static org.springframework.graphql.build.architecture.ArchitectureRules.allPackagesShouldBeFreeOfTangles;
48+
import static org.springframework.graphql.build.architecture.ArchitectureRules.classShouldNotUseSpringNullAnnotations;
4849
import static org.springframework.graphql.build.architecture.ArchitectureRules.classesShouldNotImportForbiddenTypes;
4950
import static org.springframework.graphql.build.architecture.ArchitectureRules.javaClassesShouldNotImportKotlinAnnotations;
5051
import static org.springframework.graphql.build.architecture.ArchitectureRules.noClassesShouldCallStringToLowerCaseWithoutLocale;
5152
import static org.springframework.graphql.build.architecture.ArchitectureRules.noClassesShouldCallStringToUpperCaseWithoutLocale;
53+
import static org.springframework.graphql.build.architecture.ArchitectureRules.packageInfoShouldBeNullMarked;
5254

5355
/**
5456
* {@link Task} that checks for architecture problems.
@@ -68,7 +70,9 @@ public ArchitectureCheck() {
6870
javaClassesShouldNotImportKotlinAnnotations(),
6971
allPackagesShouldBeFreeOfTangles(),
7072
noClassesShouldCallStringToLowerCaseWithoutLocale(),
71-
noClassesShouldCallStringToUpperCaseWithoutLocale());
73+
noClassesShouldCallStringToUpperCaseWithoutLocale(),
74+
packageInfoShouldBeNullMarked(),
75+
classShouldNotUseSpringNullAnnotations());
7276
getRuleDescriptions().set(getRules().map((rules) -> rules.stream().map(ArchRule::getDescription).toList()));
7377
}
7478

buildSrc/src/main/java/org/springframework/graphql/build/architecture/ArchitectureRules.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,9 +55,9 @@ static ArchRule packageInfoShouldBeNullMarked() {
5555
static ArchRule classShouldNotUseSpringNullAnnotations() {
5656
return ArchRuleDefinition.noClasses()
5757
.should().dependOnClassesThat()
58-
.haveFullyQualifiedName("org.springframework.lang.NonNull")
58+
.haveFullyQualifiedName("org.jspecify.annotations.NonNull")
5959
.orShould().dependOnClassesThat()
60-
.haveFullyQualifiedName("org.springframework.lang.Nullable");
60+
.haveFullyQualifiedName("org.jspecify.annotations.Nullable");
6161
}
6262

6363

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
/*
2+
* Copyright 2020-2025 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.graphql.build.nullability;
18+
19+
import java.util.function.Consumer;
20+
import java.util.regex.Pattern;
21+
22+
import net.ltgt.gradle.errorprone.ErrorProneOptions;
23+
import net.ltgt.gradle.errorprone.ErrorPronePlugin;
24+
import org.gradle.api.Plugin;
25+
import org.gradle.api.Project;
26+
import org.gradle.api.artifacts.DependencySet;
27+
import org.gradle.api.plugins.ExtensionAware;
28+
import org.gradle.api.plugins.JavaPlugin;
29+
import org.gradle.api.tasks.compile.CompileOptions;
30+
import org.gradle.api.tasks.compile.JavaCompile;
31+
32+
/**
33+
* {@link Plugin} for enforcing Nullability checks on the source code.
34+
*
35+
* @author Brian Clozel
36+
*/
37+
public class NullabilityPlugin implements Plugin<Project> {
38+
39+
private static final Pattern COMPILE_MAIN_SOURCES_TASK_NAME = Pattern.compile("compile(\\d+)?Java");
40+
41+
@Override
42+
public void apply(Project project) {
43+
project.getPlugins().apply(ErrorPronePlugin.class);
44+
DependencySet errorproneConfig = project.getConfigurations().getByName("errorprone").getDependencies();
45+
errorproneConfig.add(project.getDependencies().create("com.uber.nullaway:nullaway:0.12.6"));
46+
errorproneConfig.add(project.getDependencies().create("com.google.errorprone:error_prone_core:2.37.0"));
47+
48+
project.getTasks()
49+
.withType(JavaCompile.class)
50+
.configureEach((javaCompile) -> {
51+
if (compilesMainSources(javaCompile)) {
52+
doWithErrorProneOptions(javaCompile, (errorProneOptions) -> {
53+
errorProneOptions.getDisableAllChecks().set(true);
54+
errorProneOptions.option("NullAway:OnlyNullMarked", "true");
55+
errorProneOptions.option("NullAway:CustomContractAnnotations", "org.springframework.lang.Contract");
56+
errorProneOptions.option("NullAway:JSpecifyMode", "true");
57+
errorProneOptions.error("NullAway");
58+
});
59+
}
60+
else {
61+
doWithErrorProneOptions(javaCompile, (errorProneOptions) -> {
62+
errorProneOptions.getEnabled().set(false);
63+
});
64+
}
65+
});
66+
}
67+
68+
private boolean compilesMainSources(JavaCompile compileTask) {
69+
return COMPILE_MAIN_SOURCES_TASK_NAME.matcher(compileTask.getName()).matches();
70+
}
71+
72+
private void doWithErrorProneOptions(JavaCompile compileTask, Consumer<ErrorProneOptions> optionsConsumer) {
73+
CompileOptions options = compileTask.getOptions();
74+
ErrorProneOptions errorProneOptions = ((ExtensionAware) options).getExtensions().getByType(ErrorProneOptions.class);
75+
optionsConsumer.accept(errorProneOptions);
76+
}
77+
78+
}

0 commit comments

Comments
 (0)