/* * Copyright 2000-2009 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; import com.intellij.analysis.AnalysisScope; import com.intellij.codeInsight.daemon.GroupNames; import com.intellij.codeInsight.daemon.impl.RemoveSuppressWarningAction; import com.intellij.codeInspection.ex.*; import com.intellij.codeInspection.reference.*; import com.intellij.codeInspection.ui.SingleCheckboxOptionsPanel; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.WriteExternalException; import com.intellij.openapi.util.text.StringUtil; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.*; import com.intellij.psi.util.PsiTreeUtil; import com.intellij.util.containers.BidirectionalMap; import com.intellij.util.containers.ContainerUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jdom.Element; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.util.*; /** * @author cdr */ public class RedundantSuppressInspection extends GlobalInspectionTool{ private BidirectionalMap myQuickFixes = null; private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.RedundantSuppressInspection"); public boolean IGNORE_ALL = false; @Override @NotNull public String getGroupDisplayName() { return GroupNames.DECLARATION_REDUNDANCY; } @Override @NotNull public String getDisplayName() { return InspectionsBundle.message("inspection.redundant.suppression.name"); } @Override @NotNull @NonNls public String getShortName() { return "RedundantSuppression"; } @Override public JComponent createOptionsPanel() { return new SingleCheckboxOptionsPanel("Ignore @SuppressWarning(\"ALL\")", this, "IGNORE_ALL"); } @Override public void writeSettings(@NotNull Element node) throws WriteExternalException { if (IGNORE_ALL) { super.writeSettings(node); } } @Override public void runInspection(@NotNull final AnalysisScope scope, @NotNull final InspectionManager manager, @NotNull final GlobalInspectionContext globalContext, @NotNull final ProblemDescriptionsProcessor problemDescriptionsProcessor) { globalContext.getRefManager().iterate(new RefJavaVisitor() { @Override public void visitClass(@NotNull RefClass refClass) { if (!globalContext.shouldCheck(refClass, RedundantSuppressInspection.this)) return; CommonProblemDescriptor[] descriptors = checkElement(refClass, manager, globalContext.getProject()); if (descriptors != null) { for (CommonProblemDescriptor descriptor : descriptors) { if (descriptor instanceof ProblemDescriptor) { final PsiElement psiElement = ((ProblemDescriptor)descriptor).getPsiElement(); final PsiMember member = PsiTreeUtil.getParentOfType(psiElement, PsiMember.class); final RefElement refElement = globalContext.getRefManager().getReference(member); if (refElement != null) { problemDescriptionsProcessor.addProblemElement(refElement, descriptor); continue; } } problemDescriptionsProcessor.addProblemElement(refClass, descriptor); } } } }); } @Nullable private CommonProblemDescriptor[] checkElement(@NotNull RefClass refEntity, @NotNull InspectionManager manager, @NotNull Project project) { final PsiClass psiClass = refEntity.getElement(); if (psiClass == null) return null; return checkElement(psiClass, manager, project); } public CommonProblemDescriptor[] checkElement(@NotNull final PsiElement psiElement, @NotNull final InspectionManager manager, @NotNull Project project) { final Map> suppressedScopes = new THashMap>(); psiElement.accept(new JavaRecursiveElementWalkingVisitor() { @Override public void visitModifierList(PsiModifierList list) { super.visitModifierList(list); final PsiElement parent = list.getParent(); if (parent instanceof PsiModifierListOwner && !(parent instanceof PsiClass)) { checkElement(parent); } } @Override public void visitComment(PsiComment comment) { checkElement(comment); } @Override public void visitClass(PsiClass aClass) { if (aClass == psiElement) { super.visitClass(aClass); checkElement(aClass); } } private void checkElement(final PsiElement owner) { String idsString = JavaSuppressionUtil.getSuppressedInspectionIdsIn(owner); if (idsString != null && !idsString.isEmpty()) { List ids = StringUtil.split(idsString, ","); if (IGNORE_ALL && (ids.contains(SuppressionUtil.ALL) || ids.contains(SuppressionUtil.ALL.toLowerCase()))) return; Collection suppressed = suppressedScopes.get(owner); if (suppressed == null) { suppressed = ids; } else { for (String id : ids) { if (!suppressed.contains(id)) { suppressed.add(id); } } } suppressedScopes.put(owner, suppressed); } } }); if (suppressedScopes.values().isEmpty()) return null; // have to visit all file from scratch since inspections can be written in any perversive way including checkFile() overriding Collection suppressedTools = new THashSet(); InspectionToolWrapper[] toolWrappers = getInspectionTools(psiElement, manager); for (Collection ids : suppressedScopes.values()) { for (Iterator iterator = ids.iterator(); iterator.hasNext(); ) { final String shortName = iterator.next().trim(); for (InspectionToolWrapper toolWrapper : toolWrappers) { if (toolWrapper instanceof LocalInspectionToolWrapper && ((LocalInspectionToolWrapper)toolWrapper).getTool().getID().equals(shortName)) { if (((LocalInspectionToolWrapper)toolWrapper).isUnfair()) { iterator.remove(); break; } else { suppressedTools.add(toolWrapper); } } else if (toolWrapper.getShortName().equals(shortName)) { //ignore global unused as it won't be checked anyway if (toolWrapper instanceof LocalInspectionToolWrapper || toolWrapper instanceof GlobalInspectionToolWrapper) { suppressedTools.add(toolWrapper); } else { iterator.remove(); break; } } } } } PsiFile file = psiElement.getContainingFile(); final AnalysisScope scope = new AnalysisScope(file); final GlobalInspectionContextBase globalContext = new GlobalInspectionContextBase(file.getProject()); globalContext.setCurrentScope(scope); final RefManagerImpl refManager = (RefManagerImpl)globalContext.getRefManager(); refManager.inspectionReadActionStarted(); final List result; try { result = new ArrayList(); for (InspectionToolWrapper toolWrapper : suppressedTools) { String toolId = toolWrapper instanceof LocalInspectionToolWrapper ? ((LocalInspectionToolWrapper)toolWrapper).getTool().getID() : toolWrapper.getShortName(); toolWrapper.initialize(globalContext); final Collection descriptors; if (toolWrapper instanceof LocalInspectionToolWrapper) { LocalInspectionToolWrapper local = (LocalInspectionToolWrapper)toolWrapper; if (local.isUnfair()) continue; //cant't work with passes other than LocalInspectionPass List results = local.getTool().processFile(file, manager); descriptors = new ArrayList(results); } else if (toolWrapper instanceof GlobalInspectionToolWrapper) { final GlobalInspectionToolWrapper global = (GlobalInspectionToolWrapper)toolWrapper; GlobalInspectionTool globalTool = global.getTool(); if (globalTool.isGraphNeeded()) { refManager.findAllDeclarations(); } descriptors = new ArrayList(); globalContext.getRefManager().iterate(new RefVisitor() { @Override public void visitElement(@NotNull RefEntity refEntity) { CommonProblemDescriptor[] descriptors1 = global.getTool().checkElement(refEntity, scope, manager, globalContext, new ProblemDescriptionsProcessor() { @Nullable @Override public CommonProblemDescriptor[] getDescriptions(@NotNull RefEntity refEntity) { return new CommonProblemDescriptor[0]; } @Override public void ignoreElement(@NotNull RefEntity refEntity) { } @Override public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) { int i =0; } @Override public RefEntity getElement(@NotNull CommonProblemDescriptor descriptor) { return null; } }); if (descriptors1 != null) { ContainerUtil.addAll(descriptors, descriptors1); } } }); } else { continue; } for (PsiElement suppressedScope : suppressedScopes.keySet()) { Collection suppressedIds = suppressedScopes.get(suppressedScope); if (!suppressedIds.contains(toolId)) continue; for (CommonProblemDescriptor descriptor : descriptors) { if (!(descriptor instanceof ProblemDescriptor)) continue; PsiElement element = ((ProblemDescriptor)descriptor).getPsiElement(); if (element == null) continue; PsiElement annotation = JavaSuppressionUtil.getElementToolSuppressedIn(element, toolId); if (annotation != null && PsiTreeUtil.isAncestor(suppressedScope, annotation, false) || annotation == null && !PsiTreeUtil.isAncestor(suppressedScope, element, false)) { suppressedIds.remove(toolId); break; } } } } for (PsiElement suppressedScope : suppressedScopes.keySet()) { Collection suppressedIds = suppressedScopes.get(suppressedScope); for (String toolId : suppressedIds) { PsiMember psiMember; String problemLine = null; if (suppressedScope instanceof PsiMember) { psiMember = (PsiMember)suppressedScope; } else { psiMember = PsiTreeUtil.getParentOfType(suppressedScope, PsiDocCommentOwner.class); final PsiStatement statement = PsiTreeUtil.getNextSiblingOfType(suppressedScope, PsiStatement.class); problemLine = statement != null ? statement.getText() : null; } if (psiMember != null && psiMember.isValid()) { String description = InspectionsBundle.message("inspection.redundant.suppression.description"); if (myQuickFixes == null) myQuickFixes = new BidirectionalMap(); final String key = toolId + (problemLine != null ? ";" + problemLine : ""); QuickFix fix = myQuickFixes.get(key); if (fix == null) { fix = new RemoveSuppressWarningAction(toolId, problemLine); myQuickFixes.put(key, fix); } PsiElement identifier = null; if (psiMember instanceof PsiMethod) { identifier = ((PsiMethod)psiMember).getNameIdentifier(); } else if (psiMember instanceof PsiField) { identifier = ((PsiField)psiMember).getNameIdentifier(); } else if (psiMember instanceof PsiClass) { identifier = ((PsiClass)psiMember).getNameIdentifier(); } if (identifier == null) { identifier = psiMember; } result.add( manager.createProblemDescriptor(identifier, description, (LocalQuickFix)fix, ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false)); } } } } finally { refManager.inspectionReadActionFinished(); globalContext.close(true); } return result.toArray(new ProblemDescriptor[result.size()]); } protected InspectionToolWrapper[] getInspectionTools(PsiElement psiElement, @NotNull InspectionManager manager) { ModifiableModel model = InspectionProjectProfileManager.getInstance(manager.getProject()).getInspectionProfile().getModifiableModel(); InspectionProfileWrapper profile = new InspectionProfileWrapper((InspectionProfile)model); profile.init(manager.getProject()); return profile.getInspectionTools(psiElement); } @Override @Nullable public QuickFix getQuickFix(final String hint) { return myQuickFixes != null ? myQuickFixes.get(hint) : new RemoveSuppressWarningAction(hint); } @Override @Nullable public String getHint(@NotNull final QuickFix fix) { if (myQuickFixes != null) { final List list = myQuickFixes.getKeysByValue(fix); if (list != null) { LOG.assertTrue(list.size() == 1); return list.get(0); } } return null; } @Override public boolean isEnabledByDefault() { return false; } }