mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Basic test-suite for codeanalysis-annotations
GitOrigin-RevId: a134aead43faf095ac3ccec4fb24af23c8f241a7
This commit is contained in:
committed by
intellij-monorepo-bot
parent
1ecf89dcbf
commit
71300a36eb
+47
-7
@@ -7,11 +7,13 @@ import com.intellij.codeInsight.daemon.GroupNames;
|
||||
import com.intellij.codeInsight.intention.AddAnnotationPsiFix;
|
||||
import com.intellij.codeInsight.intention.impl.BaseIntentionAction;
|
||||
import com.intellij.codeInspection.*;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInstructionVisitor.ConstantResult;
|
||||
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind.NullabilityProblem;
|
||||
import com.intellij.codeInspection.dataFlow.fix.*;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.InstanceofInstruction;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.Instruction;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfType;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfTypes;
|
||||
import com.intellij.codeInspection.dataFlow.value.DfaValue;
|
||||
import com.intellij.codeInspection.nullable.NullableStuffInspectionBase;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
@@ -504,15 +506,15 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return null;
|
||||
}
|
||||
|
||||
private void reportNullabilityProblems(ProblemReporter reporter,
|
||||
List<NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, ConstantResult> expressions) {
|
||||
protected void reportNullabilityProblems(ProblemReporter reporter,
|
||||
List<NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, ConstantResult> expressions) {
|
||||
for (NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
if (!REPORT_UNSOUND_WARNINGS) {
|
||||
if (expression == null) continue;
|
||||
PsiExpression unwrapped = PsiUtil.skipParenthesizedExprDown(expression);
|
||||
if (!ExpressionUtils.isNullLiteral(unwrapped) && expressions.get(expression) != DataFlowInstructionVisitor.ConstantResult.NULL) {
|
||||
if (!ExpressionUtils.isNullLiteral(unwrapped) && expressions.get(expression) != ConstantResult.NULL) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@@ -1136,10 +1138,48 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
return SHORT_NAME;
|
||||
}
|
||||
|
||||
protected enum ConstantResult {
|
||||
TRUE, FALSE, NULL, UNKNOWN;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.toLowerCase(name());
|
||||
}
|
||||
|
||||
public Object value() {
|
||||
switch (this) {
|
||||
case TRUE:
|
||||
return Boolean.TRUE;
|
||||
case FALSE:
|
||||
return Boolean.FALSE;
|
||||
case NULL:
|
||||
return null;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ConstantResult fromDfType(@NotNull DfType dfType) {
|
||||
if (dfType == DfTypes.NULL) return NULL;
|
||||
if (dfType == DfTypes.TRUE) return TRUE;
|
||||
if (dfType == DfTypes.FALSE) return FALSE;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ConstantResult mergeValue(@Nullable ConstantResult state, @NotNull DfaMemoryState memState, @Nullable DfaValue value) {
|
||||
if (state == UNKNOWN || value == null) return UNKNOWN;
|
||||
ConstantResult nextState = fromDfType(memState.getUnboxedDfType(value));
|
||||
return state == null || state == nextState ? nextState : UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link ProblemsHolder} wrapper to avoid reporting two problems on the same anchor
|
||||
*/
|
||||
private static class ProblemReporter {
|
||||
protected static class ProblemReporter {
|
||||
private final Set<PsiElement> myReportedAnchors = new HashSet<>();
|
||||
private final ProblemsHolder myHolder;
|
||||
private final PsiElement myScope;
|
||||
@@ -1149,7 +1189,7 @@ public abstract class DataFlowInspectionBase extends AbstractBaseJavaLocalInspec
|
||||
myScope = scope;
|
||||
}
|
||||
|
||||
void registerProblem(PsiElement element, String message, LocalQuickFix... fixes) {
|
||||
public void registerProblem(PsiElement element, String message, LocalQuickFix... fixes) {
|
||||
if (register(element)) {
|
||||
myHolder.registerProblem(element, message, fixes);
|
||||
}
|
||||
|
||||
+1
-40
@@ -1,10 +1,10 @@
|
||||
// Copyright 2000-2019 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.codeInspection.dataFlow;
|
||||
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspectionBase.ConstantResult;
|
||||
import com.intellij.codeInspection.dataFlow.instructions.*;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfConstantType;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfType;
|
||||
import com.intellij.codeInspection.dataFlow.types.DfTypes;
|
||||
import com.intellij.codeInspection.dataFlow.value.*;
|
||||
import com.intellij.codeInspection.util.OptionalUtil;
|
||||
import com.intellij.openapi.application.Application;
|
||||
@@ -12,7 +12,6 @@ import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.openapi.util.TextRange;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.impl.PsiImplUtil;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
@@ -387,44 +386,6 @@ final class DataFlowInstructionVisitor extends StandardInstructionVisitor {
|
||||
}
|
||||
}
|
||||
|
||||
enum ConstantResult {
|
||||
TRUE, FALSE, NULL, UNKNOWN;
|
||||
|
||||
@NotNull
|
||||
@Override
|
||||
public String toString() {
|
||||
return StringUtil.toLowerCase(name());
|
||||
}
|
||||
|
||||
public Object value() {
|
||||
switch (this) {
|
||||
case TRUE:
|
||||
return Boolean.TRUE;
|
||||
case FALSE:
|
||||
return Boolean.FALSE;
|
||||
case NULL:
|
||||
return null;
|
||||
default:
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ConstantResult fromDfType(@NotNull DfType dfType) {
|
||||
if (dfType == DfTypes.NULL) return NULL;
|
||||
if (dfType == DfTypes.TRUE) return TRUE;
|
||||
if (dfType == DfTypes.FALSE) return FALSE;
|
||||
return UNKNOWN;
|
||||
}
|
||||
|
||||
@NotNull
|
||||
static ConstantResult mergeValue(@Nullable ConstantResult state, @NotNull DfaMemoryState memState, @Nullable DfaValue value) {
|
||||
if (state == UNKNOWN || value == null) return UNKNOWN;
|
||||
ConstantResult nextState = fromDfType(memState.getUnboxedDfType(value));
|
||||
return state == null || state == nextState ? nextState : UNKNOWN;
|
||||
}
|
||||
}
|
||||
|
||||
static class ExpressionChunk {
|
||||
final @NotNull PsiExpression myExpression;
|
||||
final @Nullable TextRange myRange;
|
||||
|
||||
+3
-3
@@ -179,7 +179,7 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
else if (parent instanceof PsiReturnStatement) {
|
||||
targetType = PsiTypesUtil.getMethodReturnType(parent);
|
||||
}
|
||||
if (targetType != null && !PsiType.VOID.equals(targetType)) {
|
||||
if (targetType != null && !PsiType.VOID.equals(targetType) && DfaPsiUtil.getTypeNullability(targetType) != Nullability.NULLABLE) {
|
||||
if (TypeConversionUtil.isPrimitiveAndNotNull(targetType)) {
|
||||
return createUnboxingProblem(context, expression);
|
||||
}
|
||||
@@ -509,13 +509,13 @@ public class NullabilityProblemKind<T extends PsiElement> {
|
||||
}
|
||||
|
||||
@NotNull
|
||||
public String getMessage(Map<PsiExpression, DataFlowInstructionVisitor.ConstantResult> expressions) {
|
||||
public String getMessage(Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
if (myKind.myAlwaysNullMessage == null || myKind.myNormalMessage == null) {
|
||||
throw new IllegalStateException("This problem kind has no message associated: " + myKind);
|
||||
}
|
||||
PsiExpression expression = PsiUtil.skipParenthesizedExprDown(getDereferencedExpression());
|
||||
if (expression != null) {
|
||||
if (ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInstructionVisitor.ConstantResult.NULL) {
|
||||
if (ExpressionUtils.isNullLiteral(expression) || expressions.get(expression) == DataFlowInspectionBase.ConstantResult.NULL) {
|
||||
return myKind.myAlwaysNullMessage;
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -4,6 +4,7 @@ package com.intellij.codeInsight.annoPackages;
|
||||
import com.intellij.codeInsight.Nullability;
|
||||
import com.intellij.codeInsight.NullabilityAnnotationInfo;
|
||||
import com.intellij.codeInsight.NullableNotNullManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.psi.PsiAnnotation;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -48,7 +49,7 @@ public interface AnnotationPackageSupport {
|
||||
new JetBrainsAnnotationSupport(), new FindBugsAnnotationSupport(), new AndroidAnnotationSupport(),
|
||||
new Jsr305Support(manager), new CheckerFrameworkSupport()
|
||||
};
|
||||
if (CodeAnalysisAnnotationSupport.IS_AVAILABLE) {
|
||||
if (CodeAnalysisAnnotationSupport.IS_AVAILABLE || ApplicationManager.getApplication().isUnitTestMode()) {
|
||||
return ArrayUtil.append(extensions, new CodeAnalysisAnnotationSupport());
|
||||
}
|
||||
return extensions;
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
import codeanalysis.experimental.annotations.*;
|
||||
|
||||
class X {
|
||||
int f;
|
||||
|
||||
void test(@Nullable X x) {
|
||||
m(/*ca-nullable-to-not-null*/x);
|
||||
}
|
||||
|
||||
native void m(@NotNull X x);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import codeanalysis.experimental.annotations.*;
|
||||
|
||||
class X {
|
||||
int f;
|
||||
|
||||
void test(@Nullable X x) {
|
||||
/*ca-nullable-to-not-null*/x.f = 1;
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
import codeanalysis.experimental.annotations.DefaultNotNull;
|
||||
|
||||
@DefaultNotNull
|
||||
class X {
|
||||
X get() {
|
||||
return /*ca-nullable-to-not-null*/null;
|
||||
}
|
||||
}
|
||||
+13
@@ -0,0 +1,13 @@
|
||||
import codeanalysis.experimental.annotations.DefaultNotNull;
|
||||
import codeanalysis.experimental.annotations.Nullable;
|
||||
|
||||
@DefaultNotNull
|
||||
class NullnessDemo {
|
||||
@Nullable Object something() {
|
||||
return null;
|
||||
}
|
||||
|
||||
void foo() {
|
||||
Object o = something();
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
import codeanalysis.experimental.annotations.DefaultNotNull;
|
||||
|
||||
@DefaultNotNull
|
||||
class X {
|
||||
void m(X x) {}
|
||||
|
||||
void use() {
|
||||
m(/*ca-nullable-to-not-null*/null);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
class X {
|
||||
int f;
|
||||
|
||||
void test(X x) {
|
||||
m(x);
|
||||
}
|
||||
|
||||
native void m(X x);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import codeanalysis.experimental.annotations.*;
|
||||
|
||||
class X {
|
||||
int f;
|
||||
|
||||
void test(X x) {
|
||||
x.f = 1;
|
||||
}
|
||||
}
|
||||
+110
@@ -0,0 +1,110 @@
|
||||
// Copyright 2000-2020 JetBrains s.r.o. Use of this source code is governed by the Apache 2.0 license that can be found in the LICENSE file.
|
||||
package com.intellij.java.codeInspection;
|
||||
|
||||
import com.intellij.JavaTestUtil;
|
||||
import com.intellij.codeInspection.ProblemsHolder;
|
||||
import com.intellij.codeInspection.dataFlow.DataFlowInspectionBase;
|
||||
import com.intellij.codeInspection.dataFlow.NullabilityProblemKind;
|
||||
import com.intellij.codeInspection.ex.InspectionManagerEx;
|
||||
import com.intellij.openapi.application.ReadAction;
|
||||
import com.intellij.openapi.vfs.CharsetToolkit;
|
||||
import com.intellij.psi.PsiElement;
|
||||
import com.intellij.psi.PsiElementVisitor;
|
||||
import com.intellij.psi.PsiExpression;
|
||||
import com.intellij.psi.PsiFile;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.rt.execution.junit.FileComparisonFailure;
|
||||
import com.intellij.testFramework.fixtures.LightJavaCodeInsightFixtureTestCase;
|
||||
import one.util.streamex.EntryStream;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.junit.runners.Parameterized;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@RunWith(Parameterized.class)
|
||||
public class CodeAnalysisAnnotationsTest extends LightJavaCodeInsightFixtureTestCase {
|
||||
private static final String PACKAGE_NAME = "codeanalysis.experimental.annotations";
|
||||
private static final Path PATH = Paths.get(JavaTestUtil.getJavaTestDataPath(), "/inspection/dataFlow/codeanalysis/");
|
||||
@Parameterized.Parameter
|
||||
public String myFileName;
|
||||
|
||||
@Parameterized.Parameters(name = "{0}")
|
||||
public static List<String> getData() throws IOException {
|
||||
return Files.walk(PATH).filter(Files::isRegularFile).map(PATH::relativize).map(Path::toString).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
mockAnnotations();
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
private void mockAnnotations() {
|
||||
String template = "package " + PACKAGE_NAME + ";import java.lang.annotation.*;\n\n@Target(%s)public @interface %s {}";
|
||||
myFixture.addClass(String.format(Locale.ROOT, template, "ElementType.TYPE_USE", "NotNull"));
|
||||
myFixture.addClass(String.format(Locale.ROOT, template, "ElementType.TYPE_USE", "Nullable"));
|
||||
myFixture.addClass(String.format(Locale.ROOT, template, "ElementType.TYPE", "DefaultNotNull"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void test() throws IOException {
|
||||
Path path = PATH.resolve(myFileName);
|
||||
String fileText = new String(Files.readAllBytes(path), CharsetToolkit.UTF8_CHARSET).replace("\r\n", "\n");
|
||||
String stripped = fileText.replaceAll("/\\*ca-[a-z\\-]+\\*/", "");
|
||||
PsiFile file = myFixture.configureByText(path.getFileName().toString(), stripped);
|
||||
|
||||
CodeAnalysisDataFlowInspection inspection = new CodeAnalysisDataFlowInspection();
|
||||
ReadAction.run(() -> {
|
||||
PsiElementVisitor visitor = inspection.buildVisitor(new ProblemsHolder(new InspectionManagerEx(getProject()), file, false), false);
|
||||
PsiTreeUtil.processElements(file, e -> {
|
||||
e.accept(visitor);
|
||||
return true;
|
||||
});
|
||||
String actualText = inspection.getActualText(stripped);
|
||||
if (!fileText.equals(actualText)) {
|
||||
throw new FileComparisonFailure("Messages don't match", fileText, actualText, path.toString());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Reports dataflow problems in code-analysis-conformant way
|
||||
private static class CodeAnalysisDataFlowInspection extends DataFlowInspectionBase {
|
||||
private final Map<PsiElement, String> actual = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
protected void reportNullabilityProblems(DataFlowInspectionBase.ProblemReporter reporter,
|
||||
List<NullabilityProblemKind.NullabilityProblem<?>> problems,
|
||||
Map<PsiExpression, DataFlowInspectionBase.ConstantResult> expressions) {
|
||||
for (NullabilityProblemKind.NullabilityProblem<?> problem : problems) {
|
||||
PsiExpression expression = problem.getDereferencedExpression();
|
||||
if (expression != null) {
|
||||
actual.put(expression, "ca-nullable-to-not-null");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String getActualText(String stripped) {
|
||||
Map<Integer, String> map = EntryStream.of(this.actual)
|
||||
.mapKeys(e -> e.getTextRange().getStartOffset())
|
||||
.mapValues(v -> "/*" + v + "*/")
|
||||
.grouping(Collectors.joining());
|
||||
return StreamEx.ofKeys(map).prepend(0).append(stripped.length())
|
||||
.pairMap((prev, next) -> stripped.substring(prev, next) + map.getOrDefault(next, ""))
|
||||
.joining();
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user