| /* |
| * Copyright 2000-2014 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.codeInsight.daemon.impl; |
| |
| import com.intellij.codeHighlighting.Pass; |
| import com.intellij.codeInsight.daemon.HighlightDisplayKey; |
| import com.intellij.codeInsight.daemon.ImplicitUsageProvider; |
| import com.intellij.codeInsight.daemon.JavaErrorMessages; |
| import com.intellij.codeInsight.daemon.ProblemHighlightFilter; |
| import com.intellij.codeInsight.daemon.impl.analysis.*; |
| import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; |
| import com.intellij.codeInsight.intention.EmptyIntentionAction; |
| import com.intellij.codeInsight.intention.IntentionAction; |
| import com.intellij.codeInsight.intention.QuickFixFactory; |
| import com.intellij.codeInspection.InspectionProfile; |
| import com.intellij.codeInspection.InspectionsBundle; |
| import com.intellij.codeInspection.SuppressionUtil; |
| import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection; |
| import com.intellij.codeInspection.ex.EntryPointsManagerBase; |
| import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider; |
| import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection; |
| import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspectionBase; |
| import com.intellij.codeInspection.util.SpecialAnnotationsUtilBase; |
| import com.intellij.lang.Language; |
| import com.intellij.lang.annotation.HighlightSeverity; |
| import com.intellij.openapi.application.ApplicationManager; |
| import com.intellij.openapi.diagnostic.Logger; |
| import com.intellij.openapi.editor.Document; |
| import com.intellij.openapi.editor.Editor; |
| import com.intellij.openapi.extensions.Extensions; |
| import com.intellij.openapi.progress.ProcessCanceledException; |
| import com.intellij.openapi.progress.ProgressIndicator; |
| import com.intellij.openapi.project.Project; |
| import com.intellij.openapi.roots.ProjectFileIndex; |
| import com.intellij.openapi.roots.ProjectRootManager; |
| import com.intellij.openapi.util.Key; |
| import com.intellij.openapi.vfs.VirtualFile; |
| import com.intellij.pom.PomNamedTarget; |
| import com.intellij.pom.java.LanguageLevel; |
| import com.intellij.profile.codeInspection.InspectionProjectProfileManager; |
| import com.intellij.psi.*; |
| import com.intellij.psi.codeStyle.JavaCodeStyleManager; |
| import com.intellij.psi.impl.PsiClassImplUtil; |
| import com.intellij.psi.impl.source.PsiClassImpl; |
| import com.intellij.psi.search.GlobalSearchScope; |
| import com.intellij.psi.search.PsiNonJavaFileReferenceProcessor; |
| import com.intellij.psi.search.PsiSearchHelper; |
| import com.intellij.psi.search.SearchScope; |
| import com.intellij.psi.search.searches.OverridingMethodsSearch; |
| import com.intellij.psi.search.searches.ReferencesSearch; |
| import com.intellij.psi.search.searches.SuperMethodsSearch; |
| import com.intellij.psi.util.PropertyUtil; |
| import com.intellij.psi.util.PsiModificationTracker; |
| import com.intellij.psi.util.PsiUtil; |
| import com.intellij.psi.util.PsiUtilCore; |
| import com.intellij.util.Processor; |
| import com.intellij.util.containers.Predicate; |
| import gnu.trove.THashSet; |
| import org.jetbrains.annotations.NotNull; |
| import org.jetbrains.annotations.Nullable; |
| import org.jetbrains.annotations.PropertyKey; |
| |
| import java.util.*; |
| |
| public class PostHighlightingPass extends ProgressableTextEditorHighlightingPass { |
| private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass"); |
| private static final Key<Long> LAST_POST_PASS_TIMESTAMP = Key.create("LAST_POST_PASS_TIMESTAMP"); |
| private final LanguageLevel myLanguageLevel; |
| private RefCountHolder myRefCountHolder; |
| private final PsiFile myFile; |
| @Nullable private final Editor myEditor; |
| @NotNull private final Predicate<PsiElement> myIsEntryPointPredicate; |
| private final int myStartOffset; |
| private final int myEndOffset; |
| |
| private Collection<HighlightInfo> myHighlights; |
| private boolean myHasRedundantImports; |
| private int myCurrentEntryIndex; |
| private boolean myHasMissortedImports; |
| private static final ImplicitUsageProvider[] ourImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME); |
| private UnusedSymbolLocalInspectionBase myUnusedSymbolInspection; |
| private HighlightDisplayKey myUnusedSymbolKey; |
| private boolean myInLibrary; |
| private HighlightDisplayKey myDeadCodeKey; |
| private HighlightInfoType myDeadCodeInfoType; |
| |
| public PostHighlightingPass(@NotNull Project project, |
| @NotNull PsiFile file, |
| @Nullable Editor editor, |
| @NotNull Document document, |
| @NotNull HighlightInfoProcessor highlightInfoProcessor, |
| @NotNull Predicate<PsiElement> isEntryPoint) { |
| super(project, document, "Unused symbols", file, editor, file.getTextRange(), true, highlightInfoProcessor); |
| myFile = file; |
| myEditor = editor; |
| myIsEntryPointPredicate = isEntryPoint; |
| myStartOffset = 0; |
| myEndOffset = file.getTextLength(); |
| |
| myCurrentEntryIndex = -1; |
| myLanguageLevel = PsiUtil.getLanguageLevel(file); |
| } |
| |
| static boolean isUpToDate(@NotNull PsiFile file) { |
| Long lastStamp = file.getUserData(LAST_POST_PASS_TIMESTAMP); |
| long currentStamp = PsiModificationTracker.SERVICE.getInstance(file.getProject()).getModificationCount(); |
| return lastStamp != null && lastStamp == currentStamp || !ProblemHighlightFilter.shouldHighlightFile(file); |
| } |
| |
| private static void markFileUpToDate(@NotNull PsiFile file) { |
| long lastStamp = PsiModificationTracker.SERVICE.getInstance(file.getProject()).getModificationCount(); |
| file.putUserData(LAST_POST_PASS_TIMESTAMP, lastStamp); |
| } |
| |
| private static boolean isInjected(@NotNull Project project, @NotNull PsiModifierListOwner modifierListOwner) { |
| return EntryPointsManagerBase.getInstance(project).isEntryPoint(modifierListOwner); |
| } |
| |
| @Override |
| protected void collectInformationWithProgress(@NotNull final ProgressIndicator progress) { |
| DaemonCodeAnalyzerEx daemonCodeAnalyzer = DaemonCodeAnalyzerEx.getInstanceEx(myProject); |
| final FileStatusMap fileStatusMap = daemonCodeAnalyzer.getFileStatusMap(); |
| final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>(); |
| final FileViewProvider viewProvider = myFile.getViewProvider(); |
| final Set<Language> relevantLanguages = viewProvider.getLanguages(); |
| final Set<PsiElement> elementSet = new THashSet<PsiElement>(); |
| for (Language language : relevantLanguages) { |
| PsiElement psiRoot = viewProvider.getPsi(language); |
| if (!HighlightingLevelManager.getInstance(myProject).shouldHighlight(psiRoot)) continue; |
| List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset); |
| elementSet.addAll(elements); |
| } |
| |
| ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex(); |
| VirtualFile virtualFile = viewProvider.getVirtualFile(); |
| myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile); |
| |
| myRefCountHolder = RefCountHolder.endUsing(myFile, progress); |
| if (myRefCountHolder == null || !myRefCountHolder.retrieveUnusedReferencesInfo(progress, new Runnable() { |
| @Override |
| public void run() { |
| boolean errorFound = collectHighlights(elementSet, highlights, progress); |
| myHighlights = highlights; |
| if (errorFound) { |
| fileStatusMap.setErrorFoundFlag(myDocument, true); |
| } |
| } |
| })) { |
| // we must be sure GHP will restart |
| fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL); |
| GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this); |
| } |
| } |
| |
| @NotNull |
| @Override |
| public List<HighlightInfo> getInfos() { |
| Collection<HighlightInfo> infos = myHighlights; |
| return infos == null ? Collections.<HighlightInfo>emptyList() : new ArrayList<HighlightInfo>(infos); |
| } |
| |
| @Override |
| protected void applyInformationWithProgress() { |
| if (myHighlights == null) return; |
| UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, getColorsScheme(), Pass.POST_UPDATE_ALL); |
| markFileUpToDate(myFile); |
| |
| Editor editor = myEditor; |
| if (editor != null) { |
| optimizeImportsOnTheFly(editor); |
| } |
| } |
| |
| private void optimizeImportsOnTheFly(@NotNull final Editor editor) { |
| if (myHasRedundantImports || myHasMissortedImports) { |
| IntentionAction optimizeImportsFix = QuickFixFactory.getInstance().createOptimizeImportsFix(true); |
| if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) { |
| optimizeImportsFix.invoke(myProject, editor, myFile); |
| } |
| } |
| } |
| |
| // returns true if error highlight was created |
| private boolean collectHighlights(@NotNull Collection<PsiElement> elements, |
| @NotNull final List<HighlightInfo> result, |
| @NotNull ProgressIndicator progress) throws ProcessCanceledException { |
| ApplicationManager.getApplication().assertReadAccessAllowed(); |
| |
| InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); |
| myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspectionBase.SHORT_NAME); |
| boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile); |
| myUnusedSymbolInspection = (UnusedSymbolLocalInspectionBase)profile.getUnwrappedTool(UnusedSymbolLocalInspectionBase.SHORT_NAME, myFile); |
| LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null); |
| |
| myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME); |
| |
| HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME); |
| |
| myDeadCodeInfoType = myDeadCodeKey == null |
| ? HighlightInfoType.UNUSED_SYMBOL |
| : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), |
| HighlightInfoType.UNUSED_SYMBOL.getAttributesKey()); |
| |
| GlobalUsageHelper helper = new GlobalUsageHelper() { |
| @Override |
| public boolean shouldCheckUsages(@NotNull PsiMember member) { |
| return !myInLibrary && !myIsEntryPointPredicate.apply(member); |
| } |
| |
| @Override |
| public boolean isCurrentFileAlreadyChecked() { |
| return true; |
| } |
| |
| @Override |
| public boolean isLocallyUsed(@NotNull PsiNamedElement member) { |
| return myRefCountHolder.isReferenced(member); |
| } |
| }; |
| |
| boolean errorFound = false; |
| if (unusedSymbolEnabled) { |
| for (PsiElement element : elements) { |
| progress.checkCanceled(); |
| if (element instanceof PsiIdentifier) { |
| PsiIdentifier identifier = (PsiIdentifier)element; |
| HighlightInfo info = processIdentifier(identifier, progress, helper); |
| if (info != null) { |
| errorFound |= info.getSeverity() == HighlightSeverity.ERROR; |
| result.add(info); |
| } |
| } |
| } |
| } |
| if (isUnusedImportEnabled(unusedImportKey)) { |
| PsiImportList importList = ((PsiJavaFile)myFile).getImportList(); |
| if (importList != null) { |
| final PsiImportStatementBase[] imports = importList.getAllImportStatements(); |
| for (PsiImportStatementBase statement : imports) { |
| progress.checkCanceled(); |
| final HighlightInfo info = processImport(statement, unusedImportKey); |
| if (info != null) { |
| errorFound |= info.getSeverity() == HighlightSeverity.ERROR; |
| result.add(info); |
| } |
| } |
| } |
| } |
| |
| return errorFound; |
| } |
| |
| protected boolean isUnusedImportEnabled(HighlightDisplayKey unusedImportKey) { |
| InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile(); |
| if (!profile.isToolEnabled(unusedImportKey, myFile)) return false; |
| return myFile instanceof PsiJavaFile && HighlightingLevelManager.getInstance(myProject).shouldHighlight(myFile); |
| } |
| |
| @Nullable |
| private HighlightInfo processIdentifier(@NotNull PsiIdentifier identifier, @NotNull ProgressIndicator progress, @NotNull GlobalUsageHelper helper) { |
| if (SuppressionUtil.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null; |
| PsiElement parent = identifier.getParent(); |
| if (PsiUtilCore.hasErrorElementChild(parent)) return null; |
| |
| if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) { |
| return processLocalVariable((PsiLocalVariable)parent, identifier, progress); |
| } |
| if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) { |
| return processField(myProject, (PsiField)parent, identifier, progress, helper); |
| } |
| if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) { |
| if (SuppressionUtil.isSuppressed(identifier, UnusedSymbolLocalInspectionBase.UNUSED_PARAMETERS_SHORT_NAME)) return null; |
| return processParameter(myProject, (PsiParameter)parent, identifier, progress); |
| } |
| if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) { |
| return processMethod(myProject, (PsiMethod)parent, identifier, progress, helper); |
| } |
| if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) { |
| return processClass(myProject, (PsiClass)parent, identifier, progress, helper); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private HighlightInfo processLocalVariable(@NotNull PsiLocalVariable variable, |
| @NotNull PsiIdentifier identifier, |
| @NotNull ProgressIndicator progress) { |
| if (variable instanceof PsiResourceVariable && PsiUtil.isIgnoredName(variable.getName())) return null; |
| if (isImplicitUsage(myProject, variable, progress)) return null; |
| |
| if (!myRefCountHolder.isReferenced(variable)) { |
| String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText()); |
| HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); |
| IntentionAction fix = variable instanceof PsiResourceVariable ? QuickFixFactory.getInstance().createRenameToIgnoredFix(variable) : QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable); |
| QuickFixAction.registerQuickFixAction(highlightInfo, fix, myUnusedSymbolKey); |
| return highlightInfo; |
| } |
| |
| boolean referenced = myRefCountHolder.isReferencedForRead(variable); |
| if (!referenced && !isImplicitRead(myProject, variable, progress)) { |
| String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText()); |
| HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveUnusedVariableFix(variable), myUnusedSymbolKey); |
| return highlightInfo; |
| } |
| |
| if (!variable.hasInitializer()) { |
| referenced = myRefCountHolder.isReferencedForWrite(variable); |
| if (!referenced && !isImplicitWrite(myProject, variable, progress)) { |
| String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText()); |
| final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); |
| QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspectionBase.DISPLAY_NAME), myUnusedSymbolKey); |
| return unusedSymbolInfo; |
| } |
| } |
| |
| return null; |
| } |
| |
| public static boolean isImplicitUsage(@NotNull Project project, |
| @NotNull PsiModifierListOwner element, |
| @NotNull ProgressIndicator progress) { |
| if (isInjected(project, element)) return true; |
| for (ImplicitUsageProvider provider : ourImplicitUsageProviders) { |
| progress.checkCanceled(); |
| if (provider.isImplicitUsage(element)) { |
| return true; |
| } |
| } |
| |
| return false; |
| } |
| |
| private static boolean isImplicitRead(@NotNull Project project, @NotNull PsiVariable element, @NotNull ProgressIndicator progress) { |
| for(ImplicitUsageProvider provider: ourImplicitUsageProviders) { |
| progress.checkCanceled(); |
| if (provider.isImplicitRead(element)) { |
| return true; |
| } |
| } |
| return isInjected(project, element); |
| } |
| |
| private static boolean isImplicitWrite(@NotNull Project project, |
| @NotNull PsiVariable element, |
| @NotNull ProgressIndicator progress) { |
| for(ImplicitUsageProvider provider: ourImplicitUsageProviders) { |
| progress.checkCanceled(); |
| if (provider.isImplicitWrite(element)) { |
| return true; |
| } |
| } |
| return isInjected(project, element); |
| } |
| |
| @Nullable |
| public static HighlightInfo createUnusedSymbolInfo(@NotNull PsiElement element, |
| @NotNull String message, |
| @NotNull final HighlightInfoType highlightInfoType) { |
| HighlightInfo info = HighlightInfo.newHighlightInfo(highlightInfoType).range(element).descriptionAndTooltip(message).create(); |
| if (info == null) { |
| return null; //filtered out |
| } |
| |
| UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME); |
| for (UnusedDeclarationFixProvider provider : fixProviders) { |
| IntentionAction[] fixes = provider.getQuickFixes(element); |
| for (IntentionAction fix : fixes) { |
| QuickFixAction.registerQuickFixAction(info, fix); |
| } |
| } |
| return info; |
| } |
| |
| @Nullable |
| private HighlightInfo processField(@NotNull final Project project, |
| @NotNull final PsiField field, |
| @NotNull PsiIdentifier identifier, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (HighlightUtil.isSerializationImplicitlyUsedField(field)) { |
| return null; |
| } |
| if (field.hasModifierProperty(PsiModifier.PRIVATE)) { |
| if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(myProject, field, progress)) { |
| String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText()); |
| |
| HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message); |
| if (!field.hasInitializer()) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), QuickFixFactory.getInstance().createCreateConstructorParameterFromFieldFix(field)); |
| } |
| return highlightInfo; |
| } |
| |
| final boolean readReferenced = myRefCountHolder.isReferencedForRead(field); |
| if (!readReferenced && !isImplicitRead(project, field, progress)) { |
| String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText()); |
| return suggestionsToMakeFieldUsed(field, identifier, message); |
| } |
| |
| if (field.hasInitializer()) { |
| return null; |
| } |
| final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field); |
| if (!writeReferenced && !isImplicitWrite(project, field, progress)) { |
| String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText()); |
| final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); |
| |
| QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey); |
| QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), QuickFixFactory.getInstance().createCreateConstructorParameterFromFieldFix( |
| field)); |
| SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(field, new Processor<String>() { |
| @Override |
| public boolean process(final String annoName) { |
| QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance() |
| .createAddToDependencyInjectionAnnotationsFix(project, annoName, "fields")); |
| return true; |
| } |
| }); |
| return info; |
| } |
| } |
| else if (isImplicitUsage(myProject, field, progress)) { |
| return null; |
| } |
| else if (isFieldUnused(myProject, myFile, field, progress, helper)) { |
| return formatUnusedSymbolHighlightInfo(project, "field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType, identifier); |
| } |
| return null; |
| } |
| |
| public static boolean isFieldUnused(@NotNull Project project, |
| @NotNull PsiFile containingFile, |
| @NotNull PsiField field, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (helper.isLocallyUsed(field) || !weAreSureThereAreNoUsages(project, containingFile, field, progress, helper)) { |
| return false; |
| } |
| return !(field instanceof PsiEnumConstant) || !isEnumValuesMethodUsed(project, containingFile, field, progress, helper); |
| } |
| |
| private HighlightInfo suggestionsToMakeFieldUsed(@NotNull PsiField field, @NotNull PsiIdentifier identifier, @NotNull String message) { |
| HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRemoveUnusedVariableFix(field), myUnusedSymbolKey); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createCreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey); |
| return highlightInfo; |
| } |
| |
| private static boolean isOverriddenOrOverrides(PsiMethod method) { |
| boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null; |
| return overrides || OverridingMethodsSearch.search(method).findFirst() != null; |
| } |
| |
| @Nullable |
| private HighlightInfo processParameter(@NotNull Project project, |
| @NotNull PsiParameter parameter, |
| @NotNull PsiIdentifier identifier, |
| @NotNull ProgressIndicator progress) { |
| PsiElement declarationScope = parameter.getDeclarationScope(); |
| if (declarationScope instanceof PsiMethod) { |
| PsiMethod method = (PsiMethod)declarationScope; |
| if (PsiUtilCore.hasErrorElementChild(method)) return null; |
| if ((method.isConstructor() || |
| method.hasModifierProperty(PsiModifier.PRIVATE) || |
| method.hasModifierProperty(PsiModifier.STATIC) || |
| !method.hasModifierProperty(PsiModifier.ABSTRACT) && |
| myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS && |
| !isOverriddenOrOverrides(method)) && |
| !method.hasModifierProperty(PsiModifier.NATIVE) && |
| !JavaHighlightUtil.isSerializationRelatedMethod(method, method.getContainingClass()) && |
| !PsiClassImplUtil.isMainOrPremainMethod(method)) { |
| if (isInjected(project, method)) return null; |
| HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress); |
| if (highlightInfo != null) { |
| QuickFixFactory.getInstance().registerFixesForUnusedParameter(parameter, highlightInfo); |
| return highlightInfo; |
| } |
| } |
| } |
| else if (declarationScope instanceof PsiForeachStatement && !PsiUtil.isIgnoredName(parameter.getName())) { |
| HighlightInfo highlightInfo = checkUnusedParameter(parameter, identifier, progress); |
| if (highlightInfo != null) { |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createRenameToIgnoredFix(parameter), myUnusedSymbolKey); |
| return highlightInfo; |
| } |
| } |
| |
| return null; |
| } |
| |
| @Nullable |
| private HighlightInfo checkUnusedParameter(@NotNull PsiParameter parameter, |
| @NotNull PsiIdentifier identifier, |
| @NotNull ProgressIndicator progress) { |
| if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(myProject, parameter, progress)) { |
| //parameter is defined by functional interface |
| final PsiElement declarationScope = parameter.getDeclarationScope(); |
| if (declarationScope instanceof PsiMethod && |
| myRefCountHolder.isReferencedByMethodReference((PsiMethod)declarationScope, myLanguageLevel)) { |
| return null; |
| } |
| String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText()); |
| return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL); |
| } |
| return null; |
| } |
| |
| @Nullable |
| private HighlightInfo processMethod(@NotNull final Project project, |
| @NotNull final PsiMethod method, |
| @NotNull PsiIdentifier identifier, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (isMethodReferenced(myProject, myFile, method, progress, helper)) return null; |
| HighlightInfoType highlightInfoType; |
| HighlightDisplayKey highlightDisplayKey; |
| String key; |
| if (method.hasModifierProperty(PsiModifier.PRIVATE)) { |
| highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; |
| highlightDisplayKey = myUnusedSymbolKey; |
| key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used"; |
| } |
| else { |
| highlightInfoType = myDeadCodeInfoType; |
| highlightDisplayKey = myDeadCodeKey; |
| key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used"; |
| } |
| String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY); |
| String message = JavaErrorMessages.message(key, symbolName); |
| final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(method), highlightDisplayKey); |
| SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes(method, new Processor<String>() { |
| @Override |
| public boolean process(final String annoName) { |
| IntentionAction fix = QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName, "methods"); |
| QuickFixAction.registerQuickFixAction(highlightInfo, fix); |
| return true; |
| } |
| }); |
| return highlightInfo; |
| } |
| |
| public static boolean isMethodReferenced(@NotNull Project project, |
| @NotNull PsiFile containingFile, |
| @NotNull PsiMethod method, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (helper.isLocallyUsed(method)) return true; |
| |
| boolean aPrivate = method.hasModifierProperty(PsiModifier.PRIVATE); |
| PsiClass containingClass = method.getContainingClass(); |
| if (JavaHighlightUtil.isSerializationRelatedMethod(method, containingClass)) return true; |
| if (aPrivate) { |
| if (isIntentionalPrivateConstructor(method, containingClass)) { |
| return true; |
| } |
| if (isImplicitUsage(project, method, progress)) { |
| return true; |
| } |
| if (!helper.isCurrentFileAlreadyChecked()) { |
| return !weAreSureThereAreNoUsages(project, containingFile, method, progress, helper); |
| } |
| } |
| else { |
| //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too |
| if (containingClass != null && method.isConstructor() |
| && containingClass.getConstructors().length == 1 |
| && isClassUsed(project, containingFile, containingClass, progress, helper)) { |
| return true; |
| } |
| if (isImplicitUsage(project, method, progress)) return true; |
| |
| if (method.findSuperMethods().length != 0) { |
| return true; |
| } |
| if (!weAreSureThereAreNoUsages(project, containingFile, method, progress, helper)) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| private static boolean weAreSureThereAreNoUsages(@NotNull Project project, |
| @NotNull PsiFile containingFile, |
| @NotNull PsiMember member, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (!helper.shouldCheckUsages(member)) return false; |
| |
| String name = member.getName(); |
| if (name == null) return false; |
| SearchScope useScope = member.getUseScope(); |
| PsiSearchHelper searchHelper = PsiSearchHelper.SERVICE.getInstance(project); |
| PsiFile ignoreFile = helper.isCurrentFileAlreadyChecked() ? containingFile : null; |
| if (useScope instanceof GlobalSearchScope) { |
| // some classes may have references from within XML outside dependent modules, e.g. our actions |
| if (member instanceof PsiClass) { |
| useScope = GlobalSearchScope.projectScope(project).uniteWith((GlobalSearchScope)useScope); |
| } |
| |
| PsiSearchHelper.SearchCostResult cheapEnough = searchHelper.isCheapEnoughToSearch(name, (GlobalSearchScope)useScope, ignoreFile, progress); |
| if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false; |
| |
| //search usages if it cheap |
| //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before |
| if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES && !canBeReferencedViaWeirdNames(member, containingFile)) { |
| return true; |
| } |
| |
| if (member instanceof PsiMethod) { |
| String propertyName = PropertyUtil.getPropertyName(member); |
| if (propertyName != null) { |
| SearchScope fileScope = containingFile.getUseScope(); |
| if (fileScope instanceof GlobalSearchScope && |
| searchHelper.isCheapEnoughToSearch(propertyName, (GlobalSearchScope)fileScope, ignoreFile, progress) == |
| PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) { |
| return false; |
| } |
| } |
| } |
| } |
| if (ReferencesSearch.search(member, useScope, true).findFirst() != null) return false; |
| return !(useScope instanceof GlobalSearchScope) || !foundUsageInText(member, (GlobalSearchScope)useScope, searchHelper, ignoreFile); |
| } |
| |
| private static boolean foundUsageInText(@NotNull PsiMember member, |
| @NotNull GlobalSearchScope scope, |
| @NotNull PsiSearchHelper searchHelper, |
| final PsiFile ignoreFile) { |
| return !searchHelper.processUsagesInNonJavaFiles(member, member.getName(), new PsiNonJavaFileReferenceProcessor() { |
| @Override |
| public boolean process(final PsiFile psiFile, final int startOffset, final int endOffset) { |
| if (psiFile == ignoreFile) return true; // ignore usages in containingFile because isLocallyUsed() method would have caught that |
| PsiElement element = psiFile.findElementAt(startOffset); |
| return element instanceof PsiComment; // ignore comments |
| } |
| }, scope); |
| } |
| |
| private static boolean isEnumValuesMethodUsed(@NotNull Project project, |
| @NotNull PsiFile containingFile, |
| @NotNull PsiMember member, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| final PsiClass containingClass = member.getContainingClass(); |
| if (containingClass == null || !(containingClass instanceof PsiClassImpl)) return true; |
| final PsiMethod valuesMethod = ((PsiClassImpl)containingClass).getValuesMethod(); |
| return valuesMethod == null || isMethodReferenced(project, containingFile, valuesMethod, progress, helper); |
| } |
| |
| private static boolean canBeReferencedViaWeirdNames(@NotNull PsiMember member, @NotNull PsiFile containingFile) { |
| if (member instanceof PsiClass) return false; |
| if (!(containingFile instanceof PsiJavaFile)) return true; // Groovy field can be referenced from Java by getter |
| if (member instanceof PsiField) return false; //Java field cannot be referenced by anything but its name |
| if (member instanceof PsiMethod) { |
| return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member); //Java accessors can be referenced by field name from Groovy |
| } |
| return false; |
| } |
| |
| @Nullable |
| private HighlightInfo processClass(@NotNull Project project, |
| @NotNull PsiClass aClass, |
| @NotNull PsiIdentifier identifier, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (isClassUsed(project, myFile, aClass, progress, helper)) return null; |
| |
| String pattern; |
| HighlightDisplayKey highlightDisplayKey; |
| HighlightInfoType highlightInfoType; |
| if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) { |
| pattern = aClass.isInterface() |
| ? "private.inner.interface.is.not.used" |
| : "private.inner.class.is.not.used"; |
| highlightDisplayKey = myUnusedSymbolKey; |
| highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; |
| } |
| else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class |
| pattern = "local.class.is.not.used"; |
| highlightDisplayKey = myUnusedSymbolKey; |
| highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; |
| } |
| else if (aClass instanceof PsiTypeParameter) { |
| pattern = "type.parameter.is.not.used"; |
| highlightDisplayKey = myUnusedSymbolKey; |
| highlightInfoType = HighlightInfoType.UNUSED_SYMBOL; |
| } |
| else { |
| pattern = "class.is.not.used"; |
| highlightDisplayKey = myDeadCodeKey; |
| highlightInfoType = myDeadCodeInfoType; |
| } |
| return formatUnusedSymbolHighlightInfo(myProject, pattern, aClass, "classes", highlightDisplayKey, highlightInfoType, identifier); |
| } |
| |
| public static boolean isClassUsed(@NotNull Project project, |
| @NotNull PsiFile containingFile, |
| @NotNull PsiClass aClass, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| Boolean result = helper.unusedClassCache.get(aClass); |
| if (result == null) { |
| result = isReallyUsed(project, containingFile, aClass, progress, helper); |
| helper.unusedClassCache.put(aClass, result); |
| } |
| return result; |
| } |
| |
| private static boolean isReallyUsed(@NotNull Project project, |
| @NotNull PsiFile containingFile, |
| @NotNull PsiClass aClass, |
| @NotNull ProgressIndicator progress, |
| @NotNull GlobalUsageHelper helper) { |
| if (isImplicitUsage(project, aClass, progress) || helper.isLocallyUsed(aClass)) return true; |
| if (helper.isCurrentFileAlreadyChecked()) { |
| if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) || |
| aClass.getParent() instanceof PsiDeclarationStatement || |
| aClass instanceof PsiTypeParameter) return false; |
| } |
| return !weAreSureThereAreNoUsages(project, containingFile, aClass, progress, helper); |
| } |
| |
| private static HighlightInfo formatUnusedSymbolHighlightInfo(@NotNull final Project project, |
| @NotNull @PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern, |
| @NotNull final PsiNameIdentifierOwner aClass, |
| @NotNull final String element, |
| HighlightDisplayKey highlightDisplayKey, |
| @NotNull HighlightInfoType highlightInfoType, |
| @NotNull PsiElement identifier) { |
| String symbolName = aClass.getName(); |
| String message = JavaErrorMessages.message(pattern, symbolName); |
| final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType); |
| QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixFactory.getInstance().createSafeDeleteFix(aClass), highlightDisplayKey); |
| SpecialAnnotationsUtilBase.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() { |
| @Override |
| public boolean process(final String annoName) { |
| QuickFixAction |
| .registerQuickFixAction(highlightInfo, |
| QuickFixFactory.getInstance().createAddToDependencyInjectionAnnotationsFix(project, annoName, element)); |
| return true; |
| } |
| }); |
| return highlightInfo; |
| } |
| |
| @Nullable |
| private HighlightInfo processImport(@NotNull PsiImportStatementBase importStatement, @NotNull HighlightDisplayKey unusedImportKey) { |
| // jsp include directive hack |
| if (importStatement.isForeignFileImport()) return null; |
| |
| if (PsiUtilCore.hasErrorElementChild(importStatement)) return null; |
| |
| boolean isRedundant = myRefCountHolder.isRedundant(importStatement); |
| if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) { |
| //check import from same package |
| String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName(); |
| PsiJavaCodeReferenceElement reference = importStatement.getImportReference(); |
| PsiElement resolved = reference == null ? null : reference.resolve(); |
| if (resolved instanceof PsiPackage) { |
| isRedundant = packageName.equals(((PsiQualifiedNamedElement)resolved).getQualifiedName()); |
| } |
| else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) { |
| String qName = ((PsiClass)resolved).getQualifiedName(); |
| if (qName != null) { |
| String name = ((PomNamedTarget)resolved).getName(); |
| isRedundant = qName.equals(packageName + '.' + name); |
| } |
| } |
| } |
| |
| if (isRedundant) { |
| return registerRedundantImport(importStatement, unusedImportKey); |
| } |
| |
| int entryIndex = JavaCodeStyleManager.getInstance(myProject).findEntryIndex(importStatement); |
| if (entryIndex < myCurrentEntryIndex) { |
| myHasMissortedImports = true; |
| } |
| myCurrentEntryIndex = entryIndex; |
| |
| return null; |
| } |
| |
| private HighlightInfo registerRedundantImport(@NotNull PsiImportStatementBase importStatement, @NotNull HighlightDisplayKey unusedImportKey) { |
| String description = InspectionsBundle.message("unused.import.statement"); |
| HighlightInfo info = |
| HighlightInfo.newHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT).range(importStatement).descriptionAndTooltip(description) |
| .create(); |
| |
| QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createOptimizeImportsFix(false), unusedImportKey); |
| QuickFixAction.registerQuickFixAction(info, QuickFixFactory.getInstance().createEnableOptimizeImportsOnTheFlyFix(), unusedImportKey); |
| myHasRedundantImports = true; |
| return info; |
| } |
| |
| |
| private static boolean isIntentionalPrivateConstructor(@NotNull PsiMethod method, PsiClass containingClass) { |
| return method.isConstructor() && |
| method.getParameterList().getParametersCount() == 0 && |
| containingClass != null && |
| containingClass.getConstructors().length == 1; |
| } |
| } |