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 gnu.trove.TObjectIntHashMap;
86 import org.jetbrains.annotations.NotNull;
87 import org.jetbrains.annotations.Nullable;
88 import org.jetbrains.annotations.PropertyKey;
92 public class PostHighlightingPass extends TextEditorHighlightingPass {
93 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
94 private RefCountHolder myRefCountHolder;
95 private final PsiFile myFile;
96 @Nullable private final Editor myEditor;
97 private final int myStartOffset;
98 private final int myEndOffset;
100 private Collection<HighlightInfo> myHighlights;
101 private boolean myHasRedundantImports;
102 private final JavaCodeStyleManager myStyleManager;
103 private int myCurrentEntryIndex;
104 private boolean myHasMissortedImports;
105 private final ImplicitUsageProvider[] myImplicitUsageProviders;
106 private UnusedDeclarationInspection myDeadCodeInspection;
107 private UnusedSymbolLocalInspection myUnusedSymbolInspection;
108 private HighlightDisplayKey myUnusedSymbolKey;
109 private boolean myDeadCodeEnabled;
110 private boolean myInLibrary;
111 private HighlightDisplayKey myDeadCodeKey;
112 private HighlightInfoType myDeadCodeInfoType;
113 private UnusedParametersInspection myUnusedParametersInspection;
115 PostHighlightingPass(@NotNull Project project,
116 @NotNull PsiFile file,
117 @Nullable Editor editor,
118 @NotNull Document document) {
119 super(project, document, true);
123 myEndOffset = file.getTextLength();
125 myStyleManager = JavaCodeStyleManager.getInstance(myProject);
126 myCurrentEntryIndex = -1;
128 myImplicitUsageProviders = Extensions.getExtensions(ImplicitUsageProvider.EP_NAME);
132 public void doCollectInformation(final ProgressIndicator progress) {
133 DaemonCodeAnalyzer daemonCodeAnalyzer = DaemonCodeAnalyzer.getInstance(myProject);
134 final FileStatusMap fileStatusMap = ((DaemonCodeAnalyzerImpl)daemonCodeAnalyzer).getFileStatusMap();
135 final List<HighlightInfo> highlights = new ArrayList<HighlightInfo>();
136 final FileViewProvider viewProvider = myFile.getViewProvider();
137 final Set<Language> relevantLanguages = viewProvider.getLanguages();
138 final Set<PsiElement> elementSet = new THashSet<PsiElement>();
139 for (Language language : relevantLanguages) {
140 PsiElement psiRoot = viewProvider.getPsi(language);
141 if (!HighlightLevelUtil.shouldHighlight(psiRoot)) continue;
142 List<PsiElement> elements = CollectHighlightsUtil.getElementsInRange(psiRoot, myStartOffset, myEndOffset);
143 elementSet.addAll(elements);
146 ProjectFileIndex fileIndex = ProjectRootManager.getInstance(myProject).getFileIndex();
147 VirtualFile virtualFile = viewProvider.getVirtualFile();
148 myInLibrary = fileIndex.isInLibraryClasses(virtualFile) || fileIndex.isInLibrarySource(virtualFile);
150 myRefCountHolder = RefCountHolder.getInstance(myFile);
151 if (!myRefCountHolder.retrieveUnusedReferencesInfo(new Runnable() {
154 boolean errorFound = collectHighlights(elementSet, highlights, progress);
155 myHighlights = highlights;
157 fileStatusMap.setErrorFoundFlag(myDocument, true);
161 // we must be sure GHP will restart
162 fileStatusMap.markFileScopeDirty(getDocument(), Pass.UPDATE_ALL);
163 GeneralHighlightingPass.cancelAndRestartDaemonLater(progress, myProject, this);
168 public List<HighlightInfo> getInfos() {
169 return myHighlights == null ? null : new ArrayList<HighlightInfo>(myHighlights);
173 public void doApplyInformationToEditor() {
174 if (myHighlights == null) return;
175 UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myHighlights, getColorsScheme(), Pass.POST_UPDATE_ALL);
176 PostHighlightingPassFactory.markFileUpToDate(myFile);
178 Editor editor = myEditor;
179 if (editor != null && timeToOptimizeImports()) {
180 optimizeImportsOnTheFly(editor);
184 private void optimizeImportsOnTheFly(@NotNull final Editor editor) {
185 if (myHasRedundantImports || myHasMissortedImports) {
186 final OptimizeImportsFix optimizeImportsFix = new OptimizeImportsFix();
187 if (optimizeImportsFix.isAvailable(myProject, editor, myFile) && myFile.isWritable()) {
188 invokeOnTheFlyImportOptimizer(new Runnable() {
191 optimizeImportsFix.invoke(myProject, editor, myFile);
198 public static void invokeOnTheFlyImportOptimizer(@NotNull final Runnable runnable, @NotNull final PsiFile file, @NotNull final Editor editor) {
199 final long stamp = editor.getDocument().getModificationStamp();
200 ApplicationManager.getApplication().invokeLater(new Runnable() {
203 if (file.getProject().isDisposed() || editor.isDisposed() || editor.getDocument().getModificationStamp() != stamp) return;
204 //no need to optimize imports on the fly during undo/redo
205 final UndoManager undoManager = UndoManager.getInstance(editor.getProject());
206 if (undoManager.isUndoInProgress() || undoManager.isRedoInProgress()) return;
207 PsiDocumentManager.getInstance(file.getProject()).commitAllDocuments();
208 String beforeText = file.getText();
209 final long oldStamp = editor.getDocument().getModificationStamp();
210 CommandProcessor.getInstance().runUndoTransparentAction(new Runnable() {
213 ApplicationManager.getApplication().runWriteAction(runnable);
216 if (oldStamp != editor.getDocument().getModificationStamp()) {
217 String afterText = file.getText();
218 if (Comparing.strEqual(beforeText, afterText)) {
219 LOG.error(LogMessageEx.createEvent("Import optimizer hasn't optimized any imports", file.getViewProvider().getVirtualFile().getPath(),
220 new Attachment(file.getViewProvider().getVirtualFile())));
227 // returns true if error highlight was created
228 private boolean collectHighlights(@NotNull Collection<PsiElement> elements, @NotNull final List<HighlightInfo> result, @NotNull ProgressIndicator progress) throws ProcessCanceledException {
229 ApplicationManager.getApplication().assertReadAccessAllowed();
231 InspectionProfile profile = InspectionProjectProfileManager.getInstance(myProject).getInspectionProfile();
232 myUnusedSymbolKey = HighlightDisplayKey.find(UnusedSymbolLocalInspection.SHORT_NAME);
233 boolean unusedSymbolEnabled = profile.isToolEnabled(myUnusedSymbolKey, myFile);
234 HighlightDisplayKey unusedImportKey = HighlightDisplayKey.find(UnusedImportLocalInspection.SHORT_NAME);
235 boolean unusedImportEnabled = profile.isToolEnabled(unusedImportKey, myFile);
236 LocalInspectionToolWrapper unusedSymbolTool = (LocalInspectionToolWrapper)profile.getInspectionTool(UnusedSymbolLocalInspection.SHORT_NAME,
238 myUnusedSymbolInspection = unusedSymbolTool == null ? null : (UnusedSymbolLocalInspection)unusedSymbolTool.getTool();
239 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedSymbolInspection != null);
241 myDeadCodeKey = HighlightDisplayKey.find(UnusedDeclarationInspection.SHORT_NAME);
242 myDeadCodeInspection = (UnusedDeclarationInspection)profile.getInspectionTool(UnusedDeclarationInspection.SHORT_NAME, myFile);
243 myDeadCodeEnabled = profile.isToolEnabled(myDeadCodeKey, myFile);
245 final InspectionProfileEntry inspectionTool = profile.getInspectionTool(UnusedParametersInspection.SHORT_NAME, myFile);
246 myUnusedParametersInspection = inspectionTool != null ? (UnusedParametersInspection)((GlobalInspectionToolWrapper)inspectionTool).getTool() : null;
247 LOG.assertTrue(ApplicationManager.getApplication().isUnitTestMode() || myUnusedParametersInspection != null);
248 if (unusedImportEnabled && JspPsiUtil.isInJspFile(myFile)) {
249 final JspFile jspFile = JspPsiUtil.getJspFile(myFile);
250 if (jspFile != null) {
251 unusedImportEnabled = !JspSpiUtil.isIncludedOrIncludesSomething(jspFile);
255 myDeadCodeInfoType = myDeadCodeKey == null ? null : new HighlightInfoType.HighlightInfoTypeImpl(profile.getErrorLevel(myDeadCodeKey, myFile).getSeverity(), HighlightInfoType.UNUSED_SYMBOL.getAttributesKey());
257 boolean errorFound = false;
258 if (unusedSymbolEnabled) {
259 for (PsiElement element : elements) {
260 progress.checkCanceled();
261 if (element instanceof PsiIdentifier) {
262 PsiIdentifier identifier = (PsiIdentifier)element;
263 HighlightInfo info = processIdentifier(identifier, progress);
265 errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
271 if (unusedImportEnabled && myFile instanceof PsiJavaFile && HighlightLevelUtil.shouldHighlight(myFile)) {
272 PsiImportList importList = ((PsiJavaFile)myFile).getImportList();
273 if (importList != null) {
274 final PsiImportStatementBase[] imports = importList.getAllImportStatements();
275 for (PsiImportStatementBase statement : imports) {
276 progress.checkCanceled();
277 final HighlightInfo info = processImport(statement, unusedImportKey);
279 errorFound |= info.getSeverity() == HighlightSeverity.ERROR;
290 private HighlightInfo processIdentifier(PsiIdentifier identifier, ProgressIndicator progress) {
291 if (InspectionManagerEx.inspectionResultSuppressed(identifier, myUnusedSymbolInspection)) return null;
292 PsiElement parent = identifier.getParent();
293 if (PsiUtilCore.hasErrorElementChild(parent)) return null;
295 if (parent instanceof PsiLocalVariable && myUnusedSymbolInspection.LOCAL_VARIABLE) {
296 return processLocalVariable((PsiLocalVariable)parent, progress);
298 if (parent instanceof PsiField && myUnusedSymbolInspection.FIELD) {
299 return processField((PsiField)parent, identifier, progress);
301 if (parent instanceof PsiParameter && myUnusedSymbolInspection.PARAMETER) {
302 if (InspectionManagerEx.isSuppressed(identifier, UnusedParametersInspection.SHORT_NAME)) return null;
303 return processParameter((PsiParameter)parent, progress);
305 if (parent instanceof PsiMethod && myUnusedSymbolInspection.METHOD) {
306 return processMethod((PsiMethod)parent, progress);
308 if (parent instanceof PsiClass && myUnusedSymbolInspection.CLASS) {
309 return processClass((PsiClass)parent, progress);
316 private HighlightInfo processLocalVariable(PsiLocalVariable variable, ProgressIndicator progress) {
317 PsiIdentifier identifier = variable.getNameIdentifier();
318 if (identifier == null) return null;
319 if (isImplicitUsage(variable, progress)) return null;
320 if (!myRefCountHolder.isReferenced(variable)) {
321 String message = JavaErrorMessages.message("local.variable.is.never.used", identifier.getText());
322 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
323 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
324 return highlightInfo;
327 boolean referenced = myRefCountHolder.isReferencedForRead(variable);
328 if (!referenced && !isImplicitRead(variable, progress)) {
329 String message = JavaErrorMessages.message("local.variable.is.not.used.for.reading", identifier.getText());
330 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
331 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(variable), myUnusedSymbolKey);
332 return highlightInfo;
335 if (!variable.hasInitializer()) {
336 referenced = myRefCountHolder.isReferencedForWrite(variable);
337 if (!referenced && !isImplicitWrite(variable, progress)) {
338 String message = JavaErrorMessages.message("local.variable.is.not.assigned", identifier.getText());
339 final HighlightInfo unusedSymbolInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
340 QuickFixAction.registerQuickFixAction(unusedSymbolInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
341 return unusedSymbolInfo;
349 private boolean isImplicitUsage(final PsiModifierListOwner element, ProgressIndicator progress) {
350 if (UnusedSymbolLocalInspection.isInjected(element)) return true;
351 for (ImplicitUsageProvider provider : myImplicitUsageProviders) {
352 progress.checkCanceled();
353 if (provider.isImplicitUsage(element)) {
361 private boolean isImplicitRead(final PsiVariable element, ProgressIndicator progress) {
362 for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
363 progress.checkCanceled();
364 if (provider.isImplicitRead(element)) {
368 return UnusedSymbolLocalInspection.isInjected(element);
371 private boolean isImplicitWrite(final PsiVariable element, ProgressIndicator progress) {
372 for(ImplicitUsageProvider provider: myImplicitUsageProviders) {
373 progress.checkCanceled();
374 if (provider.isImplicitWrite(element)) {
378 return UnusedSymbolLocalInspection.isInjected(element);
381 private static HighlightInfo createUnusedSymbolInfo(PsiElement element, String message, final HighlightInfoType highlightInfoType) {
382 HighlightInfo info = HighlightInfo.createHighlightInfo(highlightInfoType, element, message);
383 UnusedDeclarationFixProvider[] fixProviders = Extensions.getExtensions(UnusedDeclarationFixProvider.EP_NAME);
384 for (UnusedDeclarationFixProvider provider : fixProviders) {
385 IntentionAction[] fixes = provider.getQuickFixes(element);
386 for (IntentionAction fix : fixes) {
387 QuickFixAction.registerQuickFixAction(info, fix);
394 private HighlightInfo processField(final PsiField field, final PsiIdentifier identifier, ProgressIndicator progress) {
395 if (field.hasModifierProperty(PsiModifier.PRIVATE)) {
396 if (!myRefCountHolder.isReferenced(field) && !isImplicitUsage(field, progress)) {
397 if (HighlightUtil.isSerializationImplicitlyUsedField(field)) {
400 String message = JavaErrorMessages.message("private.field.is.not.used", identifier.getText());
402 HighlightInfo highlightInfo = suggestionsToMakeFieldUsed(field, identifier, message);
403 if (!field.hasInitializer()) {
404 QuickFixAction.registerQuickFixAction(highlightInfo, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
406 return highlightInfo;
409 final boolean readReferenced = myRefCountHolder.isReferencedForRead(field);
410 if (!readReferenced && !isImplicitRead(field, progress)) {
411 String message = JavaErrorMessages.message("private.field.is.not.used.for.reading", identifier.getText());
412 return suggestionsToMakeFieldUsed(field, identifier, message);
415 if (field.hasInitializer()) {
418 final boolean writeReferenced = myRefCountHolder.isReferencedForWrite(field);
419 if (!writeReferenced && !isImplicitWrite(field, progress)) {
420 String message = JavaErrorMessages.message("private.field.is.not.assigned", identifier.getText());
421 final HighlightInfo info = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
423 QuickFixAction.registerQuickFixAction(info, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
424 QuickFixAction.registerQuickFixAction(info, HighlightMethodUtil.getFixRange(field), new CreateConstructorParameterFromFieldFix(field), null);
425 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(field, new Processor<String>() {
427 public boolean process(final String annoName) {
428 QuickFixAction.registerQuickFixAction(info, UnusedSymbolLocalInspection.createQuickFix(annoName, "fields", field.getProject()));
435 else if (isImplicitUsage(field, progress)) {
438 else if (!myRefCountHolder.isReferenced(field) && weAreSureThereAreNoUsages(field, progress)) {
439 return formatUnusedSymbolHighlightInfo("field.is.not.used", field, "fields", myDeadCodeKey, myDeadCodeInfoType);
444 private HighlightInfo suggestionsToMakeFieldUsed(final PsiField field, final PsiIdentifier identifier, final String message) {
445 HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
446 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedVariableFix(field), myUnusedSymbolKey);
447 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, false, field), myUnusedSymbolKey);
448 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(false, true, field), myUnusedSymbolKey);
449 QuickFixAction.registerQuickFixAction(highlightInfo, new CreateGetterOrSetterFix(true, true, field), myUnusedSymbolKey);
450 return highlightInfo;
453 private static boolean isOverriddenOrOverrides(PsiMethod method) {
454 boolean overrides = SuperMethodsSearch.search(method, null, true, false).findFirst() != null;
455 return overrides || OverridingMethodsSearch.search(method).findFirst() != null;
459 private HighlightInfo processParameter(PsiParameter parameter, ProgressIndicator progress) {
460 PsiElement declarationScope = parameter.getDeclarationScope();
461 if (declarationScope instanceof PsiMethod) {
462 PsiMethod method = (PsiMethod)declarationScope;
463 if (PsiUtilCore.hasErrorElementChild(method)) return null;
464 if ((method.isConstructor() ||
465 method.hasModifierProperty(PsiModifier.PRIVATE) ||
466 method.hasModifierProperty(PsiModifier.STATIC) ||
467 !method.hasModifierProperty(PsiModifier.ABSTRACT) &&
468 myUnusedSymbolInspection.REPORT_PARAMETER_FOR_PUBLIC_METHODS &&
469 !isOverriddenOrOverrides(method)) &&
470 !method.hasModifierProperty(PsiModifier.NATIVE) &&
471 !HighlightMethodUtil.isSerializationRelatedMethod(method, method.getContainingClass()) &&
472 !PsiClassImplUtil.isMainMethod(method)) {
473 if (UnusedSymbolLocalInspection.isInjected(method)) return null;
474 HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
475 if (highlightInfo != null) {
476 final ArrayList<IntentionAction> options = new ArrayList<IntentionAction>();
477 options.addAll(IntentionManager.getInstance().getStandardIntentionOptions(myUnusedSymbolKey, myFile));
478 if (myUnusedParametersInspection != null) {
479 Collections.addAll(options, myUnusedParametersInspection.getSuppressActions(parameter));
481 //need suppress from Unused Parameters but settings from Unused Symbol
482 QuickFixAction.registerQuickFixAction(highlightInfo, new RemoveUnusedParameterFix(parameter),
483 options, HighlightDisplayKey.getDisplayNameByKey(myUnusedSymbolKey));
484 return highlightInfo;
488 else if (declarationScope instanceof PsiForeachStatement) {
489 HighlightInfo highlightInfo = checkUnusedParameter(parameter, progress);
490 if (highlightInfo != null) {
491 QuickFixAction.registerQuickFixAction(highlightInfo, new EmptyIntentionAction(UnusedSymbolLocalInspection.DISPLAY_NAME), myUnusedSymbolKey);
492 return highlightInfo;
500 private HighlightInfo checkUnusedParameter(final PsiParameter parameter, ProgressIndicator progress) {
501 if (!myRefCountHolder.isReferenced(parameter) && !isImplicitUsage(parameter, progress)) {
502 PsiIdentifier identifier = parameter.getNameIdentifier();
503 assert identifier != null;
504 String message = JavaErrorMessages.message("parameter.is.not.used", identifier.getText());
505 return createUnusedSymbolInfo(identifier, message, HighlightInfoType.UNUSED_SYMBOL);
511 private HighlightInfo processMethod(final PsiMethod method, ProgressIndicator progress) {
512 boolean isPrivate = method.hasModifierProperty(PsiModifier.PRIVATE);
513 PsiClass containingClass = method.getContainingClass();
514 if (isMethodReferenced(method, progress, isPrivate, containingClass)) return null;
515 HighlightInfoType highlightInfoType;
516 HighlightDisplayKey highlightDisplayKey;
519 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
520 highlightDisplayKey = myUnusedSymbolKey;
521 key = method.isConstructor() ? "private.constructor.is.not.used" : "private.method.is.not.used";
524 highlightInfoType = myDeadCodeInfoType;
525 highlightDisplayKey = myDeadCodeKey;
526 key = method.isConstructor() ? "constructor.is.not.used" : "method.is.not.used";
528 String symbolName = HighlightMessageUtil.getSymbolName(method, PsiSubstitutor.EMPTY);
529 String message = JavaErrorMessages.message(key, symbolName);
530 PsiIdentifier identifier = method.getNameIdentifier();
531 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
532 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(method), highlightDisplayKey);
533 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes(method, new Processor<String>() {
535 public boolean process(final String annoName) {
536 QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, "methods", method.getProject()));
540 if (method.getReturnType() != null || containingClass != null && Comparing.strEqual(containingClass.getName(), method.getName())) {
541 //ignore methods with deleted return types as they are always marked as unused without any reason
542 ChangeSignatureGestureDetector.getInstance(myProject).dismissForElement(method);
544 return highlightInfo;
547 private boolean isMethodReferenced(PsiMethod method, ProgressIndicator progress, boolean aPrivate, PsiClass containingClass) {
548 if (myRefCountHolder.isReferenced(method)) return true;
550 if (HighlightMethodUtil.isSerializationRelatedMethod(method, containingClass)) return true;
552 if (isIntentionalPrivateConstructor(method, containingClass)) {
555 if (isImplicitUsage(method, progress)) {
560 //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
561 if (containingClass != null && method.isConstructor()
562 && containingClass.getConstructors().length == 1
563 && isClassUnused(containingClass, progress) == USED) {
566 if (isImplicitUsage(method, progress)) return true;
568 if (method.findSuperMethods().length != 0) {
571 if (!weAreSureThereAreNoUsages(method, progress)) {
578 private boolean weAreSureThereAreNoUsages(PsiMember member, ProgressIndicator progress) {
579 if (myInLibrary) return false;
580 if (!myDeadCodeEnabled) return false;
581 if (myDeadCodeInspection.isEntryPoint(member)) return false;
583 String name = member.getName();
584 if (name == null) return false;
585 SearchScope useScope = member.getUseScope();
586 if (!(useScope instanceof GlobalSearchScope)) return false;
587 GlobalSearchScope scope = (GlobalSearchScope)useScope;
588 // some classes may have references from within XML outside dependent modules, e.g. our actions
589 if (member instanceof PsiClass) scope = GlobalSearchScope.projectScope(myProject).uniteWith(scope);
591 PsiSearchHelper.SearchCostResult cheapEnough = PsiSearchHelper.SERVICE.getInstance(myFile.getProject())
592 .isCheapEnoughToSearch(name, scope, myFile, progress);
593 if (cheapEnough == PsiSearchHelper.SearchCostResult.TOO_MANY_OCCURRENCES) return false;
595 //search usages if it cheap
596 //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
597 if (cheapEnough == PsiSearchHelper.SearchCostResult.ZERO_OCCURRENCES) {
598 if (member instanceof PsiEnumConstant) {
599 return !isEnumValuesMethodUsed(member, progress);
601 if (!canBeReferencedViaWeirdNames(member)) return true;
603 FindUsagesManager findUsagesManager = ((FindManagerImpl)FindManager.getInstance(myProject)).getFindUsagesManager();
604 FindUsagesOptions findUsagesOptions;
605 if (member instanceof PsiClass) {
606 findUsagesOptions = new JavaClassFindUsagesOptions(myProject);
608 else if (member instanceof PsiMethod) {
609 findUsagesOptions = new JavaMethodFindUsagesOptions(myProject);
611 else if (member instanceof PsiField) {
612 findUsagesOptions = new JavaVariableFindUsagesOptions(myProject);
615 LOG.error("unknown member: " + member);
618 findUsagesOptions.searchScope = scope;
620 boolean used = findUsagesManager.isUsed(member, findUsagesOptions);
622 if (!used && member instanceof PsiEnumConstant) {
623 return !isEnumValuesMethodUsed(member, progress);
628 private boolean isEnumValuesMethodUsed(PsiMember member, ProgressIndicator progress) {
629 final PsiClassImpl containingClass = (PsiClassImpl)member.getContainingClass();
630 if (containingClass == null) return true;
631 final PsiMethod valuesMethod = containingClass.getValuesMethod();
632 if (valuesMethod == null) return true;
633 boolean isPrivate = valuesMethod.hasModifierProperty(PsiModifier.PRIVATE);
634 return isMethodReferenced(valuesMethod, progress, isPrivate, containingClass);
637 private static boolean canBeReferencedViaWeirdNames(PsiMember member) {
638 if (member instanceof PsiClass) return false;
639 PsiFile containingFile = member.getContainingFile();
640 if (!(containingFile instanceof PsiJavaFile)) return true; // Groovy field can be referenced from Java by getter
641 if (member instanceof PsiField) return false; //Java field cannot be referenced by anything but its name
642 if (member instanceof PsiMethod) {
643 return PropertyUtil.isSimplePropertyAccessor((PsiMethod)member); //Java accessors can be referenced by field name from Groovy
649 private HighlightInfo processClass(PsiClass aClass, ProgressIndicator progress) {
650 int usage = isClassUnused(aClass, progress);
651 if (usage == USED) return null;
654 HighlightDisplayKey highlightDisplayKey;
655 HighlightInfoType highlightInfoType;
656 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE)) {
657 pattern = aClass.isInterface()
658 ? "private.inner.interface.is.not.used"
659 : "private.inner.class.is.not.used";
660 highlightDisplayKey = myUnusedSymbolKey;
661 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
663 else if (aClass.getParent() instanceof PsiDeclarationStatement) { // local class
664 pattern = "local.class.is.not.used";
665 highlightDisplayKey = myUnusedSymbolKey;
666 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
668 else if (aClass instanceof PsiTypeParameter) {
669 pattern = "type.parameter.is.not.used";
670 highlightDisplayKey = myUnusedSymbolKey;
671 highlightInfoType = HighlightInfoType.UNUSED_SYMBOL;
674 pattern = "class.is.not.used";
675 highlightDisplayKey = myDeadCodeKey;
676 highlightInfoType = myDeadCodeInfoType;
678 return formatUnusedSymbolHighlightInfo(pattern, aClass, "classes", highlightDisplayKey, highlightInfoType);
681 private static final int USED = 1;
682 private static final int UNUSED_LOCALLY = 2;
683 private static final int UNUSED_GLOBALLY = 3;
684 private final TObjectIntHashMap<PsiClass> unusedClassCache = new TObjectIntHashMap<PsiClass>();
685 private int isClassUnused(PsiClass aClass, ProgressIndicator progress) {
686 if (aClass == null) return USED;
687 int result = unusedClassCache.get(aClass);
689 result = isReallyUnused(aClass, progress);
690 unusedClassCache.put(aClass, result);
695 private int isReallyUnused(PsiClass aClass, ProgressIndicator progress) {
696 if (isImplicitUsage(aClass, progress) || myRefCountHolder.isReferenced(aClass)) return USED;
697 if (aClass.getContainingClass() != null && aClass.hasModifierProperty(PsiModifier.PRIVATE) ||
698 aClass.getParent() instanceof PsiDeclarationStatement ||
699 aClass instanceof PsiTypeParameter) return UNUSED_LOCALLY;
700 if (weAreSureThereAreNoUsages(aClass, progress)) return UNUSED_GLOBALLY;
704 private static HighlightInfo formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle = JavaErrorMessages.BUNDLE) String pattern,
705 final PsiNameIdentifierOwner aClass,
706 final String element,
707 final HighlightDisplayKey highlightDisplayKey,
708 final HighlightInfoType highlightInfoType) {
709 String symbolName = aClass.getName();
710 String message = JavaErrorMessages.message(pattern, symbolName);
711 PsiElement identifier = aClass.getNameIdentifier();
712 final HighlightInfo highlightInfo = createUnusedSymbolInfo(identifier, message, highlightInfoType);
713 QuickFixAction.registerQuickFixAction(highlightInfo, new SafeDeleteFix(aClass), highlightDisplayKey);
714 SpecialAnnotationsUtil.createAddToSpecialAnnotationFixes((PsiModifierListOwner)aClass, new Processor<String>() {
716 public boolean process(final String annoName) {
717 QuickFixAction.registerQuickFixAction(highlightInfo, UnusedSymbolLocalInspection.createQuickFix(annoName, element, aClass.getProject()));
721 return highlightInfo;
725 private HighlightInfo processImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
726 // jsp include directive hack
727 if (importStatement instanceof JspxImportStatement && ((JspxImportStatement)importStatement).isForeignFileImport()) return null;
729 if (PsiUtilCore.hasErrorElementChild(importStatement)) return null;
731 boolean isRedundant = myRefCountHolder.isRedundant(importStatement);
732 if (!isRedundant && !(importStatement instanceof PsiImportStaticStatement)) {
733 //check import from same package
734 String packageName = ((PsiClassOwner)importStatement.getContainingFile()).getPackageName();
735 PsiJavaCodeReferenceElement reference = importStatement.getImportReference();
736 PsiElement resolved = reference == null ? null : reference.resolve();
737 if (resolved instanceof PsiPackage) {
738 isRedundant = packageName.equals(((PsiPackage)resolved).getQualifiedName());
740 else if (resolved instanceof PsiClass && !importStatement.isOnDemand()) {
741 String qName = ((PsiClass)resolved).getQualifiedName();
743 String name = ((PsiClass)resolved).getName();
744 isRedundant = qName.equals(packageName + '.' + name);
750 return registerRedundantImport(importStatement, unusedImportKey);
753 int entryIndex = myStyleManager.findEntryIndex(importStatement);
754 if (entryIndex < myCurrentEntryIndex) {
755 myHasMissortedImports = true;
757 myCurrentEntryIndex = entryIndex;
762 private HighlightInfo registerRedundantImport(PsiImportStatementBase importStatement, HighlightDisplayKey unusedImportKey) {
763 HighlightInfo info = HighlightInfo.createHighlightInfo(JavaHighlightInfoTypes.UNUSED_IMPORT, importStatement, InspectionsBundle.message("unused.import.statement"));
765 QuickFixAction.registerQuickFixAction(info, new OptimizeImportsFix(), unusedImportKey);
766 QuickFixAction.registerQuickFixAction(info, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey);
767 myHasRedundantImports = true;
771 private boolean timeToOptimizeImports() {
772 if (!CodeInsightSettings.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY) return false;
774 DaemonCodeAnalyzerImpl codeAnalyzer = (DaemonCodeAnalyzerImpl)DaemonCodeAnalyzer.getInstance(myProject);
775 PsiFile file = PsiDocumentManager.getInstance(myProject).getPsiFile(myDocument);
776 // dont optimize out imports in JSP since it can be included in other JSP
777 if (file == null || !codeAnalyzer.isHighlightingAvailable(file) || !(file instanceof PsiJavaFile) || file instanceof JspFile) return false;
779 if (!codeAnalyzer.isErrorAnalyzingFinished(file)) return false;
780 boolean errors = containsErrorsPreventingOptimize(file);
782 return !errors && codeAnalyzer.canChangeFileSilently(myFile);
785 private boolean containsErrorsPreventingOptimize(PsiFile file) {
786 // ignore unresolved imports errors
787 PsiImportList importList = ((PsiJavaFile)file).getImportList();
788 final TextRange importsRange = importList == null ? TextRange.EMPTY_RANGE : importList.getTextRange();
789 boolean hasErrorsExceptUnresolvedImports = !DaemonCodeAnalyzerImpl.processHighlights(myDocument, myProject, HighlightSeverity.ERROR, 0, myDocument.getTextLength(), new Processor<HighlightInfo>() {
791 public boolean process(HighlightInfo error) {
792 int infoStart = error.getActualStartOffset();
793 int infoEnd = error.getActualEndOffset();
795 return importsRange.containsRange(infoStart,infoEnd) && error.type.equals(HighlightInfoType.WRONG_REF);
799 return hasErrorsExceptUnresolvedImports;
802 private static boolean isIntentionalPrivateConstructor(PsiMethod method, PsiClass containingClass) {
803 return method.isConstructor() &&
804 method.getParameterList().getParametersCount() == 0 &&
805 containingClass != null &&
806 containingClass.getConstructors().length == 1;