From 56e3ec73c0bb945e6e0510be6c15363af1c7026a Mon Sep 17 00:00:00 2001 From: Snjezana Peco Date: Tue, 19 Dec 2023 18:27:07 +0100 Subject: [PATCH] Add java.compile.nullAnalysis.nonnullbydefault property --- .../internal/preferences/Preferences.java | 39 +++++++++++--- .../src/main/java/org/sample/Test.java | 22 ++++++++ .../main/java/org/sample/package-info.java | 2 + .../preferences/NullAnalysisTest.java | 54 +++++++++++++++++++ 4 files changed, 111 insertions(+), 6 deletions(-) create mode 100644 org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/Test.java create mode 100644 org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/package-info.java diff --git a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java index 531fede8d9..87f797b4ec 100644 --- a/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java +++ b/org.eclipse.jdt.ls.core/src/org/eclipse/jdt/ls/core/internal/preferences/Preferences.java @@ -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"; /** @@ -571,6 +572,7 @@ public class Preferences { // private static Map> nonnullClasspathStorage = new HashMap<>(); private static Map> nullableClasspathStorage = new HashMap<>(); + private static Map> nonnullbydefaultClasspathStorage = new HashMap<>(); private Map configuration; private Severity incompleteClasspathSeverity; @@ -668,6 +670,7 @@ public class Preferences { private boolean androidSupportEnabled; private List nonnullTypes; private List nullableTypes; + private List nonnullbydefaultTypes; private FeatureStatus nullAnalysisMode; private List cleanUpActionsOnSave; private boolean extractInterfaceReplaceEnabled; @@ -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; @@ -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")); @@ -1266,6 +1274,8 @@ public static Preferences createFrom(Map configuration) { prefs.setNonnullTypes(nonnullTypes); List nullableTypes = getList(configuration, JAVA_COMPILE_NULLANALYSIS_NULLABLE, Collections.emptyList()); prefs.setNullableTypes(nullableTypes); + List 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 cleanupActionsOnSave = getList(configuration, JAVA_CLEANUPS_ACTIONS_ON_SAVE, Collections.emptyList()); @@ -2244,6 +2254,14 @@ public void setNullableTypes(List nullableTypes) { this.nullableTypes = nullableTypes; } + public List getNonnullbydefaultTypes() { + return this.nonnullbydefaultTypes; + } + + public void setNonnullbydefaultTypes(List nonnullbydefaultTypes) { + this.nonnullbydefaultTypes = nonnullbydefaultTypes; + } + public void setNullAnalysisMode(FeatureStatus nullAnalysisMode) { this.nullAnalysisMode = nullAnalysisMode; } @@ -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) { @@ -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()) { @@ -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; } } @@ -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 generateProjectNullAnalysisOptions(String nonnullType, String nullableType) { + private Map generateProjectNullAnalysisOptions(String nonnullType, String nullableType, String nonnullbydefaultType) { Map 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 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)); @@ -2442,10 +2467,12 @@ private Map 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; diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/Test.java b/org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/Test.java new file mode 100644 index 0000000000..7cf67f3fc6 --- /dev/null +++ b/org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/Test.java @@ -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; + } + +} + + diff --git a/org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/package-info.java b/org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/package-info.java new file mode 100644 index 0000000000..e68d21daec --- /dev/null +++ b/org.eclipse.jdt.ls.tests/projects/gradle/null-analysis/src/main/java/org/sample/package-info.java @@ -0,0 +1,2 @@ +@org.eclipse.jdt.annotation.NonNullByDefault +package org.sample; \ No newline at end of file diff --git a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/preferences/NullAnalysisTest.java b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/preferences/NullAnalysisTest.java index bd60592029..163f479460 100644 --- a/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/preferences/NullAnalysisTest.java +++ b/org.eclipse.jdt.ls.tests/src/org/eclipse/jdt/ls/core/internal/preferences/NullAnalysisTest.java @@ -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; @@ -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); @@ -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); } @@ -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); @@ -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(); } @@ -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); @@ -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()) { @@ -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 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(); } }