infer nullable/not null (initial)

This commit is contained in:
anna
2010-09-06 12:07:24 +04:00
parent 1729e18f8e
commit 0750cff4b1
5 changed files with 697 additions and 0 deletions
@@ -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...
+1
View File
@@ -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>