diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java new file mode 100644 index 000000000000..e8ed6591d40a --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/InferNullityAnnotationsAction.java @@ -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; + } +} diff --git a/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java new file mode 100644 index 000000000000..9ea6a14c55a4 --- /dev/null +++ b/java/java-impl/src/com/intellij/codeInspection/inferNullity/NullityInferrer.java @@ -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> myNotNullSet = + new HashSet>(); + private final HashSet> myNullableSet = + new HashSet>(); + 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 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 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 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 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 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 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 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); + } + } + } +} diff --git a/platform/lang-api/src/com/intellij/analysis/AnalysisScope.java b/platform/lang-api/src/com/intellij/analysis/AnalysisScope.java index 1377642f7947..9ed0a9d7656a 100644 --- a/platform/lang-api/src/com/intellij/analysis/AnalysisScope.java +++ b/platform/lang-api/src/com/intellij/analysis/AnalysisScope.java @@ -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; diff --git a/platform/platform-resources-en/src/messages/ActionsBundle.properties b/platform/platform-resources-en/src/messages/ActionsBundle.properties index 2f9fef5d0582..48a514d4b07d 100644 --- a/platform/platform-resources-en/src/messages/ActionsBundle.properties +++ b/platform/platform-resources-en/src/messages/ActionsBundle.properties @@ -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... diff --git a/resources/src/idea/IdeaActions.xml b/resources/src/idea/IdeaActions.xml index ab3d47f0d907..2a0d68d4b3d6 100644 --- a/resources/src/idea/IdeaActions.xml +++ b/resources/src/idea/IdeaActions.xml @@ -124,6 +124,7 @@ +