Skip to content

Commit

Permalink
Add java.compile.nullAnalysis.nonnullbydefault property
Browse files Browse the repository at this point in the history
  • Loading branch information
snjeza committed Dec 19, 2023
1 parent 7ce1316 commit 56e3ec7
Show file tree
Hide file tree
Showing 4 changed files with 111 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,7 @@ public class Preferences {

public static final String JAVA_COMPILE_NULLANALYSIS_NONNULL = "java.compile.nullAnalysis.nonnull";
public static final String JAVA_COMPILE_NULLANALYSIS_NULLABLE = "java.compile.nullAnalysis.nullable";
public static final String JAVA_COMPILE_NULLANALYSIS_NONNULLBYDEFAULT = "java.compile.nullAnalysis.nonnullbydefault";
public static final String JAVA_COMPILE_NULLANALYSIS_MODE = "java.compile.nullAnalysis.mode";

/**
Expand Down Expand Up @@ -571,6 +572,7 @@ public class Preferences {
// <typeName, subString of classpath>
private static Map<String, List<String>> nonnullClasspathStorage = new HashMap<>();
private static Map<String, List<String>> nullableClasspathStorage = new HashMap<>();
private static Map<String, List<String>> nonnullbydefaultClasspathStorage = new HashMap<>();

private Map<String, Object> configuration;
private Severity incompleteClasspathSeverity;
Expand Down Expand Up @@ -668,6 +670,7 @@ public class Preferences {
private boolean androidSupportEnabled;
private List<String> nonnullTypes;
private List<String> nullableTypes;
private List<String> nonnullbydefaultTypes;
private FeatureStatus nullAnalysisMode;
private List<String> cleanUpActionsOnSave;
private boolean extractInterfaceReplaceEnabled;
Expand Down Expand Up @@ -901,6 +904,7 @@ public Preferences() {
avoidVolatileChanges = true;
nonnullTypes = new ArrayList<>();
nullableTypes = new ArrayList<>();
nonnullbydefaultTypes = new ArrayList<>();
nullAnalysisMode = FeatureStatus.disabled;
cleanUpActionsOnSave = new ArrayList<>();
extractInterfaceReplaceEnabled = false;
Expand All @@ -913,15 +917,19 @@ private static void initializeNullAnalysisClasspathStorage() {
// should support Maven style and Gradle style classpath
nonnullClasspathStorage.put("javax.annotation.Nonnull", getClasspathSubStringFromArtifact("com.google.code.findbugs:jsr305"));
nullableClasspathStorage.put("javax.annotation.Nullable", getClasspathSubStringFromArtifact("com.google.code.findbugs:jsr305"));
nonnullbydefaultClasspathStorage.put("javax.annotation.ParametersAreNonnullByDefault", getClasspathSubStringFromArtifact("com.google.code.findbugs:jsr305"));

nonnullClasspathStorage.put("org.eclipse.jdt.annotation.NonNull", getClasspathSubStringFromArtifact("org.eclipse.jdt:org.eclipse.jdt.annotation"));
nullableClasspathStorage.put("org.eclipse.jdt.annotation.Nullable", getClasspathSubStringFromArtifact("org.eclipse.jdt:org.eclipse.jdt.annotation"));
nonnullbydefaultClasspathStorage.put("org.eclipse.jdt.annotation.NonNullByDefault", getClasspathSubStringFromArtifact("org.eclipse.jdt:org.eclipse.jdt.annotation"));

nonnullClasspathStorage.put("org.springframework.lang.NonNull", getClasspathSubStringFromArtifact("org.springframework:spring-core"));
nullableClasspathStorage.put("org.springframework.lang.Nullable", getClasspathSubStringFromArtifact("org.springframework:spring-core"));
nonnullbydefaultClasspathStorage.put("org.springframework.lang.NonNullApi", getClasspathSubStringFromArtifact("org.springframework:spring-core"));

nonnullClasspathStorage.put("io.micrometer.core.lang.NonNull", getClasspathSubStringFromArtifact("io.micrometer:micrometer-core"));
nullableClasspathStorage.put("io.micrometer.core.lang.Nullable", getClasspathSubStringFromArtifact("io.micrometer:micrometer-core"));
nonnullbydefaultClasspathStorage.put("io.micrometer.core.lang.NonNullApi", getClasspathSubStringFromArtifact("io.micrometer:micrometer-core"));

nonnullClasspathStorage.put("org.jetbrains.annotations.NotNull", getClasspathSubStringFromArtifact("org.jetbrains:annotations"));
nullableClasspathStorage.put("org.jetbrains.annotations.Nullable", getClasspathSubStringFromArtifact("org.jetbrains:annotations"));
Expand Down Expand Up @@ -1266,6 +1274,8 @@ public static Preferences createFrom(Map<String, Object> configuration) {
prefs.setNonnullTypes(nonnullTypes);
List<String> nullableTypes = getList(configuration, JAVA_COMPILE_NULLANALYSIS_NULLABLE, Collections.emptyList());
prefs.setNullableTypes(nullableTypes);
List<String> nonullbydefaultTypes = getList(configuration, JAVA_COMPILE_NULLANALYSIS_NONNULLBYDEFAULT, Collections.emptyList());
prefs.setNonnullbydefaultTypes(nonullbydefaultTypes);
String nullAnalysisMode = getString(configuration, JAVA_COMPILE_NULLANALYSIS_MODE, null);
prefs.setNullAnalysisMode(FeatureStatus.fromString(nullAnalysisMode, FeatureStatus.disabled));
List<String> cleanupActionsOnSave = getList(configuration, JAVA_CLEANUPS_ACTIONS_ON_SAVE, Collections.emptyList());
Expand Down Expand Up @@ -2244,6 +2254,14 @@ public void setNullableTypes(List<String> nullableTypes) {
this.nullableTypes = nullableTypes;
}

public List<String> getNonnullbydefaultTypes() {
return this.nonnullbydefaultTypes;
}

public void setNonnullbydefaultTypes(List<String> nonnullbydefaultTypes) {
this.nonnullbydefaultTypes = nonnullbydefaultTypes;
}

public void setNullAnalysisMode(FeatureStatus nullAnalysisMode) {
this.nullAnalysisMode = nullAnalysisMode;
}
Expand Down Expand Up @@ -2325,9 +2343,14 @@ public boolean updateAnnotationNullAnalysisOptions(IJavaProject javaProject, boo
if (enabled) {
String nonnullType = getAnnotationType(javaProject, this.nonnullTypes, nonnullClasspathStorage);
String nullableType = getAnnotationType(javaProject, this.nullableTypes, nullableClasspathStorage);
projectNullAnalysisOptions = generateProjectNullAnalysisOptions(nonnullType, nullableType);
String nonnullbydefaultTypes = getAnnotationType(javaProject, this.nonnullbydefaultTypes, nonnullbydefaultClasspathStorage);
if (nonnullbydefaultTypes == null) {
// there is not NonNullByDefault in org.jetbrains:annotations
nonnullbydefaultTypes = "org.eclipse.jdt.annotation.NonNullByDefault";
}
projectNullAnalysisOptions = generateProjectNullAnalysisOptions(nonnullType, nullableType, nonnullbydefaultTypes);
} else {
projectNullAnalysisOptions = generateProjectNullAnalysisOptions(null, null);
projectNullAnalysisOptions = generateProjectNullAnalysisOptions(null, null, null);
}
boolean shouldUpdate = !projectNullAnalysisOptions.entrySet().stream().allMatch(e -> e.getValue().equals(projectInheritOptions.get(e.getKey())));
if (shouldUpdate) {
Expand All @@ -2344,7 +2367,7 @@ public boolean updateAnnotationNullAnalysisOptions(IJavaProject javaProject, boo
}

private boolean hasAnnotationNullAnalysisTypes() {
if (this.nonnullTypes.isEmpty() && this.nullableTypes.isEmpty()) {
if (this.nonnullTypes.isEmpty() && this.nullableTypes.isEmpty() && this.nonnullbydefaultTypes.isEmpty()) {
return false;
}
for (IJavaProject javaProject : ProjectUtils.getJavaProjects()) {
Expand All @@ -2353,7 +2376,8 @@ private boolean hasAnnotationNullAnalysisTypes() {
}
String nonnullType = getAnnotationType(javaProject, this.nonnullTypes, nonnullClasspathStorage);
String nullableType = getAnnotationType(javaProject, this.nullableTypes, nullableClasspathStorage);
if (nonnullType != null || nullableType != null) {
String nonnullbydefaultTypes = getAnnotationType(javaProject, this.nonnullbydefaultTypes, nonnullbydefaultClasspathStorage);
if (nonnullType != null || nullableType != null || nonnullbydefaultTypes != null) {
return true;
}
}
Expand Down Expand Up @@ -2425,14 +2449,15 @@ private String findTypeInProject(IJavaProject javaProject, String annotationType
* @param nullableType the given nullable type
* @return the map contains the null analysis options, if both given types are null, will return default null analysis options
*/
private Map<String, String> generateProjectNullAnalysisOptions(String nonnullType, String nullableType) {
private Map<String, String> generateProjectNullAnalysisOptions(String nonnullType, String nullableType, String nonnullbydefaultType) {
Map<String, String> options = new HashMap<>();
if (nonnullType == null && nullableType == null) {
if (nonnullType == null && nullableType == null && nonnullbydefaultType == null) {
options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, "disabled");
// set default values
Hashtable<String, String> defaultOptions = JavaCore.getDefaultOptions();
options.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, defaultOptions.get(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME));
options.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, defaultOptions.get(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME));
options.put(JavaCore.COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME, defaultOptions.get(JavaCore.COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME));
options.put(JavaCore.COMPILER_PB_NULL_REFERENCE, defaultOptions.get(JavaCore.COMPILER_PB_NULL_REFERENCE));
options.put(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE, defaultOptions.get(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE));
options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, defaultOptions.get(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION));
Expand All @@ -2442,10 +2467,12 @@ private Map<String, String> generateProjectNullAnalysisOptions(String nonnullTyp
options.put(JavaCore.COMPILER_ANNOTATION_NULL_ANALYSIS, "enabled");
options.put(JavaCore.COMPILER_NONNULL_ANNOTATION_NAME, nonnullType != null ? nonnullType : "");
options.put(JavaCore.COMPILER_NULLABLE_ANNOTATION_NAME, nullableType != null ? nullableType : "");
options.put(JavaCore.COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME, nonnullbydefaultType != null ? nonnullbydefaultType : "");
options.put(JavaCore.COMPILER_PB_NULL_REFERENCE, "warning");
options.put(JavaCore.COMPILER_PB_POTENTIAL_NULL_REFERENCE, "warning");
options.put(JavaCore.COMPILER_PB_NULL_SPECIFICATION_VIOLATION, "warning");
options.put(JavaCore.COMPILER_PB_NULL_ANNOTATION_INFERENCE_CONFLICT, "warning");
options.put(JavaCore.COMPILER_PB_MISSING_NONNULL_BY_DEFAULT_ANNOTATION, "ignore");
options.put(JavaCore.COMPILER_PB_SYNTACTIC_NULL_ANALYSIS_FOR_FIELDS, JavaCore.ENABLED);
}
return options;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package org.sample;

public class Test {

private String obj = "";
private Integer count;
public String getObj() {
return obj;
}
public void setObj(String obj) {
this.obj = obj;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}

}


Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
@org.eclipse.jdt.annotation.NonNullByDefault
package org.sample;
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,15 @@
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;

import java.io.ByteArrayInputStream;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IMarker;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.JavaCore;
Expand All @@ -44,6 +47,7 @@ public void testNullAnalysisWithJavax() throws Exception {
try {
this.preferenceManager.getPreferences().setNonnullTypes(List.of("javax.annotation.Nonnull", "org.eclipse.jdt.annotation.NonNull"));
this.preferenceManager.getPreferences().setNullableTypes(List.of("javax.annotation.Nullable", "org.eclipse.jdt.annotation.Nullable"));
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(List.of("org.eclipse.jdt.annotation.NonNullByDefault", "javax.annotation.ParametersAreNonnullByDefault"));
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.automatic);
IProject project = importGradleProject("null-analysis");
assertIsJavaProject(project);
Expand Down Expand Up @@ -74,6 +78,7 @@ public void testNullAnalysisWithJavax() throws Exception {
} finally {
this.preferenceManager.getPreferences().setNonnullTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNullableTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(Collections.emptyList());
this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions();
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.disabled);
}
Expand All @@ -84,6 +89,7 @@ public void testMixedNullAnalysis() throws Exception {
try {
this.preferenceManager.getPreferences().setNonnullTypes(List.of("javax.annotation.Nonnull", "org.eclipse.jdt.annotation.NonNull"));
this.preferenceManager.getPreferences().setNullableTypes(List.of("org.eclipse.jdt.annotation.Nullable", "javax.annotation.Nonnull"));
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(List.of("org.eclipse.jdt.annotation.NonNullByDefault", "javax.annotation.ParametersAreNonnullByDefault"));
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.automatic);
IProject project = importGradleProject("null-analysis");
assertIsJavaProject(project);
Expand Down Expand Up @@ -111,6 +117,7 @@ public void testMixedNullAnalysis() throws Exception {
} finally {
this.preferenceManager.getPreferences().setNonnullTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNullableTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.disabled);
this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions();
}
Expand All @@ -120,6 +127,7 @@ public void testMixedNullAnalysis() throws Exception {
public void testNullAnalysisDisabled() throws Exception {
this.preferenceManager.getPreferences().setNonnullTypes(List.of("javax.annotation.Nonnull", "org.eclipse.jdt.annotation.NonNull"));
this.preferenceManager.getPreferences().setNullableTypes(List.of("javax.annotation.Nullable", "org.eclipse.jdt.annotation.Nullable"));
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(List.of("org.eclipse.jdt.annotation.NonNullByDefault", "javax.annotation.ParametersAreNonnullByDefault"));
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.disabled);
IProject project = importGradleProject("null-analysis");
assertIsJavaProject(project);
Expand All @@ -137,6 +145,8 @@ public void testKeepExistingProjectOptions() throws Exception {
try {
this.preferenceManager.getPreferences().setNonnullTypes(List.of("javax.annotation.Nonnull", "org.eclipse.jdt.annotation.NonNull"));
this.preferenceManager.getPreferences().setNullableTypes(List.of("javax.annotation.Nullable", "org.eclipse.jdt.annotation.Nullable"));
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(List.of("org.eclipse.jdt.annotation.NonNullByDefault", "javax.annotation.ParametersAreNonnullByDefault"));

IProject project = importGradleProject("null-analysis");
assertIsJavaProject(project);
if (this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions()) {
Expand All @@ -152,6 +162,50 @@ public void testKeepExistingProjectOptions() throws Exception {
} finally {
this.preferenceManager.getPreferences().setNonnullTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNullableTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(Collections.emptyList());
this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions();
}
}

@Test
public void testNonnullbyDefault() throws Exception {
try {
this.preferenceManager.getPreferences().setNonnullTypes(List.of("javax.annotation.Nonnull", "org.eclipse.jdt.annotation.NonNull"));
this.preferenceManager.getPreferences().setNullableTypes(List.of("org.eclipse.jdt.annotation.Nullable", "javax.annotation.Nonnull"));
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(List.of("org.eclipse.jdt.annotation.NonNullByDefault", "javax.annotation.ParametersAreNonnullByDefault"));
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.automatic);
IProject project = importGradleProject("null-analysis");
assertIsJavaProject(project);
BuildWorkspaceHandler buildWorkspaceHandler = new BuildWorkspaceHandler(JavaLanguageServerPlugin.getProjectsManager());
if (this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions()) {
buildWorkspaceHandler.buildWorkspace(true, new NullProgressMonitor());
}
Map<String, String> options = JavaCore.create(project).getOptions(true);
assertEquals(options.get(JavaCore.COMPILER_NONNULL_BY_DEFAULT_ANNOTATION_NAME), "org.eclipse.jdt.annotation.NonNullByDefault");
IFile file = project.getFile("/src/main/java/org/sample/Test.java");
assertTrue(file.exists());
IMarker[] markers = file.findMarkers(null, true, IResource.DEPTH_INFINITE);
assertEquals(1, markers.length);
IMarker marker = getWarningMarker(project, "The @Nonnull field count may not have been initialized");
assertNotNull(marker);
IFile packageInfo = project.getFile("/src/main/java/org/sample/package-info.java");
assertTrue(packageInfo.exists());
String contents = """
package org.sample;
""";
ByteArrayInputStream newContent = new ByteArrayInputStream(contents.getBytes("UTF-8")); //$NON-NLS-1$
packageInfo.setContents(newContent, IResource.FORCE, new NullProgressMonitor());
buildWorkspaceHandler.buildWorkspace(true, new NullProgressMonitor());
markers = file.findMarkers(null, true, IResource.DEPTH_INFINITE);
assertEquals(0, markers.length);
marker = getWarningMarker(project, "The @Nonnull field count may not have been initialized");
assertNull(marker);
assertNoErrors(project);
} finally {
this.preferenceManager.getPreferences().setNonnullTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNullableTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNonnullbydefaultTypes(Collections.emptyList());
this.preferenceManager.getPreferences().setNullAnalysisMode(FeatureStatus.disabled);
this.preferenceManager.getPreferences().updateAnnotationNullAnalysisOptions();
}
}
Expand Down

0 comments on commit 56e3ec7

Please sign in to comment.