mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
infer nullable/not null (initial)
This commit is contained in:
+130
@@ -0,0 +1,130 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.inferNullity;
|
||||
|
||||
import com.intellij.analysis.AnalysisScope;
|
||||
import com.intellij.analysis.BaseAnalysisAction;
|
||||
import com.intellij.analysis.BaseAnalysisActionDialog;
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.openapi.application.Result;
|
||||
import com.intellij.openapi.command.WriteCommandAction;
|
||||
import com.intellij.openapi.progress.ProgressIndicator;
|
||||
import com.intellij.openapi.progress.ProgressManager;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.project.ProjectUtil;
|
||||
import com.intellij.openapi.ui.Messages;
|
||||
import com.intellij.openapi.ui.VerticalFlowLayout;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.pom.java.LanguageLevel;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.GlobalSearchScope;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.ui.TitledSeparator;
|
||||
import org.jetbrains.annotations.NonNls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
|
||||
public class InferNullityAnnotationsAction extends BaseAnalysisAction {
|
||||
@NonNls private static final String INFER_NULLITY_ANNOTATIONS = "Infer Nullity Annotations";
|
||||
private JCheckBox myAnnotateLocalVariablesCb;
|
||||
|
||||
public InferNullityAnnotationsAction() {
|
||||
super("Infer nullity", INFER_NULLITY_ANNOTATIONS);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void analyze(@NotNull final Project project, final AnalysisScope scope) {
|
||||
final PsiClass annotationClass = JavaPsiFacade.getInstance(project).findClass(AnnotationUtil.NULLABLE, GlobalSearchScope.allScope(project));
|
||||
if (annotationClass == null) {
|
||||
Messages.showErrorDialog(project, "Infer Nullity Annotations requires that the JetBrains nullity annotations" +
|
||||
" be available to your project.\n\nYou will need to add annotations.jar (available in your IDEA distribution) as a library. " +
|
||||
" The IDEA nullity annotations are freely usable and redistributable under the Apache 2.0 license.",
|
||||
INFER_NULLITY_ANNOTATIONS);
|
||||
return;
|
||||
}
|
||||
final LanguageLevel languageLevel = PsiUtil.getLanguageLevel(annotationClass);
|
||||
if (languageLevel.compareTo(LanguageLevel.JDK_1_5) < 0) {
|
||||
Messages.showErrorDialog(project, "Infer Nullity Annotations requires the project language level be set to 1.5 or greater.",
|
||||
INFER_NULLITY_ANNOTATIONS);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (scope.checkScopeWritable(project)) return;
|
||||
final NullityInferrer inferrer = new NullityInferrer(myAnnotateLocalVariablesCb.isSelected(), project);
|
||||
|
||||
|
||||
final ProgressManager progressManager = ProgressManager.getInstance();
|
||||
final int totalFiles = scope.getFileCount();
|
||||
final ProgressIndicator indicator = ProgressManager.getInstance().getProgressIndicator();
|
||||
|
||||
if (indicator != null) {
|
||||
indicator.setText(INFER_NULLITY_ANNOTATIONS);
|
||||
}
|
||||
if (progressManager.runProcessWithProgressSynchronously(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
scope.accept(new PsiElementVisitor() {
|
||||
int myFileCount = 0;
|
||||
@Override
|
||||
public void visitFile(final PsiFile file) {
|
||||
myFileCount++;
|
||||
if (indicator != null) {
|
||||
final VirtualFile virtualFile = file.getVirtualFile();
|
||||
if (virtualFile != null) {
|
||||
indicator.setText2(ProjectUtil.calcRelativeToProjectPath(virtualFile, project));
|
||||
}
|
||||
indicator.setFraction(((double)myFileCount) / totalFiles);
|
||||
}
|
||||
if (file instanceof PsiJavaFile) {
|
||||
inferrer.collect(file);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, INFER_NULLITY_ANNOTATIONS, true, project)) return;
|
||||
|
||||
final Runnable applyRunnable = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
new WriteCommandAction(project, INFER_NULLITY_ANNOTATIONS) {
|
||||
@Override
|
||||
protected void run(Result result) throws Throwable {
|
||||
inferrer.apply(project);
|
||||
}
|
||||
}.execute();
|
||||
}
|
||||
};
|
||||
SwingUtilities.invokeLater(applyRunnable);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JComponent getAdditionalActionSettings(Project project, BaseAnalysisActionDialog dialog) {
|
||||
final JPanel panel = new JPanel(new VerticalFlowLayout());
|
||||
panel.add(new TitledSeparator());
|
||||
myAnnotateLocalVariablesCb = new JCheckBox("Annotate local variables", false);
|
||||
panel.add(myAnnotateLocalVariablesCb);
|
||||
return panel;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void canceled() {
|
||||
super.canceled();
|
||||
myAnnotateLocalVariablesCb = null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,555 @@
|
||||
/*
|
||||
* Copyright 2000-2010 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.intellij.codeInspection.inferNullity;
|
||||
|
||||
import com.intellij.codeInsight.AnnotationUtil;
|
||||
import com.intellij.codeInsight.intention.AddAnnotationFix;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.psi.*;
|
||||
import com.intellij.psi.search.LocalSearchScope;
|
||||
import com.intellij.psi.search.searches.OverridingMethodsSearch;
|
||||
import com.intellij.psi.search.searches.ReferencesSearch;
|
||||
import com.intellij.psi.util.PsiTreeUtil;
|
||||
import com.intellij.psi.util.PsiUtil;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.Query;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
class NullityInferrer {
|
||||
private static final Logger LOG = Logger.getInstance("#" + NullityInferrer.class.getName());
|
||||
|
||||
private static final int MAX_PASSES = 10;
|
||||
private int numAnnotationsAdded = 0;
|
||||
private final HashSet<SmartPsiElementPointer<? extends PsiModifierListOwner>> myNotNullSet =
|
||||
new HashSet<SmartPsiElementPointer<? extends PsiModifierListOwner>>();
|
||||
private final HashSet<SmartPsiElementPointer<? extends PsiModifierListOwner>> myNullableSet =
|
||||
new HashSet<SmartPsiElementPointer<? extends PsiModifierListOwner>>();
|
||||
private boolean myAnnotateLocalVariables;
|
||||
private SmartPointerManager myPointerManager;
|
||||
|
||||
|
||||
public NullityInferrer(boolean annotateLocalVariables, Project project) {
|
||||
myAnnotateLocalVariables = annotateLocalVariables;
|
||||
myPointerManager = SmartPointerManager.getInstance(project);
|
||||
}
|
||||
|
||||
private boolean expressionIsNeverNull(@Nullable PsiExpression expression) {
|
||||
if (expression == null) {
|
||||
return false;
|
||||
}
|
||||
final ExpressionIsNeverNullVisitor visitor = new ExpressionIsNeverNullVisitor();
|
||||
expression.accept(visitor);
|
||||
return visitor.isNeverNull();
|
||||
}
|
||||
|
||||
protected boolean expressionIsSometimesNull(@Nullable PsiExpression expression) {
|
||||
if (expression == null) {
|
||||
return false;
|
||||
}
|
||||
final ExpressionIsSometimesNullVisitor visitor = new ExpressionIsSometimesNullVisitor();
|
||||
expression.accept(visitor);
|
||||
return visitor.isSometimesNull();
|
||||
}
|
||||
|
||||
private boolean methodNeverReturnsNull(@NotNull PsiMethod method) {
|
||||
final MethodNeverReturnsNullVisitor visitor = new MethodNeverReturnsNullVisitor();
|
||||
method.accept(visitor);
|
||||
return visitor.getNeverReturnsNull();
|
||||
}
|
||||
|
||||
private boolean variableNeverAssignedNull(@NotNull PsiVariable variable) {
|
||||
final PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null) {
|
||||
if (!expressionIsNeverNull(initializer)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
else if (!variable.hasModifierProperty(PsiModifier.FINAL)) {
|
||||
return false;
|
||||
}
|
||||
final Query<PsiReference> references = ReferencesSearch.search(variable);
|
||||
for (final PsiReference reference : references) {
|
||||
final PsiElement element = reference.getElement();
|
||||
if (!(element instanceof PsiReferenceExpression)) {
|
||||
continue;
|
||||
}
|
||||
final PsiElement parent = element.getParent();
|
||||
if (!(parent instanceof PsiAssignmentExpression)) {
|
||||
continue;
|
||||
}
|
||||
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
|
||||
if (assignment.getLExpression().equals(element) &&
|
||||
!expressionIsNeverNull(assignment.getRExpression())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private boolean variableSometimesAssignedNull(@NotNull PsiVariable variable) {
|
||||
final PsiExpression initializer = variable.getInitializer();
|
||||
if (initializer != null && expressionIsSometimesNull(initializer)) {
|
||||
return true;
|
||||
}
|
||||
final Query<PsiReference> references = ReferencesSearch.search(variable);
|
||||
for (final PsiReference reference : references) {
|
||||
final PsiElement element = reference.getElement();
|
||||
if (!(element instanceof PsiReferenceExpression)) {
|
||||
continue;
|
||||
}
|
||||
final PsiElement parent = element.getParent();
|
||||
if (!(parent instanceof PsiAssignmentExpression)) {
|
||||
continue;
|
||||
}
|
||||
final PsiAssignmentExpression assignment = (PsiAssignmentExpression)parent;
|
||||
if (assignment.getLExpression().equals(element) && expressionIsSometimesNull(assignment.getRExpression())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public void collect(@NotNull PsiFile file) {
|
||||
int prevNumAnnotationsAdded;
|
||||
int pass = 0;
|
||||
do {
|
||||
final JavaRecursiveElementVisitor visitor = new NullityInferrerVisitor();
|
||||
prevNumAnnotationsAdded = numAnnotationsAdded;
|
||||
file.accept(visitor);
|
||||
pass++;
|
||||
}
|
||||
while (prevNumAnnotationsAdded < numAnnotationsAdded && pass < MAX_PASSES);
|
||||
}
|
||||
|
||||
public void apply(Project project) {
|
||||
for (SmartPsiElementPointer<? extends PsiModifierListOwner> pointer : myNullableSet) {
|
||||
final PsiModifierListOwner element = pointer.getElement();
|
||||
if (element != null) {
|
||||
if (!myAnnotateLocalVariables && element instanceof PsiLocalVariable) continue;
|
||||
new AddAnnotationFix(AnnotationUtil.NULLABLE, element, AnnotationUtil.NOT_NULL).invoke(project, null, element.getContainingFile());
|
||||
}
|
||||
}
|
||||
|
||||
for (SmartPsiElementPointer<? extends PsiModifierListOwner> pointer : myNotNullSet) {
|
||||
final PsiModifierListOwner element = pointer.getElement();
|
||||
if (element != null) {
|
||||
if (!myAnnotateLocalVariables && element instanceof PsiLocalVariable) continue;
|
||||
new AddAnnotationFix(AnnotationUtil.NOT_NULL, element, AnnotationUtil.NULLABLE).invoke(project, null, element.getContainingFile());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void registerNullableAnnotation(@NotNull PsiModifierListOwner method) {
|
||||
registerAnnotation(method, true);
|
||||
}
|
||||
|
||||
private void registerNotNullAnnotation(@NotNull PsiModifierListOwner method) {
|
||||
registerAnnotation(method, false);
|
||||
}
|
||||
|
||||
private void registerAnnotation(@NotNull PsiModifierListOwner method, boolean isNullable) {
|
||||
final SmartPsiElementPointer<PsiModifierListOwner> methodPointer = myPointerManager.createLazyPointer(method);
|
||||
if (isNullable) {
|
||||
myNullableSet.add(methodPointer);
|
||||
}
|
||||
else {
|
||||
myNotNullSet.add(methodPointer);
|
||||
}
|
||||
numAnnotationsAdded++;
|
||||
}
|
||||
|
||||
private class ExpressionIsNeverNullVisitor extends JavaElementVisitor {
|
||||
private boolean neverNull = true;
|
||||
|
||||
@Override
|
||||
public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) {
|
||||
neverNull = !"null".equals(expression.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
|
||||
neverNull = expressionIsNeverNull(expression.getRExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) {
|
||||
neverNull = expressionIsNeverNull(expression.getThenExpression()) &&
|
||||
expressionIsNeverNull(expression.getElseExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitParenthesizedExpression(@NotNull PsiParenthesizedExpression expression) {
|
||||
neverNull = expressionIsNeverNull(expression.getExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitArrayAccessExpression(PsiArrayAccessExpression expression) {
|
||||
neverNull = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
|
||||
final PsiElement referent = expression.resolve();
|
||||
if (referent instanceof PsiVariable) {
|
||||
final PsiVariable var = (PsiVariable)referent;
|
||||
if (myNotNullSet.contains(myPointerManager.createLazyPointer(var)) ||
|
||||
var instanceof PsiEnumConstant ||
|
||||
NullityInferrer.this.isNotNull(var)) {
|
||||
neverNull = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
neverNull = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method == null) {
|
||||
neverNull = false;
|
||||
}
|
||||
neverNull = isNotNull(method);
|
||||
}
|
||||
|
||||
private boolean isNeverNull() {
|
||||
return neverNull;
|
||||
}
|
||||
}
|
||||
|
||||
private class ExpressionIsSometimesNullVisitor extends JavaElementVisitor {
|
||||
private boolean sometimesNull = false;
|
||||
|
||||
|
||||
@Override
|
||||
public void visitLiteralExpression(@NotNull PsiLiteralExpression expression) {
|
||||
sometimesNull = "null".equals(expression.getText());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitAssignmentExpression(@NotNull PsiAssignmentExpression expression) {
|
||||
sometimesNull = expressionIsSometimesNull(expression.getRExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitConditionalExpression(@NotNull PsiConditionalExpression expression) {
|
||||
sometimesNull = expressionIsSometimesNull(expression.getThenExpression()) ||
|
||||
expressionIsSometimesNull(expression.getElseExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitParenthesizedExpression(@NotNull PsiParenthesizedExpression expression) {
|
||||
sometimesNull = expressionIsSometimesNull(expression.getExpression());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReferenceExpression(@NotNull PsiReferenceExpression expression) {
|
||||
final PsiElement referent = expression.resolve();
|
||||
if (referent instanceof PsiVariable) {
|
||||
final PsiVariable var = (PsiVariable)referent;
|
||||
if (myNullableSet.contains(myPointerManager.createLazyPointer(var)) || isNullable(var)) {
|
||||
sometimesNull = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitMethodCallExpression(@NotNull PsiMethodCallExpression expression) {
|
||||
final PsiMethod method = expression.resolveMethod();
|
||||
if (method != null) {
|
||||
sometimesNull = isNullable(method);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isSometimesNull() {
|
||||
return sometimesNull;
|
||||
}
|
||||
}
|
||||
|
||||
private class MethodNeverReturnsNullVisitor extends JavaRecursiveElementVisitor {
|
||||
private boolean neverReturnsNull = true;
|
||||
|
||||
@Override
|
||||
public void visitClass(PsiClass aClass) {
|
||||
//so as not to drill into anonymous classes
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReturnStatement(@NotNull PsiReturnStatement statement) {
|
||||
super.visitReturnStatement(statement);
|
||||
final PsiExpression value = statement.getReturnValue();
|
||||
if (expressionIsNeverNull(value)) {
|
||||
return;
|
||||
}
|
||||
if (value instanceof PsiMethodCallExpression) {
|
||||
final PsiMethodCallExpression methodCallExpression = (PsiMethodCallExpression)value;
|
||||
|
||||
//if it's a recursive call, don't throw the red flag
|
||||
final PsiMethod method = methodCallExpression.resolveMethod();
|
||||
final PsiMethod containingMethod = PsiTreeUtil.getParentOfType(value, PsiMethod.class);
|
||||
if (method != null && method.equals(containingMethod)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
neverReturnsNull = false;
|
||||
}
|
||||
|
||||
private boolean getNeverReturnsNull() {
|
||||
return neverReturnsNull;
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isNotNull(PsiModifierListOwner owner) {
|
||||
return AnnotationUtil.isNotNull(owner) || myNotNullSet.contains(myPointerManager.createLazyPointer(owner));
|
||||
}
|
||||
|
||||
private boolean isNullable(PsiModifierListOwner owner) {
|
||||
return AnnotationUtil.isNullable(owner) || myNullableSet.contains(myPointerManager.createLazyPointer(owner));
|
||||
}
|
||||
|
||||
private class NullityInferrerVisitor extends JavaRecursiveElementVisitor {
|
||||
|
||||
@Override
|
||||
public void visitMethod(@NotNull PsiMethod method) {
|
||||
super.visitMethod(method);
|
||||
if (method.isConstructor() || method.getReturnType() instanceof PsiPrimitiveType) {
|
||||
return;
|
||||
}
|
||||
final Collection<PsiMethod> overridingMethods = OverridingMethodsSearch.search(method).findAll();
|
||||
for (final PsiMethod overridingMethod : overridingMethods) {
|
||||
if (isNullable(overridingMethod)) {
|
||||
registerNullableAnnotation(method);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!AnnotationUtil.isAnnotated(method, AnnotationUtil.NOT_NULL, false) &&
|
||||
AnnotationUtil.isAnnotated(method, AnnotationUtil.NOT_NULL, true)) {
|
||||
registerNotNullAnnotation(method);
|
||||
return;
|
||||
}
|
||||
if (isNotNull(method) || isNullable(method)) {
|
||||
return;
|
||||
}
|
||||
final PsiCodeBlock body = method.getBody();
|
||||
if (body != null) {
|
||||
final boolean[] sometimesReturnsNull = new boolean[1];
|
||||
body.accept(new JavaRecursiveElementWalkingVisitor() {
|
||||
@Override
|
||||
public void visitClass(PsiClass aClass) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitElement(PsiElement element) {
|
||||
if (sometimesReturnsNull[0]) return;
|
||||
super.visitElement(element);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitReturnStatement(PsiReturnStatement statement) {
|
||||
super.visitReturnStatement(statement);
|
||||
final PsiExpression value = statement.getReturnValue();
|
||||
if (expressionIsSometimesNull(value)) {
|
||||
sometimesReturnsNull[0] = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
if (sometimesReturnsNull[0]) {
|
||||
registerNullableAnnotation(method);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
if (methodNeverReturnsNull(method)) {
|
||||
for (final PsiMethod overridingMethod : overridingMethods) {
|
||||
if (!isNotNull(overridingMethod)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
//and check that all of the submethods are not nullable
|
||||
registerNotNullAnnotation(method);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitLocalVariable(@NotNull PsiLocalVariable variable) {
|
||||
super.visitLocalVariable(variable);
|
||||
if (variable.getType() instanceof PsiPrimitiveType ||
|
||||
isNotNull(variable) || isNullable(variable)) {
|
||||
return;
|
||||
}
|
||||
|
||||
final SmartPsiElementPointer<PsiModifierListOwner> pointer = myPointerManager.createLazyPointer((PsiModifierListOwner)variable);
|
||||
if (variableNeverAssignedNull(variable)) {
|
||||
if (myAnnotateLocalVariables) {
|
||||
registerNotNullAnnotation(variable);
|
||||
}
|
||||
else {
|
||||
myNotNullSet.add(pointer);
|
||||
numAnnotationsAdded++;
|
||||
}
|
||||
}
|
||||
if (variableSometimesAssignedNull(variable)) {
|
||||
if (myAnnotateLocalVariables) {
|
||||
registerNullableAnnotation(variable);
|
||||
}
|
||||
else {
|
||||
myNullableSet.add(pointer);
|
||||
numAnnotationsAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void visitParameter(@NotNull PsiParameter parameter) {
|
||||
super.visitParameter(parameter);
|
||||
if (parameter.getType() instanceof PsiPrimitiveType ||
|
||||
isNotNull(parameter) || isNullable(parameter)) {
|
||||
return;
|
||||
}
|
||||
final PsiElement grandParent = parameter.getParent().getParent();
|
||||
if (grandParent instanceof PsiMethod) {
|
||||
final PsiMethod method = (PsiMethod)grandParent;
|
||||
if (method.getBody() != null) {
|
||||
|
||||
for (PsiReference reference : ReferencesSearch.search(parameter, new LocalSearchScope(method))) {
|
||||
final PsiElement place = reference.getElement();
|
||||
if (place instanceof PsiReferenceExpression) {
|
||||
final PsiReferenceExpression expr = (PsiReferenceExpression)place;
|
||||
if (PsiUtil.isAccessedForWriting(expr)) return;
|
||||
final PsiElement parent = PsiTreeUtil.skipParentsOfType(expr, PsiParenthesizedExpression.class, PsiTypeCastExpression.class);
|
||||
if (parent instanceof PsiBinaryExpression) { //todo check if comparison operation
|
||||
PsiExpression opposite = null;
|
||||
final PsiExpression lOperand = ((PsiBinaryExpression)parent).getLOperand();
|
||||
final PsiExpression rOperand = ((PsiBinaryExpression)parent).getROperand();
|
||||
if (lOperand == expr) {
|
||||
opposite = rOperand;
|
||||
}
|
||||
else if (rOperand == expr) {
|
||||
opposite = lOperand;
|
||||
}
|
||||
if (opposite != null && opposite.getType() == PsiType.NULL) {
|
||||
registerNullableAnnotation(parameter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiReferenceExpression) {
|
||||
if (((PsiReferenceExpression)parent).getQualifierExpression() == expr) {
|
||||
registerNotNullAnnotation(parameter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
else if (parent instanceof PsiAssignmentExpression) {
|
||||
if (((PsiAssignmentExpression)parent).getRExpression() == expr) {
|
||||
final PsiExpression expression = ((PsiAssignmentExpression)parent).getLExpression();
|
||||
if (expression instanceof PsiReferenceExpression) {
|
||||
final PsiElement resolve = ((PsiReferenceExpression)expression).resolve();
|
||||
if (resolve instanceof PsiVariable) {
|
||||
final PsiVariable localVar = (PsiVariable)resolve;
|
||||
if (isNotNull(localVar)) {
|
||||
registerNotNullAnnotation(parameter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isNotNull(method)) {
|
||||
PsiElement toReturn = parent;
|
||||
if (parent instanceof PsiConditionalExpression &&
|
||||
((PsiConditionalExpression)parent).getCondition() != expr) { //todo check conditional operations
|
||||
toReturn = parent.getParent();
|
||||
}
|
||||
if (toReturn instanceof PsiReturnStatement) {
|
||||
registerNotNullAnnotation(parameter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final PsiCall call = PsiTreeUtil.getParentOfType(expr, PsiCall.class);
|
||||
if (call != null) {
|
||||
final PsiExpressionList argumentList = call.getArgumentList();
|
||||
if (argumentList != null) {
|
||||
final PsiExpression[] args = argumentList.getExpressions();
|
||||
int idx = ArrayUtil.find(args, expr);
|
||||
if (idx >= 0) {
|
||||
final PsiMethod resolvedMethod = call.resolveMethod();
|
||||
if (resolvedMethod != null) {
|
||||
final PsiParameter[] parameters = resolvedMethod.getParameterList().getParameters();
|
||||
if (idx < parameters.length) { //not vararg
|
||||
final PsiParameter resolvedToParam = parameters[idx];
|
||||
if (isNotNull(resolvedToParam) && !resolvedToParam.isVarArgs()) {
|
||||
registerNotNullAnnotation(parameter);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (variableNeverAssignedNull(parameter)) {
|
||||
if (myAnnotateLocalVariables) {
|
||||
registerNotNullAnnotation(parameter);
|
||||
}
|
||||
else {
|
||||
myNotNullSet.add(myPointerManager.createLazyPointer((PsiModifierListOwner)parameter));
|
||||
numAnnotationsAdded++;
|
||||
}
|
||||
}
|
||||
if (variableSometimesAssignedNull(parameter)) {
|
||||
if (myAnnotateLocalVariables) {
|
||||
registerNullableAnnotation(parameter);
|
||||
}
|
||||
else {
|
||||
myNullableSet.add(myPointerManager.createLazyPointer((PsiModifierListOwner)parameter));
|
||||
numAnnotationsAdded++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitField(@NotNull PsiField field) {
|
||||
super.visitField(field);
|
||||
if (field instanceof PsiEnumConstant) {
|
||||
return;
|
||||
}
|
||||
if (field.getType() instanceof PsiPrimitiveType ||
|
||||
isNotNull(field) || isNullable(field)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (variableNeverAssignedNull(field)) {
|
||||
registerNotNullAnnotation(field);
|
||||
}
|
||||
if (variableSometimesAssignedNull(field)) {
|
||||
registerNullableAnnotation(field);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -35,6 +35,7 @@ import com.intellij.openapi.roots.libraries.LibraryUtil;
|
||||
import com.intellij.openapi.util.Computable;
|
||||
import com.intellij.openapi.util.io.FileUtil;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.ReadonlyStatusHandler;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.openapi.vfs.VirtualFileFilter;
|
||||
import com.intellij.profile.ProjectProfileManager;
|
||||
@@ -471,6 +472,14 @@ public class AnalysisScope {
|
||||
return myFilesSet.size();
|
||||
}
|
||||
|
||||
public boolean checkScopeWritable(Project project) {
|
||||
if (myFilesSet == null) initFilesSet();
|
||||
final ReadonlyStatusHandler statusHandler = ReadonlyStatusHandler.getInstance(project);
|
||||
final ReadonlyStatusHandler.OperationStatus status =
|
||||
statusHandler.ensureFilesWritable(myFilesSet);
|
||||
return status.hasReadonlyFiles();
|
||||
}
|
||||
|
||||
public void invalidate(){
|
||||
if (myType != VIRTUAL_FILES) {
|
||||
myFilesSet = null;
|
||||
|
||||
@@ -457,6 +457,8 @@ action.MoveStatementUp.description=Move selected statements one line up
|
||||
group.AnalyzeMenu.text=Analy_ze
|
||||
action.InspectCode.text=_Inspect Code...
|
||||
action.InspectCode.description=Inspect code
|
||||
action.InferNullity.text=Infer _Nullity...
|
||||
action.InferNullity.description=Infer nullity
|
||||
action.ViewOfflineInspection.text=View _Offline Inspection Results...
|
||||
action.ViewOfflineInspection.description=Load offline inspection results
|
||||
action.ShowPackageDeps.text=Analyze _Dependencies...
|
||||
|
||||
@@ -124,6 +124,7 @@
|
||||
|
||||
<group id="InspectCodeGroup" text="Inspect Code Actions">
|
||||
<action id="InspectCode" class="com.intellij.codeInspection.actions.CodeInspectionAction"/>
|
||||
<action id="InferNullity" class="com.intellij.codeInspection.inferNullity.InferNullityAnnotationsAction"/>
|
||||
<action id="ViewOfflineInspection" class="com.intellij.codeInspection.actions.ViewOfflineResultsAction"/>
|
||||
<add-to-group group-id="AnalyzeMenu" anchor="first"/>
|
||||
</group>
|
||||
|
||||
Reference in New Issue
Block a user