2 * Copyright 2000-2009 JetBrains s.r.o.
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
16 package com.intellij.codeInsight.daemon.impl;
18 import com.intellij.codeHighlighting.Pass;
19 import com.intellij.codeHighlighting.TextEditorHighlightingPass;
20 import com.intellij.codeInsight.CodeInsightSettings;
21 import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer;
22 import com.intellij.codeInsight.daemon.HighlightDisplayKey;
23 import com.intellij.codeInsight.daemon.ImplicitUsageProvider;
24 import com.intellij.codeInsight.daemon.JavaErrorMessages;
25 import com.intellij.codeInsight.daemon.impl.analysis.HighlightLevelUtil;
26 import com.intellij.codeInsight.daemon.impl.analysis.HighlightMessageUtil;
27 import com.intellij.codeInsight.daemon.impl.analysis.HighlightMethodUtil;
28 import com.intellij.codeInsight.daemon.impl.analysis.HighlightUtil;
29 import com.intellij.codeInsight.daemon.impl.quickfix.*;
30 import com.intellij.codeInsight.intention.EmptyIntentionAction;
31 import com.intellij.codeInsight.intention.IntentionAction;
32 import com.intellij.codeInsight.intention.IntentionManager;
33 import com.intellij.codeInspection.InspectionProfile;
34 import com.intellij.codeInspection.InspectionProfileEntry;
35 import com.intellij.codeInspection.InspectionsBundle;
36 import com.intellij.codeInspection.deadCode.UnusedDeclarationInspection;
37 import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
38 import com.intellij.codeInspection.ex.InspectionManagerEx;
39 import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
40 import com.intellij.codeInspection.reference.UnusedDeclarationFixProvider;
41 import com.intellij.codeInspection.unusedImport.UnusedImportLocalInspection;
42 import com.intellij.codeInspection.unusedParameters.UnusedParametersInspection;
43 import com.intellij.codeInspection.unusedSymbol.UnusedSymbolLocalInspection;
44 import com.intellij.codeInspection.util.SpecialAnnotationsUtil;
45 import com.intellij.diagnostic.LogMessageEx;
46 import com.intellij.diagnostic.errordialog.Attachment;
47 import com.intellij.find.FindManager;
48 import com.intellij.find.findUsages.*;
49 import com.intellij.find.impl.FindManagerImpl;
50 import com.intellij.lang.Language;
51 import com.intellij.lang.annotation.HighlightSeverity;
52 import com.intellij.openapi.application.ApplicationManager;
53 import com.intellij.openapi.command.CommandProcessor;
54 import com.intellij.openapi.command.undo.UndoManager;
55 import com.intellij.openapi.diagnostic.Logger;
56 import com.intellij.openapi.editor.Document;
57 import com.intellij.openapi.editor.Editor;
58 import com.intellij.openapi.extensions.Extensions;
59 import com.intellij.openapi.progress.ProcessCanceledException;
60 import com.intellij.openapi.progress.ProgressIndicator;
61 import com.intellij.openapi.project.Project;
62 import com.intellij.openapi.roots.ProjectFileIndex;
63 import com.intellij.openapi.roots.ProjectRootManager;
64 import com.intellij.openapi.util.Comparing;
65 import com.intellij.openapi.util.TextRange;
66 import com.intellij.openapi.vfs.VirtualFile;
67 import com.intellij.profile.codeInspection.InspectionProjectProfileManager;
68 import com.intellij.psi.*;
69 import com.intellij.psi.codeStyle.JavaCodeStyleManager;
70 import com.intellij.psi.impl.PsiClassImplUtil;
71 import com.intellij.psi.impl.source.PsiClassImpl;
72 import com.intellij.psi.impl.source.jsp.jspJava.JspxImportStatement;
73 import com.intellij.psi.jsp.JspFile;
74 import com.intellij.psi.jsp.JspSpiUtil;
75 import com.intellij.psi.search.GlobalSearchScope;
76 import com.intellij.psi.search.PsiSearchHelper;
77 import com.intellij.psi.search.SearchScope;
78 import com.intellij.psi.search.searches.OverridingMethodsSearch;
79 import com.intellij.psi.search.searches.SuperMethodsSearch;
80 import com.intellij.psi.util.PropertyUtil;
81 import com.intellij.psi.util.PsiUtilCore;
82 import com.intellij.refactoring.changeSignature.ChangeSignatureGestureDetector;
83 import com.intellij.util.Processor;
84 import gnu.trove.THashSet;
85 import org.jetbrains.annotations.NotNull;
86 import org.jetbrains.annotations.Nullable;
87 import org.jetbrains.annotations.PropertyKey;
91 public class PostHighlightingPass extends TextEditorHighlightingPass {
92 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
93 private RefCountHolder myRefCountHolder;
94 private final PsiFile myFile;
95 @Nullable private final Editor myEditor;
96 private final int myStartOffset;
97 private final int myEndOffset;
99 private Collection<HighlightInfo> myHighlights;
100 private boolean myHasRedundantImports;
101 private final JavaCodeStyleManager myStyleManager;
102 private int myCurrentEntryIndex;
103 private boolean myHasMissortedImports;
104 private static final ImplicitUsageProvider[] ourImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
105 private UnusedDeclarationInspection myDeadCodeInspection;
106 private UnusedSymbolLocalInspection myUnusedSymbolInspection;
107 private HighlightDisplayKey myUnusedSymbolKey;
108 private boolean myDeadCodeEnabled;
109 private boolean myInLibrary;
110 private HighlightDisplayKey myDeadCodeKey;
111 private HighlightInfoType myDeadCodeInfoType;
112 private UnusedParametersInspection myUnusedParametersInspection;
114 PostHighlightingPass(@NotNull Project project,
115 @NotNull PsiFile file,
116 @Nullable Editor editor,
117 @NotNull Document document) {
118 super(project, document, true);
122 myEndOffset = file.getTextLength();
124 myStyleManager = JavaCodeStyleManager.getInstance(myProject);
125 myCurrentEntryIndex = -1;
129 public void doCollectInformation(final ProgressIndicator progress) {
130 DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
131 final FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap();
132 final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
133 final FileViewProvider viewProvider = myFile.getViewProvider();
134 final Set<Language> relevantLanguages = viewProvider.getLanguages();
135 final Set<PsiElement> elementSet = new THashSet<PsiElement>();
136 for (Language language : relevantLanguages) {
137 PsiElement psiRoot = viewProvider.getPsi(language);
138 if (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue;
139 List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset);
140 elementSet.addAll(elements);
143 ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
144 VirtualFile virtualFile = viewProvider.getVirtualFile();
145 myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile);
147 myRefCountHolder = RefCountHolder.getInstance(myFile);
148 if (!myRefCountHolder.retrieveUnusedReferencesInfo(new Runnable() {
151 boolean errorFound = collectHighlights(elementSet, highlights, progress);
152 myHighlights = highlights;
154 fileStatusMap.setErrorFoundFlag(myDocument, true);
158 // we must be sure GHP will restart
159 fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL);
160 GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this);
165 public List<HighlightInfo> getInfos() {
166 return myHighlights == null ? null : new ArrayList<HighlightInfo>(myHighlights);
170 public void doApplyInformationToEditor() {
171 if (myHighlights == null) return;
172 UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, getColorsScheme(), Pass.POST_UPDATE_ALL);
173 PostHighlightingPassFactory.markFileUpToDate(myFile);
175 Editor editor = myEditor;
176 if (editor != null && timeToOptimizeImports()) {
177 optimizeImportsOnTheFly(editor);
181 private void optimizeImportsOnTheFly(@NotNull final Editor editor) {
182 if (myHasRedundantImports || myHasMissortedImports) {
183 final OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix();
184 if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) {
185 invokeOnTheFlyImportOptimizer(new Runnable() {
188 optimizeImportsFix.invoke(myProject, editor, myFile);
195 public static void invokeOnTheFlyImportOptimizer(@NotNull final Runnable runnable, @NotNull final PsiFile file, @NotNull final Editor editor) {
196 final long stamp = editor.getDocument().getModificationStamp();
197 ApplicationManager.getApplication().invokeLater(new Runnable() {
200 if (file.getProject().isDisposed() || editor.isDisposed() || editor.getDocument().getModificationStamp() != stamp) return;
201 //no need to optimize imports on the fly during undo/redo
202 final UndoManager undoManager = UndoManager.getInstance(editor.getProject());
203 if (undoManager.isUndoInProgress() || undoManager.isRedoInProgress()) return;
204 PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
205 String beforeText = file.getText();
206 final long oldStamp = editor.getDocument().getModificationStamp();
207 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
210 ApplicationManager.getApplication().runWriteAction(runnable);
213 if (oldStamp != editor.getDocument().getModificationStamp()) {
214 String afterText = file.getText();
215 if (Comparing.strEqual(beforeText, afterText)) {
216 LOG.error(LogMessageEx.createEvent("Import optimizer hasn't optimized any imports", file.getViewProvider().getVirtualFile().getPath(),
217 new Attachment(file.getViewProvider().getVirtualFile())));
224 // returns true if error highlight was created
225 private boolean collectHighlights(@NotNull Collection<PsiElement> elements, @NotNull final List<HighlightInfo> result, @NotNull ProgressIndicator progress) throws ProcessCanceledException {
226 ApplicationManager.getApplication().assertReadAccessAllowed();
228 InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
229 myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspection.SHORT_NAME);
230 boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile);
231 HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME);
232 boolean unusedImportEnabled = profile.isToolEnabled(unusedImportKey, myFile);
233 LocalInspectionToolWrapper unusedSymbolTool = (LocalInspectionToolWrapper)profile.getInspectionTool(UnusedSymbolLocalInspection.SHORT_NAME,
235 myUnusedSymbolInspection = unusedSymbolTool == null ? null : (UnusedSymbolLocalInspection)unusedSymbolTool.getTool();
236 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null);
238 myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME);
239 myDeadCodeInspection = (UnusedDeclarationInspection)profile.getInspectionTool(UnusedDeclarationInspection.SHORT_NAME, myFile);
240 myDeadCodeEnabled = profile.isToolEnabled(myDeadCodeKey, myFile);
242 final InspectionProfileEntry inspectionTool = profile.getInspectionTool(UnusedParametersInspection.SHORT_NAME, myFile);
243 myUnusedParametersInspection = inspectionTool != null ? (UnusedParametersInspection)((GlobalInspectionToolWrapper)inspectionTool).getTool() : null;
244 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedParametersInspection != null);
245 if (unusedImportEnabled && JspPsiUtil.isInJspFile(myFile)) {
246 final JspFile jspFile = JspPsiUtil.getJspFile(myFile);
247 if (jspFile != null) {
248 unusedImportEnabled = !JspSpiUtil.isIncludedOrIncludesSomething(jspFile);
252 myDeadCodeInfoType = myDeadCodeKey == null ? null : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), HighlightInfoType.UNUSED_SYMBOL.getAttributesKey());
254 GlobalUsageHelper helper = new GlobalUsageHelper() {
256 public boolean shouldCheckUsages(@NotNull PsiMember member) {
257 if (myInLibrary) return false;
258 if (!myDeadCodeEnabled) return false;
259 if (myDeadCodeInspection.isEntryPoint(member)) return false;
264 public boolean shouldIgnoreUsagesInCurrentFile() {
269 public boolean isLocallyUsed(@NotNull PsiNamedElement member) {
270 return myRefCountHolder.isReferenced(member);
274 boolean errorFound = false;
275 if (unusedSymbolEnabled) {
276 for (PsiElement element : elements) {
277 progress.checkCanceled();
278 if (element instanceof PsiIdentifier) {
279 PsiIdentifier identifier = (PsiIdentifier)element;
280 HighlightInfo info = processIdentifier(identifier, progress, helper);
282 errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
288 if (unusedImportEnabled && myFile instanceof PsiJavaFile && HighlightLevelUtil.shouldHighlight(myFile)) {
289 PsiImportList importList = ((PsiJavaFile)myFile).getImportList();
290 if (importList != null) {
291 final PsiImportStatementBase[] imports = importList.getAllImportStatements();
292 for (PsiImportStatementBase statement : imports) {
293 progress.checkCanceled();
294 final HighlightInfo info = processImport(statement, unusedImportKey);
296 errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
307 private HighlightInfo processIdentifier(PsiIdentifier identifier, ProgressIndicator progress, GlobalUsageHelper helper) {
308 if (InspectionManagerEx.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null;
309 PsiElement parent = identifier.getParent();
310 if (PsiUtilCore.hasErrorElementChild(parent)) return null;
312 if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) {
313 return processLocalVariable((PsiLocalVariable)parent, progress);
315 if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) {
316 return processField((PsiField)parent, identifier, progress, helper);
318 if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) {
319 if (InspectionManagerEx.isSuppressed(identifier, UnusedParametersInspection.SHORT_NAME)) return null;
320 return processParameter((PsiParameter)parent, progress);
322 if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) {
323 return processMethod((PsiMethod)parent, progress, helper);
325 if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) {
326 return processClass((PsiClass)parent, progress, helper);
333 private HighlightInfo processLocalVariable(PsiLocalVariable variable, ProgressIndicator progress) {
334 PsiIdentifier identifier = variable.getNameIdentifier();
335 if (identifier == null) return null;
336 if (isImplicitUsage(variable, progress)) return null;
337 if (!myRefCountHolder.isReferenced(variable)) {
338 String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText());
339 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
340 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
341 return highlightInfo;
344 boolean referenced = myRefCountHolder.isReferencedForRead(variable);
345 if (!referenced && !isImplicitRead(variable, progress)) {
346 String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText());
347 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
348 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
349 return highlightInfo;
352 if (!variable.hasInitializer()) {
353 referenced = myRefCountHolder.isReferencedForWrite(variable);
354 if (!referenced && !isImplicitWrite(variable, progress)) {
355 String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText());
356 final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
357 QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
358 return unusedSymbolInfo;
366 public static boolean isImplicitUsage(final PsiModifierListOwner element, ProgressIndicator progress) {
367 if (UnusedSymbolLocalInspection.isInjected(element)) return true;
368 for (ImplicitUsageProvider provider : ourImplicitUsageProviders) {
369 progress.checkCanceled();
370 if (provider.isImplicitUsage(element)) {
378 private static boolean isImplicitRead(final PsiVariable element, ProgressIndicator progress) {
379 for(ImplicitUsageProvider provider: ourImplicitUsageProviders) {
380 progress.checkCanceled();
381 if (provider.isImplicitRead(element)) {
385 return UnusedSymbolLocalInspection.isInjected(element);
388 private static boolean isImplicitWrite(final PsiVariable element, ProgressIndicator progress) {
389 for(ImplicitUsageProvider provider: ourImplicitUsageProviders) {
390 progress.checkCanceled();
391 if (provider.isImplicitWrite(element)) {
395 return UnusedSymbolLocalInspection.isInjected(element);
398 public static HighlightInfo createUnusedSymbolInfo(@NotNull PsiElement element, @Nullable String message, @NotNull final HighlightInfoType highlightInfoType) {
399 HighlightInfo info = HighlightInfo.createHighlightInfo(highlightInfoType, element, message);
400 UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME);
401 for (UnusedDeclarationFixProvider provider : fixProviders) {
402 IntentionAction[] fixes = provider.getQuickFixes(element);
403 for (IntentionAction fix : fixes) {
404 QuickFixAction.registerQuickFixAction(info, fix);
411 private HighlightInfo processField(final PsiField field, final PsiIdentifier identifier, ProgressIndicator progress, GlobalUsageHelper helper) {
412 if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
413 if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(field, progress)) {
414 if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
417 String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText());
419 HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message);
420 if (!field.hasInitializer()) {
421 QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
423 return highlightInfo;
426 final boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
427 if (!readReferenced && !isImplicitRead(field, progress)) {
428 String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText());
429 return suggestionsToMakeFieldUsed(field, identifier, message);
432 if (field.hasInitializer()) {
435 final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
436 if (!writeReferenced && !isImplicitWrite(field, progress)) {
437 String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText());
438 final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
440 QuickFixAction.registerQuickFixAction(info, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
441 QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
442 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(field, new Processor<String>() {
444 public boolean process(final String annoName) {
445 QuickFixAction.registerQuickFixAction(info, UnusedSymbolLocalInspection.createQuickFix(annoName, "fields", field.getProject()));
452 else if (isImplicitUsage(field, progress)) {
455 else if (isFieldUnused(field, progress, helper)) {
456 return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType);
461 public static boolean isFieldUnused(PsiField field, ProgressIndicator progress, GlobalUsageHelper helper) {
462 if (helper.isLocallyUsed(field) || !weAreSureThereAreNoUsages(field, progress, helper)) {
465 if (field instanceof PsiEnumConstant && isEnumValuesMethodUsed(field, progress, helper)) {
471 private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) {
472 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
473 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey);
474 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
475 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
476 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
477 return highlightInfo;
480 private static boolean isOverriddenOrOverrides(PsiMethod method) {
481 boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
482 return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
486 private HighlightInfo processParameter(PsiParameter parameter, ProgressIndicator progress) {
487 PsiElement declarationScope = parameter.getDeclarationScope();
488 if (declarationScope instanceof PsiMethod) {
489 PsiMethod method = (PsiMethod)declarationScope;
490 if (PsiUtilCore.hasErrorElementChild(method)) return null;
491 if ((method.isConstructor() ||
492 method.hasModifierProperty(PsiModifier.PRIVATE) ||
493 method.hasModifierProperty(PsiModifier.STATIC) ||
494 !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
495 myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
496 !isOverriddenOrOverrides(method)) &&
497 !method.hasModifierProperty(PsiModifier.NATIVE) &&
498 !HighlightMethodUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
499 !PsiClassImplUtil.isMainMethod(method)) {
500 if (UnusedSymbolLocalInspection.isInjected(method)) return null;
501 HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
502 if (highlightInfo != null) {
503 final ArrayList<IntentionAction> options = new ArrayList<IntentionAction>();
504 options.addAll(IntentionManager.getInstance().getStandardIntentionOptions(myUnusedSymbolKey, myFile));
505 if (myUnusedParametersInspection != null) {
506 Collections.addAll(options, myUnusedParametersInspection.getSuppressActions(parameter));
508 //need suppress from Unused Parameters but settings from Unused Symbol
509 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter),
510 options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey));
511 return highlightInfo;
515 else if (declarationScope instanceof PsiForeachStatement) {
516 HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
517 if (highlightInfo != null) {
518 QuickFixAction.registerQuickFixAction(highlightInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
519 return highlightInfo;
527 private HighlightInfo checkUnusedParameter(final PsiParameter parameter, ProgressIndicator progress) {
528 if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter, progress)) {
529 PsiIdentifier identifier = parameter.getNameIdentifier();
530 assert identifier != null;
531 String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
532 return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
538 private HighlightInfo processMethod(final PsiMethod method, ProgressIndicator progress, GlobalUsageHelper helper) {
539 if (isMethodReferenced(method, progress, helper)) return null;
540 HighlightInfoType highlightInfoType;
541 HighlightDisplayKey highlightDisplayKey;
543 if (method.hasModifierProperty(PsiModifier.PRIVATE)) {
544 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
545 highlightDisplayKey = myUnusedSymbolKey;
546 key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
549 highlightInfoType = myDeadCodeInfoType;
550 highlightDisplayKey = myDeadCodeKey;
551 key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
553 String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
554 String message = JavaErrorMessages.message(key, symbolName);
555 PsiIdentifier identifier = method.getNameIdentifier();
556 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
557 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), highlightDisplayKey);
558 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
560 public boolean process(final String annoName) {
561 QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, "methods", method.getProject()));
565 PsiClass containingClass = method.getContainingClass();
566 if (method.getReturnType() != null || containingClass != null && Comparing.strEqual(containingClass.getName(), method.getName())) {
567 //ignore methods with deleted return types as they are always marked as unused without any reason
568 ChangeSignatureGestureDetector.getInstance(myProject).dismissForElement(method);
570 return highlightInfo;
573 public static boolean isMethodReferenced(PsiMethod method,
574 ProgressIndicator progress,
575 GlobalUsageHelper helper) {
576 if (helper.isLocallyUsed(method)) return true;
578 boolean aPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
579 PsiClass containingClass = method.getContainingClass();
580 if (HighlightMethodUtil.isSerializationRelatedMethod(method, containingClass)) return true;
582 if (isIntentionalPrivateConstructor(method, containingClass)) {
585 if (isImplicitUsage(method, progress)) {
588 if (!helper.shouldIgnoreUsagesInCurrentFile()) {
589 return !weAreSureThereAreNoUsages(method, progress, helper);
593 //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
594 if (containingClass != null && method.isConstructor()
595 && containingClass.getConstructors().length == 1
596 && isClassUsed(containingClass, progress, helper)) {
599 if (isImplicitUsage(method, progress)) return true;
601 if (method.findSuperMethods().length != 0) {
604 if (!weAreSureThereAreNoUsages(method, progress, helper)) {
611 private static boolean weAreSureThereAreNoUsages(PsiMember member, ProgressIndicator progress, GlobalUsageHelper helper) {
612 if (!helper.shouldCheckUsages(member)) return false;
614 String name = member.getName();
615 if (name == null) return false;
616 SearchScope useScope = member.getUseScope();
617 Project project = member.getProject();
618 if (useScope instanceof GlobalSearchScope) {
619 // some classes may have references from within XML outside dependent modules, e.g. our actions
620 if (member instanceof PsiClass) {
621 useScope = GlobalSearchScope.projectScope(project).uniteWith((GlobalSearchScope)useScope);
624 PsiSearchHelper.SearchCostResult cheapEnough = PsiSearchHelper.SERVICE.getInstance(project).isCheapEnoughToSearch(name, (GlobalSearchScope)useScope,
625 helper.shouldIgnoreUsagesInCurrentFile() ? member.getContainingFile() : null,
627 if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false;
629 //search usages if it cheap
630 //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
631 if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
632 if (!canBeReferencedViaWeirdNames(member)) return true;
635 FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(project)).getFindUsagesManager();
636 FindUsagesHandler handler = new JavaFindUsagesHandler(member, new JavaFindUsagesHandlerFactory(project));
637 FindUsagesOptions findUsagesOptions = handler.getFindUsagesOptions();
638 findUsagesOptions.searchScope = useScope;
639 return !findUsagesManager.isUsed(member, findUsagesOptions);
642 private static boolean isEnumValuesMethodUsed(PsiMember member, ProgressIndicator progress, GlobalUsageHelper helper) {
643 final PsiClassImpl containingClass = (PsiClassImpl)member.getContainingClass();
644 if (containingClass == null) return true;
645 final PsiMethod valuesMethod = containingClass.getValuesMethod();
646 if (valuesMethod == null) return true;
647 return isMethodReferenced(valuesMethod, progress, helper);
650 private static boolean canBeReferencedViaWeirdNames(PsiMember member) {
651 if (member instanceof PsiClass) return false;
652 PsiFile containingFile = member.getContainingFile();
653 if (!(containingFile instanceof PsiJavaFile)) return true; // Groovy field can be referenced from Java by getter
654 if (member instanceof PsiField) return false; //Java field cannot be referenced by anything but its name
655 if (member instanceof PsiMethod) {
656 return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member); //Java accessors can be referenced by field name from Groovy
662 private HighlightInfo processClass(PsiClass aClass, ProgressIndicator progress, GlobalUsageHelper helper) {
663 if (isClassUsed(aClass, progress, helper)) return null;
666 HighlightDisplayKey highlightDisplayKey;
667 HighlightInfoType highlightInfoType;
668 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
669 pattern = aClass.isInterface()
670 ? "private.inner.interface.is.not.used"
671 : "private.inner.class.is.not.used";
672 highlightDisplayKey = myUnusedSymbolKey;
673 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
675 else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
676 pattern = "local.class.is.not.used";
677 highlightDisplayKey = myUnusedSymbolKey;
678 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
680 else if (aClass instanceof PsiTypeParameter) {
681 pattern = "type.parameter.is.not.used";
682 highlightDisplayKey = myUnusedSymbolKey;
683 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
686 pattern = "class.is.not.used";
687 highlightDisplayKey = myDeadCodeKey;
688 highlightInfoType = myDeadCodeInfoType;
690 return formatUnusedSymbolHighlightInfo(pattern, aClass, "classes", highlightDisplayKey, highlightInfoType);
693 public static boolean isClassUsed(PsiClass aClass, ProgressIndicator progress, GlobalUsageHelper helper) {
694 if (aClass == null) return true;
695 Boolean result = helper.unusedClassCache.get(aClass);
696 if (result == null) {
697 result = isReallyUsed(aClass, progress, helper);
698 helper.unusedClassCache.put(aClass, result);
703 private static boolean isReallyUsed(PsiClass aClass, ProgressIndicator progress, GlobalUsageHelper helper) {
704 if (isImplicitUsage(aClass, progress) || helper.isLocallyUsed(aClass)) return true;
705 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
706 aClass.getParent() instanceof PsiDeclarationStatement ||
707 aClass instanceof PsiTypeParameter) return false;
708 return !weAreSureThereAreNoUsages(aClass, progress, helper);
711 private static HighlightInfo formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
712 final PsiNameIdentifierOwner aClass,
713 final String element,
714 final HighlightDisplayKey highlightDisplayKey,
715 final HighlightInfoType highlightInfoType) {
716 String symbolName = aClass.getName();
717 String message = JavaErrorMessages.message(pattern, symbolName);
718 PsiElement identifier = aClass.getNameIdentifier();
719 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
720 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), highlightDisplayKey);
721 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
723 public boolean process(final String annoName) {
724 QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, element, aClass.getProject()));
728 return highlightInfo;
732 private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
733 // jsp include directive hack
734 if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null;
736 if (PsiUtilCore.hasErrorElementChild(importStatement)) return null;
738 boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
739 if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
740 //check import from same package
741 String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
742 PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
743 PsiElement resolved = reference == null ? null : reference.resolve();
744 if (resolved instanceof PsiPackage) {
745 isRedundant = packageName.equals(((PsiPackage)resolved).getQualifiedName());
747 else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
748 String qName = ((PsiClass)resolved).getQualifiedName();
750 String name = ((PsiClass)resolved).getName();
751 isRedundant = qName.equals(packageName + '.' + name);
757 return registerRedundantImport(importStatement, unusedImportKey);
760 int entryIndex = myStyleManager.findEntryIndex(importStatement);
761 if (entryIndex < myCurrentEntryIndex) {
762 myHasMissortedImports = true;
764 myCurrentEntryIndex = entryIndex;
769 private HighlightInfo registerRedundantImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
770 HighlightInfo info = HighlightInfo.createHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT, importStatement, InspectionsBundle.message("unused.import.statement"));
772 QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey);
773 QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
774 myHasRedundantImports = true;
778 private boolean timeToOptimizeImports() {
779 if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
781 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
782 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
783 // dont optimize out imports in JSP since it can be included in other JSP
784 if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof JspFile) return false;
786 if (!codeAnalyzer.isErrorAnalyzingFinished(file)) return false;
787 boolean errors = containsErrorsPreventingOptimize(file);
789 return !errors && codeAnalyzer.canChangeFileSilently(myFile);
792 private boolean containsErrorsPreventingOptimize(PsiFile file) {
793 // ignore unresolved imports errors
794 PsiImportList importList = ((PsiJavaFile)file).getImportList();
795 final TextRange importsRange = importList == null ? TextRange.EMPTY_RANGE : importList.getTextRange();
796 boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
798 public boolean process(HighlightInfo error) {
799 int infoStart = error.getActualStartOffset();
800 int infoEnd = error.getActualEndOffset();
802 return importsRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);
806 return hasErrorsExceptUnresolvedImports;
809 private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) {
810 return method.isConstructor() &&
811 method.getParameterList().getParametersCount() == 0 &&
812 containingClass != null &&
813 containingClass.getConstructors().length == 1;