diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json
index ef960e33daf..3b45fb50c85 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2638.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S2638",
"hasTruePositives": true,
- "falseNegatives": 7,
+ "falseNegatives": 12,
"falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json
index 2d9b985a946..d770d1dfa54 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S2789.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S2789",
"hasTruePositives": true,
- "falseNegatives": 11,
+ "falseNegatives": 17,
"falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4454.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4454.json
index b2e648ecef5..67431a3f555 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4454.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4454.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S4454",
"hasTruePositives": true,
- "falseNegatives": 1,
+ "falseNegatives": 2,
"falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json
index f29acc0f6b3..262b982dbdb 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4682.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S4682",
"hasTruePositives": true,
- "falseNegatives": 0,
+ "falseNegatives": 2,
"falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json
index 2d78f813b10..b898cd6e94b 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S4738.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S4738",
"hasTruePositives": false,
- "falseNegatives": 44,
+ "falseNegatives": 46,
"falsePositives": 0
}
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5786.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5786.json
index ef535f3f85e..0f6b9555e7b 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S5786.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S5786.json
@@ -3,4 +3,4 @@
"hasTruePositives": true,
"falseNegatives": 62,
"falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6816.json b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6816.json
index 4c46ee5a680..3cb4f0a79b2 100644
--- a/its/autoscan/src/test/resources/autoscan/diffs/diff_S6816.json
+++ b/its/autoscan/src/test/resources/autoscan/diffs/diff_S6816.json
@@ -1,6 +1,6 @@
{
"ruleKey": "S6816",
"hasTruePositives": false,
- "falseNegatives": 11,
+ "falseNegatives": 12,
"falsePositives": 0
-}
\ No newline at end of file
+}
diff --git a/java-checks-test-sources/default/pom.xml b/java-checks-test-sources/default/pom.xml
index 6fe6c5ed494..1c9e0083f50 100644
--- a/java-checks-test-sources/default/pom.xml
+++ b/java-checks-test-sources/default/pom.xml
@@ -981,6 +981,11 @@
vavr
0.10.4
+
+ org.jspecify
+ jspecify
+ 1.0.0
+
diff --git a/java-checks-test-sources/default/src/main/java/checks/BooleanMethodReturnCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/BooleanMethodReturnCheckSample.java
index d304785cb32..ed063f4d826 100644
--- a/java-checks-test-sources/default/src/main/java/checks/BooleanMethodReturnCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/BooleanMethodReturnCheckSample.java
@@ -5,6 +5,11 @@
import javax.annotation.Nullable;
class BooleanMethodReturnCheckSampleA {
+
+ public @org.jspecify.annotations.Nullable Boolean myTest() {
+ return null; // Compliant
+ }
+
public Boolean myMethod() {
return null; // Noncompliant {{Null is returned but a "Boolean" is expected.}}
}
@@ -27,6 +32,11 @@ public Boolean foo() {
public Boolean bar() {
return null; // Compliant
}
+
+ @org.jspecify.annotations.Nullable
+ public Boolean baz() {
+ return null; // Compliant
+ }
}
class BooleanMethodReturnCheckSampleB {
diff --git a/java-checks-test-sources/default/src/main/java/checks/EqualsParametersMarkedNonNullCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/EqualsParametersMarkedNonNullCheckSample.java
index 2af27e753f7..456fe6c7403 100644
--- a/java-checks-test-sources/default/src/main/java/checks/EqualsParametersMarkedNonNullCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/EqualsParametersMarkedNonNullCheckSample.java
@@ -59,4 +59,10 @@ public boolean equals(Object object) { // Compliant
}
}
+ static class J {
+ public boolean equals(@org.jspecify.annotations.NonNull Object object) { // Noncompliant
+ return false;
+ }
+ }
+
}
diff --git a/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_guava.java b/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_guava.java
index 376a7fe31cb..39e33dc438e 100644
--- a/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_guava.java
+++ b/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_guava.java
@@ -11,6 +11,8 @@ interface NullShouldNotBeUsedWithOptionalCheck_guava {
//^^^^^^^^^
public Optional getOptionalKo();
+ @org.jspecify.annotations.Nullable // Noncompliant
+ Optional getOptional();
}
class NullShouldNotBeUsedWithOptionalCheck_guavaClassA {
@@ -93,11 +95,19 @@ public void doSomething6(@Nullable Optional arg) { // Noncompliant {{"Op
// ^^^^^^^^^
}
+ public void doSomething6_Jspecify(@org.jspecify.annotations.Nullable Optional arg) { // Noncompliant
+ }
+
public void doSomething7() {
@Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}}
// ^^^^^^^^^
Optional var;
}
+
+ public void doSomething7_jspecify() {
+ @org.jspecify.annotations.Nullable // Noncompliant
+ Optional var;
+ }
public Optional doSomething8(boolean b) {
Object obj = b ? null : new Object();
diff --git a/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_jdk.java b/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_jdk.java
index cf734a953be..7d1d433f64b 100644
--- a/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_jdk.java
+++ b/java-checks-test-sources/default/src/main/java/checks/NullShouldNotBeUsedWithOptionalCheck_jdk.java
@@ -12,6 +12,9 @@ interface NullShouldNotBeUsedWithOptionalCheck_jdk {
//^^^^^^^^^
public Optional getOptionalKo();
+
+ @org.jspecify.annotations.Nullable // Noncompliant
+ Optional getOptional();
}
class NullShouldNotBeUsedWithOptionalCheck_jdkClassA {
@@ -101,12 +104,20 @@ public void doSomething6(@Nullable Optional arg) { // Noncompliant {{"Op
// ^^^^^^^^^
}
+ public void doSomething6_Jspecify(@org.jspecify.annotations.Nullable Optional arg) { // Noncompliant
+ }
+
public void doSomething7() {
@Nullable // Noncompliant {{"Optional" variables should not be "@Nullable".}}
// ^^^^^^^^^
Optional var;
}
+ public void doSomething7_jspecify() {
+ @org.jspecify.annotations.Nullable // Noncompliant
+ Optional var;
+ }
+
public void NonnullWithArgument1() {
@javax.annotation.Nonnull(when= When.MAYBE) // Noncompliant {{"Optional" variables should not be "@Nonnull(when=MAYBE)".}}
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
diff --git a/java-checks-test-sources/default/src/main/java/checks/PrimitivesMarkedNullableCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/PrimitivesMarkedNullableCheckSample.java
index d662f341cef..1e362c319d9 100644
--- a/java-checks-test-sources/default/src/main/java/checks/PrimitivesMarkedNullableCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/PrimitivesMarkedNullableCheckSample.java
@@ -38,6 +38,11 @@ abstract class PrimitivesMarkedNullableCheckSample {
@javax.annotation.Nullable
public double getDouble1() { return 0.0; } // Noncompliant {{"@Nullable" annotation should not be used on primitive types}}
+ @org.jspecify.annotations.Nullable
+ public double getDouble1_jspecify() { return 0.0; } // Noncompliant
+
+ public @org.jspecify.annotations.Nullable double getDouble2_jspecify() { return 0.0; } // Noncompliant
+
public double getDouble2() { return 0.0; }
@MyCheckForNull
diff --git a/java-checks-test-sources/default/src/main/java/checks/ReturnEmptyArrayNotNullCheckSample.java b/java-checks-test-sources/default/src/main/java/checks/ReturnEmptyArrayNotNullCheckSample.java
index 2bd8d0d7b88..6a31411bbb3 100644
--- a/java-checks-test-sources/default/src/main/java/checks/ReturnEmptyArrayNotNullCheckSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/ReturnEmptyArrayNotNullCheckSample.java
@@ -118,7 +118,11 @@ public Object bar() {
public int[] fool() {
return null;
}
-
+
+ public int @org.jspecify.annotations.Nullable [] fool_jspecify() {
+ return null;
+ }
+
@CheckForNull
public int[] bark() {
return null;
diff --git a/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/noPackageInfo/ChangeMethodContractCheck.java b/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/noPackageInfo/ChangeMethodContractCheck.java
index 404e7e1c924..6523626c3b0 100644
--- a/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/noPackageInfo/ChangeMethodContractCheck.java
+++ b/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/noPackageInfo/ChangeMethodContractCheck.java
@@ -1,6 +1,7 @@
package checks.S2638_ChangeMethodContractCheck.noPackageInfo;
import javax.annotation.meta.When;
+import java.util.List;
/**
* For parameters:
@@ -21,6 +22,10 @@ class ChangeMethodContractCheck {
void argAnnotatedWeakNullable(@javax.annotation.Nullable Object a) { }
void argAnnotatedStrongNullable(@javax.annotation.CheckForNull Object a) { }
+ void argAnnotatedNullableJSpecify(@org.jspecify.annotations.Nullable Object a) { }
+ void typeArgAnnotatedNullableJSpecify(List<@org.jspecify.annotations.Nullable String> a) { }
+ void argAnnotatedNonNullJSpecify(@org.jspecify.annotations.NonNull Object a) { }
+ void typeArgAnnotatedNonNullJSpecify(List<@org.jspecify.annotations.NonNull String> a) { }
void argAnnotatedNonNull(@javax.annotation.Nonnull Object a, @javax.annotation.Nonnull Object b) { }
@javax.annotation.Nullable
@@ -30,6 +35,16 @@ void argAnnotatedNonNull(@javax.annotation.Nonnull Object a, @javax.annotation.N
@javax.annotation.Nonnull
//^^^^^^^^^^^^^^^^^^^^^^^^^>
String annotatedNonNull(Object a) { return ""; }
+
+ @org.jspecify.annotations.Nullable
+ String annotatedNullableJSpecify(Object a) { return "null"; }
+
+ @org.jspecify.annotations.NonNull
+ String annotatedNonNullJSpecify(Object a) { return "null"; }
+
+ List<@org.jspecify.annotations.Nullable String> typeAnnotatedNullableJSpecify(Object a) { return List.of(); }
+
+ List<@org.jspecify.annotations.NonNull String> typeAnnotatedNonNullJSpecify(Object a) { return List.of(); }
}
class ChangeMethodContractCheck_B extends ChangeMethodContractCheck {
@@ -39,7 +54,19 @@ void argAnnotatedWeakNullable(@javax.annotation.CheckForNull Object a) { } // Co
@Override
void argAnnotatedStrongNullable(@javax.annotation.Nullable Object a) { } // Compliant: Weak instead of Strong Nullable is accepted.
- // For arguments: if you call the the method from the parent but the child is actually used, the caller will be force to give non-null argument
+ @Override
+ void argAnnotatedNullableJSpecify(@org.jspecify.annotations.NonNull Object a) { } // Noncompliant
+
+ @Override
+ void typeArgAnnotatedNullableJSpecify(List<@org.jspecify.annotations.NonNull String> a) { } // Noncompliant
+
+ @Override
+ void argAnnotatedNonNullJSpecify(@org.jspecify.annotations.Nullable Object a) { } // Compliant
+
+ @Override
+ void typeArgAnnotatedNonNullJSpecify(List<@org.jspecify.annotations.Nullable String> a) { } // Compliant
+
+ // For arguments: if you call the method from the parent but the child is actually used, the caller will be force to give non-null argument
// despite the fact that the implementation would accept null. It is not armful, therefore, NonNull to Strong/Weak Nullable is compliant.
@Override
void argAnnotatedNonNull(@javax.annotation.CheckForNull Object a, @javax.annotation.Nullable Object b) { } // Compliant
@@ -56,6 +83,18 @@ void argAnnotatedNonNull(@javax.annotation.CheckForNull Object a, @javax.annotat
@javax.annotation.CheckForNull // Compliant: unrelated method.
void unrelatedMethod(Object a) { }
+ @Override
+ @org.jspecify.annotations.NonNull
+ String annotatedNullableJSpecify(Object a) { return "null"; } // Compliant: Nonnull to Nullable is fine.
+
+ @Override
+ @org.jspecify.annotations.Nullable
+ String annotatedNonNullJSpecify(Object a) { return "null"; } // Noncompliant
+
+ List<@org.jspecify.annotations.NonNull String> typeAnnotatedNullableJSpecify(Object a) { return List.of(); } // Compliant
+
+ List<@org.jspecify.annotations.Nullable String> typeAnnotatedNonNullJSpecify(Object a) { return List.of(); } // Noncompliant
+
public boolean equals(Object o) { return false; } // Compliant: no nullable annotation
}
@@ -80,7 +119,7 @@ void argAnnotatedNonNull(@javax.annotation.Nonnull Object a, @javax.validation.c
String annotatedNonNull(Object a) { return null; } // Noncompliant {{Fix the incompatibility of the annotation @Nullable to honor @Nonnull of the overridden method.}}
//^^^^^^
- public boolean equals(@javax.annotation.Nonnull Object o) { return false; } // Compliant, handled by by S4454.
+ public boolean equals(@javax.annotation.Nonnull Object o) { return false; } // Compliant, handled by S4454.
}
/**
diff --git a/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/nonNullApi/ChangeMethodContractCheck.java b/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/nonNullApi/ChangeMethodContractCheck.java
index 732bae38aa8..623fa395ed5 100644
--- a/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/nonNullApi/ChangeMethodContractCheck.java
+++ b/java-checks-test-sources/default/src/main/java/checks/S2638_ChangeMethodContractCheck/nonNullApi/ChangeMethodContractCheck.java
@@ -25,6 +25,7 @@ void argAnnotatedNonNullViaPackageAnnotation(@javax.annotation.CheckForNull Obje
//^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^>
class ChangeMethodContractCheckAtClassLevel {
void argAnnotatedNonNullViaClassAnnotation(Object a) { }
+ Object argAnnotatedNonNullViaClassAnnotation_jspecify(Object a) { return new Object(); }
}
class ChangeMethodContractCheckAtClassLevel_Child extends ChangeMethodContractCheckAtClassLevel {
@@ -33,4 +34,8 @@ class ChangeMethodContractCheckAtClassLevel_Child extends ChangeMethodContractCh
@Override
void argAnnotatedNonNullViaClassAnnotation(Object a) { } // Noncompliant {{Fix the incompatibility of the annotation @Nullable to honor @NonNullByDefault at class level of the overridden method.}}
//^^^^
+
+ @org.jspecify.annotations.Nullable
+ @Override
+ Object argAnnotatedNonNullViaClassAnnotation_jspecify(Object a) { return null; } // Noncompliant
}
diff --git a/java-checks-test-sources/default/src/main/java/checks/spring/NullableInjectedFieldsHaveDefaultValueSample.java b/java-checks-test-sources/default/src/main/java/checks/spring/NullableInjectedFieldsHaveDefaultValueSample.java
index 03b81bc499e..5b8f903a91d 100644
--- a/java-checks-test-sources/default/src/main/java/checks/spring/NullableInjectedFieldsHaveDefaultValueSample.java
+++ b/java-checks-test-sources/default/src/main/java/checks/spring/NullableInjectedFieldsHaveDefaultValueSample.java
@@ -114,4 +114,8 @@ private String setNothingWithTwoParameters(@Nullable String a, String b) {
a = b;
return a;
}
+
+ @org.jspecify.annotations.Nullable
+ @Value("${my.property_jspecify}") // Noncompliant {{Provide a default null value for this field.}} [[sc=3;ec=27;secondary=-1]]
+ private String myProperty_jspecify;
}
diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java
index 9d9e48108bd..e6c0f3a7f96 100644
--- a/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java
+++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbol.java
@@ -19,6 +19,7 @@
*/
package org.sonar.java.model;
+import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
@@ -371,7 +372,7 @@ private SymbolMetadata convertMetadata() {
return new JSymbolMetadata(
sema,
this,
- type == null ? new IAnnotationBinding[0] : type.getTypeAnnotations(),
+ type == null ? new IAnnotationBinding[0] : getAnnotations(type),
binding.getAnnotations());
case IBinding.METHOD:
ITypeBinding returnType = ((IMethodBinding) binding).getReturnType();
@@ -379,16 +380,21 @@ private SymbolMetadata convertMetadata() {
if (returnType == null) {
return Symbols.EMPTY_METADATA;
}
- return new JSymbolMetadata(
- sema,
- this,
- returnType.getTypeAnnotations(),
- binding.getAnnotations());
+ return new JSymbolMetadata(sema, this, getAnnotations(returnType), binding.getAnnotations());
default:
return new JSymbolMetadata(sema, this, binding.getAnnotations());
}
}
+ private static IAnnotationBinding[] getAnnotations(ITypeBinding type) {
+ List iAnnotationBindings = new ArrayList<>();
+ for (ITypeBinding typeArgument : type.getTypeArguments()) {
+ Collections.addAll(iAnnotationBindings, typeArgument.getTypeAnnotations());
+ }
+ Collections.addAll(iAnnotationBindings, type.getTypeAnnotations());
+ return iAnnotationBindings.toArray(new IAnnotationBinding[0]);
+ }
+
/**
* @see #owner()
*/
diff --git a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java
index dbad5bd899d..058cd002efd 100644
--- a/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java
+++ b/java-frontend/src/main/java/org/sonar/java/model/JSymbolMetadataNullabilityHelper.java
@@ -77,7 +77,8 @@ private JSymbolMetadataNullabilityHelper() {
// From the documentation (https://wiki.eclipse.org/JDT_Core/Null_Analysis):
// For any variable whose type is annotated with @Nullable [...] It is illegal to dereference such a variable for either field or method access.
"org.eclipse.jdt.annotation.Nullable",
- "org.eclipse.jgit.annotations.Nullable");
+ "org.eclipse.jgit.annotations.Nullable",
+ "org.jspecify.annotations.Nullable");
/**
* List of "weak" annotations, when something can be null, but it may be fine to not check it.
@@ -133,7 +134,8 @@ private JSymbolMetadataNullabilityHelper() {
"org.jmlspecs.annotation.NonNull",
"org.netbeans.api.annotations.common.NonNull",
"org.springframework.lang.NonNull",
- "reactor.util.annotation.NonNull");
+ "reactor.util.annotation.NonNull",
+ "org.jspecify.annotations.NonNull");
/**
* Can have different type depending on the argument "when" value:
diff --git a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java
index bf5b2a36c52..2ab6cbee747 100644
--- a/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java
+++ b/java-frontend/src/test/java/org/sonar/java/model/JSymbolTest.java
@@ -19,6 +19,11 @@
*/
package org.sonar.java.model;
+import org.eclipse.jdt.core.dom.AST;
+import org.eclipse.jdt.core.dom.ASTParser;
+import org.eclipse.jdt.core.dom.CompilationUnit;
+import org.eclipse.jdt.core.dom.IBinding;
+import org.eclipse.jdt.core.dom.IVariableBinding;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
@@ -28,6 +33,7 @@
import org.sonar.java.model.declaration.VariableTreeImpl;
import org.sonar.java.model.statement.BlockTreeImpl;
import org.sonar.plugins.java.api.semantic.Symbol;
+import org.sonar.plugins.java.api.semantic.SymbolMetadata;
import org.sonar.plugins.java.api.semantic.Type;
import org.sonar.plugins.java.api.tree.ClassTree;
import org.sonar.plugins.java.api.tree.ExpressionStatementTree;
@@ -39,6 +45,8 @@
import org.sonar.plugins.java.api.tree.VariableTree;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
import static org.sonar.java.model.assertions.SymbolAssert.assertThat;
class JSymbolTest {
@@ -371,6 +379,30 @@ void var_type_as_lambda_parameters() {
assertThat(y.symbol()).isOfType("java.lang.Boolean");
}
+ @Test
+ void test_with_type_annotation() {
+ JavaTree.CompilationUnitTreeImpl cu = test("class C { void m(List<@Nullable String> p) { } }");
+ ClassTreeImpl c = (ClassTreeImpl) cu.types().get(0);
+ MethodTreeImpl m = (MethodTreeImpl) c.members().get(0);
+ Symbol.MethodSymbol declarationSymbol = m.symbol();
+ SymbolMetadata metadata = declarationSymbol.metadata();
+ assertThat(metadata).isNotNull();
+ SymbolMetadata parameterMetadata = m.parameters().get(0).symbol().metadata();
+ assertThat(parameterMetadata.annotations()).hasSize(1);
+ }
+
+ @Test
+ void test_with_type_null() {
+ ASTParser astParser = ASTParser.newParser(AST.getJLSLatest());
+ astParser.setSource("class C { void m(String p) { } }".toCharArray());
+ CompilationUnit cu = (CompilationUnit) astParser.createAST(null);
+ JSema jSema = new JSema(cu.getAST());
+ IVariableBinding mock = mock(IVariableBinding.class);
+ when(mock.getKind()).thenReturn(IBinding.VARIABLE);
+ JVariableSymbol jVariableSymbol = new JVariableSymbol(jSema, mock);
+ assertThat(jVariableSymbol.metadata()).isNotNull();
+ }
+
@Nested
class kinds {