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 {