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
.codeInspection
.InspectionProfile
;
32 import com
.intellij
.codeInspection
.InspectionsBundle
;
33 import com
.intellij
.codeInspection
.deadCode
.UnusedDeclarationInspection
;
34 import com
.intellij
.codeInspection
.ex
.InspectionManagerEx
;
35 import com
.intellij
.codeInspection
.ex
.LocalInspectionToolWrapper
;
36 import com
.intellij
.codeInspection
.unusedImport
.UnusedImportLocalInspection
;
37 import com
.intellij
.codeInspection
.unusedSymbol
.UnusedSymbolLocalInspection
;
38 import com
.intellij
.codeInspection
.util
.SpecialAnnotationsUtil
;
39 import com
.intellij
.lang
.Language
;
40 import com
.intellij
.lang
.annotation
.HighlightSeverity
;
41 import com
.intellij
.openapi
.application
.ApplicationManager
;
42 import com
.intellij
.openapi
.command
.CommandProcessor
;
43 import com
.intellij
.openapi
.diagnostic
.Logger
;
44 import com
.intellij
.openapi
.editor
.Document
;
45 import com
.intellij
.openapi
.editor
.Editor
;
46 import com
.intellij
.openapi
.extensions
.Extensions
;
47 import com
.intellij
.openapi
.progress
.ProcessCanceledException
;
48 import com
.intellij
.openapi
.progress
.ProgressIndicator
;
49 import com
.intellij
.openapi
.progress
.ProgressManager
;
50 import com
.intellij
.openapi
.project
.Project
;
51 import com
.intellij
.openapi
.roots
.ProjectFileIndex
;
52 import com
.intellij
.openapi
.roots
.ProjectRootManager
;
53 import com
.intellij
.openapi
.vfs
.VirtualFile
;
54 import com
.intellij
.profile
.codeInspection
.InspectionProjectProfileManager
;
55 import com
.intellij
.psi
.*;
56 import com
.intellij
.psi
.codeStyle
.JavaCodeStyleManager
;
57 import com
.intellij
.psi
.impl
.PsiClassImplUtil
;
58 import com
.intellij
.psi
.impl
.PsiManagerEx
;
59 import com
.intellij
.psi
.impl
.cache
.CacheManager
;
60 import com
.intellij
.psi
.impl
.source
.jsp
.jspJava
.JspxImportStatement
;
61 import com
.intellij
.psi
.jsp
.JspFile
;
62 import com
.intellij
.psi
.jsp
.JspSpiUtil
;
63 import com
.intellij
.psi
.search
.GlobalSearchScope
;
64 import com
.intellij
.psi
.search
.SearchScope
;
65 import com
.intellij
.psi
.search
.UsageSearchContext
;
66 import com
.intellij
.psi
.search
.searches
.MethodReferencesSearch
;
67 import com
.intellij
.psi
.search
.searches
.OverridingMethodsSearch
;
68 import com
.intellij
.psi
.search
.searches
.ReferencesSearch
;
69 import com
.intellij
.psi
.search
.searches
.SuperMethodsSearch
;
70 import com
.intellij
.psi
.util
.PropertyUtil
;
71 import com
.intellij
.psi
.util
.PsiUtilBase
;
72 import com
.intellij
.util
.Processor
;
73 import com
.intellij
.util
.Query
;
74 import gnu
.trove
.THashSet
;
75 import gnu
.trove
.TObjectIntHashMap
;
76 import org
.jetbrains
.annotations
.NotNull
;
77 import org
.jetbrains
.annotations
.Nullable
;
78 import org
.jetbrains
.annotations
.PropertyKey
;
79 import org
.jetbrains
.annotations
.TestOnly
;
81 import java
.util
.ArrayList
;
82 import java
.util
.Collection
;
83 import java
.util
.List
;
86 public class PostHighlightingPass
extends TextEditorHighlightingPass
{
87 private static final Logger LOG
= Logger
.getInstance("#com.intellij.codeInsight.daemon.impl.PostHighlightingPass");
88 private RefCountHolder myRefCountHolder
;
89 private final PsiFile myFile
;
90 @Nullable private final Editor myEditor
;
91 private final int myStartOffset
;
92 private final int myEndOffset
;
94 private Collection
<HighlightInfo
> myHighlights
;
95 private boolean myHasRedundantImports
;
96 private final JavaCodeStyleManager myStyleManager
;
97 private int myCurentEntryIndex
;
98 private boolean myHasMissortedImports
;
99 private final ImplicitUsageProvider
[] myImplicitUsageProviders
;
100 private UnusedDeclarationInspection myDeadCodeInspection
;
101 private UnusedSymbolLocalInspection myUnusedSymbolInspection
;
102 private HighlightDisplayKey myUnusedSymbolKey
;
103 private boolean myDeadCodeEnabled
;
104 private boolean myInLibrary
;
105 private HighlightDisplayKey myDeadCodeKey
;
106 private HighlightInfoType myDeadCodeInfoType
;
108 private PostHighlightingPass(@NotNull Project project
,
109 @NotNull PsiFile file
,
110 @Nullable Editor editor
,
111 @NotNull Document document
,
114 super(project
, document
, true);
117 myStartOffset
= startOffset
;
118 myEndOffset
= endOffset
;
120 myStyleManager
= JavaCodeStyleManager
.getInstance(myProject
);
121 myCurentEntryIndex
= -1;
123 myImplicitUsageProviders
= Extensions
.getExtensions(ImplicitUsageProvider
.EP_NAME
);
126 PostHighlightingPass(@NotNull Project project
, @NotNull PsiFile file
, @NotNull Editor editor
, int startOffset
, int endOffset
) {
127 this(project
, file
, editor
, editor
.getDocument(), startOffset
, endOffset
);
130 public PostHighlightingPass(@NotNull Project project
, @NotNull PsiFile file
, @NotNull Document document
, int startOffset
, int endOffset
) {
131 this(project
, file
, null, document
, startOffset
, endOffset
);
134 public void doCollectInformation(ProgressIndicator progress
) {
135 DaemonCodeAnalyzer daemonCodeAnalyzer
= DaemonCodeAnalyzer
.getInstance(myProject
);
136 final FileStatusMap fileStatusMap
= ((DaemonCodeAnalyzerImpl
)daemonCodeAnalyzer
).getFileStatusMap();
137 final List
<HighlightInfo
> highlights
= new ArrayList
<HighlightInfo
>();
138 final FileViewProvider viewProvider
= myFile
.getViewProvider();
139 final Set
<Language
> relevantLanguages
= viewProvider
.getLanguages();
140 final Set
<PsiElement
> elementSet
= new THashSet
<PsiElement
>();
141 for (Language language
: relevantLanguages
) {
142 PsiElement psiRoot
= viewProvider
.getPsi(language
);
143 if (!HighlightLevelUtil
.shouldHighlight(psiRoot
)) continue;
144 List
<PsiElement
> elements
= CollectHighlightsUtil
.getElementsInRange(psiRoot
, myStartOffset
, myEndOffset
);
145 elementSet
.addAll(elements
);
148 ProjectFileIndex fileIndex
= ProjectRootManager
.getInstance(myProject
).getFileIndex();
149 VirtualFile virtualFile
= viewProvider
.getVirtualFile();
150 myInLibrary
= fileIndex
.isInLibraryClasses(virtualFile
) || fileIndex
.isInLibrarySource(virtualFile
);
152 myRefCountHolder
= RefCountHolder
.getInstance(myFile
);
153 if (!myRefCountHolder
.retrieveUnusedReferencesInfo(new Runnable() {
155 collectHighlights(elementSet
, highlights
);
156 myHighlights
= highlights
;
157 for (HighlightInfo info
: highlights
) {
158 if (info
.getSeverity() == HighlightSeverity
.ERROR
) {
159 fileStatusMap
.setErrorFoundFlag(myDocument
, true);
165 // we must be sure GHP will restart
166 fileStatusMap
.markFileScopeDirty(getDocument(), Pass
.UPDATE_ALL
);
167 GeneralHighlightingPass
.cancelAndRestartDaemonLater(progress
, myProject
, this);
171 public void doApplyInformationToEditor() {
172 if (myHighlights
== null) return;
173 UpdateHighlightersUtil
.setHighlightersToEditor(myProject
, myDocument
, myStartOffset
, myEndOffset
, myHighlights
, Pass
.POST_UPDATE_ALL
);
175 DaemonCodeAnalyzer daemonCodeAnalyzer
= DaemonCodeAnalyzer
.getInstance(myProject
);
176 ((DaemonCodeAnalyzerImpl
)daemonCodeAnalyzer
).getFileStatusMap().markFileUpToDate(myDocument
, myFile
, getId());
178 if (timeToOptimizeImports() && myEditor
!= null) {
179 optimizeImportsOnTheFly();
183 private void optimizeImportsOnTheFly() {
184 if (myHasRedundantImports
|| myHasMissortedImports
) {
185 ApplicationManager
.getApplication().invokeLater(new Runnable() {
187 CommandProcessor
.getInstance().runUndoTransparentAction(new Runnable() {
189 ApplicationManager
.getApplication().runWriteAction(new Runnable() {
191 OptimizeImportsFix optimizeImportsFix
= new OptimizeImportsFix();
192 if (optimizeImportsFix
.isAvailable(myProject
, myEditor
, myFile
) && myFile
.isWritable()) {
193 PsiDocumentManager
.getInstance(myProject
).commitAllDocuments();
194 optimizeImportsFix
.invoke(myProject
, myEditor
, myFile
);
206 public Collection
<HighlightInfo
> getHighlights() {
210 private void collectHighlights(Collection
<PsiElement
> elements
, final List
<HighlightInfo
> result
) throws ProcessCanceledException
{
211 ApplicationManager
.getApplication().assertReadAccessAllowed();
213 InspectionProfile profile
= InspectionProjectProfileManager
.getInstance(myProject
).getInspectionProfile();
214 myUnusedSymbolKey
= HighlightDisplayKey
.find(UnusedSymbolLocalInspection
.SHORT_NAME
);
215 boolean unusedSymbolEnabled
= profile
.isToolEnabled(myUnusedSymbolKey
, myFile
);
216 HighlightDisplayKey unusedImportKey
= HighlightDisplayKey
.find(UnusedImportLocalInspection
.SHORT_NAME
);
217 boolean unusedImportEnabled
= profile
.isToolEnabled(unusedImportKey
, myFile
);
218 LocalInspectionToolWrapper unusedSymbolTool
= (LocalInspectionToolWrapper
)profile
.getInspectionTool(UnusedSymbolLocalInspection
.SHORT_NAME
,
220 myUnusedSymbolInspection
= unusedSymbolTool
== null ?
null : (UnusedSymbolLocalInspection
)unusedSymbolTool
.getTool();
221 LOG
.assertTrue(ApplicationManager
.getApplication().isUnitTestMode() || myUnusedSymbolInspection
!= null);
223 myDeadCodeKey
= HighlightDisplayKey
.find(UnusedDeclarationInspection
.SHORT_NAME
);
224 myDeadCodeInspection
= (UnusedDeclarationInspection
)profile
.getInspectionTool(UnusedDeclarationInspection
.SHORT_NAME
, myFile
);
225 myDeadCodeEnabled
= profile
.isToolEnabled(myDeadCodeKey
, myFile
);
226 if (unusedImportEnabled
&& JspPsiUtil
.isInJspFile(myFile
)) {
227 final JspFile jspFile
= JspPsiUtil
.getJspFile(myFile
);
228 if (jspFile
!= null) {
229 unusedImportEnabled
= !JspSpiUtil
.isIncludedOrIncludesSomething(jspFile
);
233 myDeadCodeInfoType
= myDeadCodeKey
== null ?
null : new HighlightInfoType
.HighlightInfoTypeImpl(profile
.getErrorLevel(myDeadCodeKey
, myFile
).getSeverity(), HighlightInfoType
.UNUSED_SYMBOL
.getAttributesKey());
235 if (!unusedSymbolEnabled
&& !unusedImportEnabled
) {
238 for (PsiElement element
: elements
) {
239 ProgressManager
.checkCanceled();
241 if (unusedSymbolEnabled
&& element
instanceof PsiIdentifier
) {
242 PsiIdentifier identifier
= (PsiIdentifier
)element
;
243 HighlightInfo info
= processIdentifier(identifier
);
248 else if (unusedImportEnabled
&& element
instanceof PsiImportList
) {
249 final PsiImportStatementBase
[] imports
= ((PsiImportList
)element
).getAllImportStatements();
250 for (PsiImportStatementBase statement
: imports
) {
251 ProgressManager
.checkCanceled();
252 final HighlightInfo info
= processImport(statement
, unusedImportKey
);
262 private HighlightInfo
processIdentifier(PsiIdentifier identifier
) {
263 if (InspectionManagerEx
.inspectionResultSuppressed(identifier
, myUnusedSymbolInspection
)) return null;
264 PsiElement parent
= identifier
.getParent();
265 if (PsiUtilBase
.hasErrorElementChild(parent
)) return null;
268 if (parent
instanceof PsiLocalVariable
&& myUnusedSymbolInspection
.LOCAL_VARIABLE
) {
269 info
= processLocalVariable((PsiLocalVariable
)parent
);
271 else if (parent
instanceof PsiField
&& myUnusedSymbolInspection
.FIELD
) {
272 final PsiField psiField
= (PsiField
)parent
;
273 info
= processField(psiField
, identifier
);
275 else if (parent
instanceof PsiParameter
&& myUnusedSymbolInspection
.PARAMETER
) {
276 info
= processParameter((PsiParameter
)parent
);
278 else if (parent
instanceof PsiMethod
&& myUnusedSymbolInspection
.METHOD
) {
279 info
= processMethod((PsiMethod
)parent
);
281 else if (parent
instanceof PsiClass
&& identifier
.equals(((PsiClass
)parent
).getNameIdentifier()) && myUnusedSymbolInspection
.CLASS
) {
282 info
= processClass((PsiClass
)parent
);
292 private HighlightInfo
processLocalVariable(PsiLocalVariable variable
) {
293 PsiIdentifier identifier
= variable
.getNameIdentifier();
294 if (identifier
== null) return null;
295 if (isImplicitUsage(variable
)) return null;
296 if (!myRefCountHolder
.isReferenced(variable
)) {
297 String message
= JavaErrorMessages
.message("local.variable.is.never.used", identifier
.getText());
298 HighlightInfo highlightInfo
= createUnusedSymbolInfo(identifier
, message
, HighlightInfoType
.UNUSED_SYMBOL
);
299 QuickFixAction
.registerQuickFixAction(highlightInfo
, new RemoveUnusedVariableFix(variable
), myUnusedSymbolKey
);
300 return highlightInfo
;
303 boolean referenced
= myRefCountHolder
.isReferencedForRead(variable
);
304 if (!referenced
&& !isImplicitRead(variable
)) {
305 String message
= JavaErrorMessages
.message("local.variable.is.not.used.for.reading", identifier
.getText());
306 HighlightInfo highlightInfo
= createUnusedSymbolInfo(identifier
, message
, HighlightInfoType
.UNUSED_SYMBOL
);
307 QuickFixAction
.registerQuickFixAction(highlightInfo
, new RemoveUnusedVariableFix(variable
), myUnusedSymbolKey
);
308 return highlightInfo
;
311 if (!variable
.hasInitializer()) {
312 referenced
= myRefCountHolder
.isReferencedForWrite(variable
);
313 if (!referenced
&& !isImplicitWrite(variable
)) {
314 String message
= JavaErrorMessages
.message("local.variable.is.not.assigned", identifier
.getText());
315 final HighlightInfo unusedSymbolInfo
= createUnusedSymbolInfo(identifier
, message
, HighlightInfoType
.UNUSED_SYMBOL
);
316 QuickFixAction
.registerQuickFixAction(unusedSymbolInfo
, new EmptyIntentionAction(UnusedSymbolLocalInspection
.DISPLAY_NAME
), myUnusedSymbolKey
);
317 return unusedSymbolInfo
;
325 private boolean isImplicitUsage(final PsiModifierListOwner element
) {
326 if (UnusedSymbolLocalInspection
.isInjected(element
, myUnusedSymbolInspection
)) return true;
327 for (ImplicitUsageProvider provider
: myImplicitUsageProviders
) {
328 ProgressManager
.checkCanceled();
329 if (provider
.isImplicitUsage(element
)) {
337 private boolean isImplicitRead(final PsiVariable element
) {
338 for(ImplicitUsageProvider provider
: myImplicitUsageProviders
) {
339 ProgressManager
.checkCanceled();
340 if (provider
.isImplicitRead(element
)) {
347 private boolean isImplicitWrite(final PsiVariable element
) {
348 for(ImplicitUsageProvider provider
: myImplicitUsageProviders
) {
349 ProgressManager
.checkCanceled();
350 if (provider
.isImplicitWrite(element
)) {
357 private HighlightInfo
createUnusedSymbolInfo(PsiElement element
, String message
, final HighlightInfoType highlightInfoType
) {
358 return HighlightInfo
.createHighlightInfo(highlightInfoType
, element
, message
);
361 private HighlightInfo
createDeadCodeInfo(PsiElement element
, String message
) {
362 return HighlightInfo
.createHighlightInfo(myDeadCodeInfoType
, element
, message
);
366 private HighlightInfo
processField(final PsiField field
, final PsiIdentifier identifier
) {
367 if (isImplicitUsage(field
)) return null;
368 if (field
.hasModifierProperty(PsiModifier
.PRIVATE
)) {
369 if (!myRefCountHolder
.isReferenced(field
)) {
370 if (HighlightUtil
.isSerializationImplicitlyUsedField(field
)) {
373 String message
= JavaErrorMessages
.message("private.field.is.not.used", identifier
.getText());
375 HighlightInfo highlightInfo
= suggestionsToMakeFieldUsed(field
, identifier
, message
);
376 QuickFixAction
.registerQuickFixAction(highlightInfo
, HighlightMethodUtil
.getFixRange(field
), new CreateConstructorParameterFromFieldFix(field
), null);
377 return highlightInfo
;
380 final boolean readReferenced
= myRefCountHolder
.isReferencedForRead(field
);
381 if (!readReferenced
&& !isImplicitRead(field
)) {
382 String message
= JavaErrorMessages
.message("private.field.is.not.used.for.reading", identifier
.getText());
383 return suggestionsToMakeFieldUsed(field
, identifier
, message
);
386 if (field
.hasInitializer()) {
389 final boolean writeReferenced
= myRefCountHolder
.isReferencedForWrite(field
);
390 if (!writeReferenced
&& !isImplicitWrite(field
)) {
391 String message
= JavaErrorMessages
.message("private.field.is.not.assigned", identifier
.getText());
392 final HighlightInfo info
= createUnusedSymbolInfo(identifier
, message
, HighlightInfoType
.UNUSED_SYMBOL
);
394 QuickFixAction
.registerQuickFixAction(info
, new CreateGetterOrSetterFix(false, true, field
), myUnusedSymbolKey
);
395 QuickFixAction
.registerQuickFixAction(info
, HighlightMethodUtil
.getFixRange(field
), new CreateConstructorParameterFromFieldFix(field
), null);
396 SpecialAnnotationsUtil
.createAddToSpecialAnnotationFixes(field
, new Processor
<String
>() {
397 public boolean process(final String annoName
) {
398 QuickFixAction
.registerQuickFixAction(info
, myUnusedSymbolInspection
.createQuickFix(annoName
, "fields"));
405 else if (!myRefCountHolder
.isReferenced(field
) && weAreSureThereAreNoUsages(field
)) {
406 return formatUnusedSymbolHighlightInfo("field.is.not.used", field
, "fields", myDeadCodeKey
, myDeadCodeInfoType
);
411 private HighlightInfo
suggestionsToMakeFieldUsed(final PsiField field
, final PsiIdentifier identifier
, final String message
) {
412 HighlightInfo highlightInfo
= createUnusedSymbolInfo(identifier
, message
, HighlightInfoType
.UNUSED_SYMBOL
);
413 QuickFixAction
.registerQuickFixAction(highlightInfo
, new RemoveUnusedVariableFix(field
), myUnusedSymbolKey
);
414 QuickFixAction
.registerQuickFixAction(highlightInfo
, new CreateGetterOrSetterFix(true, false, field
), myUnusedSymbolKey
);
415 QuickFixAction
.registerQuickFixAction(highlightInfo
, new CreateGetterOrSetterFix(false, true, field
), myUnusedSymbolKey
);
416 QuickFixAction
.registerQuickFixAction(highlightInfo
, new CreateGetterOrSetterFix(true, true, field
), myUnusedSymbolKey
);
417 return highlightInfo
;
420 private static boolean isOverriddenOrOverrides(PsiMethod method
) {
421 boolean overrides
= SuperMethodsSearch
.search(method
, null, true, false).findFirst() != null;
422 return overrides
|| OverridingMethodsSearch
.search(method
).findFirst() != null;
426 private HighlightInfo
processParameter(PsiParameter parameter
) {
427 PsiElement declarationScope
= parameter
.getDeclarationScope();
428 if (declarationScope
instanceof PsiMethod
) {
429 PsiMethod method
= (PsiMethod
)declarationScope
;
430 if (PsiUtilBase
.hasErrorElementChild(method
)) return null;
431 if ((method
.isConstructor() ||
432 method
.hasModifierProperty(PsiModifier
.PRIVATE
) ||
433 method
.hasModifierProperty(PsiModifier
.STATIC
) ||
434 !method
.hasModifierProperty(PsiModifier
.ABSTRACT
) &&
435 myUnusedSymbolInspection
.REPORT_PARAMETER_FOR_PUBLIC_METHODS
&&
436 !isOverriddenOrOverrides(method
)) &&
437 !method
.hasModifierProperty(PsiModifier
.NATIVE
) &&
438 !HighlightMethodUtil
.isSerializationRelatedMethod(method
, method
.getContainingClass()) &&
439 !PsiClassImplUtil
.isMainMethod(method
)) {
440 HighlightInfo highlightInfo
= checkUnusedParameter(parameter
);
441 if (highlightInfo
!= null) {
442 QuickFixAction
.registerQuickFixAction(highlightInfo
, new RemoveUnusedParameterFix(parameter
), myUnusedSymbolKey
);
443 return highlightInfo
;
447 else if (declarationScope
instanceof PsiForeachStatement
) {
448 HighlightInfo highlightInfo
= checkUnusedParameter(parameter
);
449 if (highlightInfo
!= null) {
450 QuickFixAction
.registerQuickFixAction(highlightInfo
, new EmptyIntentionAction(UnusedSymbolLocalInspection
.DISPLAY_NAME
), myUnusedSymbolKey
);
451 return highlightInfo
;
459 private HighlightInfo
checkUnusedParameter(final PsiParameter parameter
) {
460 if (!myRefCountHolder
.isReferenced(parameter
) && !isImplicitUsage(parameter
)) {
461 PsiIdentifier identifier
= parameter
.getNameIdentifier();
462 assert identifier
!= null;
463 String message
= JavaErrorMessages
.message("parameter.is.not.used", identifier
.getText());
464 return createUnusedSymbolInfo(identifier
, message
, HighlightInfoType
.UNUSED_SYMBOL
);
470 private HighlightInfo
processMethod(final PsiMethod method
) {
471 if (myRefCountHolder
.isReferenced(method
)) return null;
472 boolean isPrivate
= method
.hasModifierProperty(PsiModifier
.PRIVATE
);
473 PsiClass containingClass
= method
.getContainingClass();
474 HighlightInfoType highlightInfoType
= HighlightInfoType
.UNUSED_SYMBOL
;
475 HighlightDisplayKey highlightDisplayKey
= myUnusedSymbolKey
;
478 if (HighlightMethodUtil
.isSerializationRelatedMethod(method
, containingClass
) ||
479 isIntentionalPrivateConstructor(method
, containingClass
)) {
482 if (isImplicitUsage(method
)) {
487 //class maybe used in some weird way, e.g. from XML, therefore the only constructor is used too
488 if (containingClass
!= null && method
.isConstructor() && containingClass
.getConstructors().length
== 1 && isClassUnused(containingClass
) == USED
) return null;
489 if (isImplicitUsage(method
)) return null;
491 if (method
.findSuperMethods().length
!= 0) {
494 if (!weAreSureThereAreNoUsages(method
)) {
497 highlightInfoType
= myDeadCodeInfoType
;
498 highlightDisplayKey
= myDeadCodeKey
;
500 String key
= isPrivate
501 ? method
.isConstructor() ?
"private.constructor.is.not.used" : "private.method.is.not.used"
502 : method
.isConstructor() ?
"constructor.is.not.used" : "method.is.not.used";
503 String symbolName
= HighlightMessageUtil
.getSymbolName(method
, PsiSubstitutor
.EMPTY
);
504 String message
= JavaErrorMessages
.message(key
, symbolName
);
505 PsiIdentifier identifier
= method
.getNameIdentifier();
506 final HighlightInfo highlightInfo
= createUnusedSymbolInfo(identifier
, message
, highlightInfoType
);
507 QuickFixAction
.registerQuickFixAction(highlightInfo
, new SafeDeleteFix(method
), highlightDisplayKey
);
508 SpecialAnnotationsUtil
.createAddToSpecialAnnotationFixes(method
, new Processor
<String
>() {
509 public boolean process(final String annoName
) {
510 QuickFixAction
.registerQuickFixAction(highlightInfo
, myUnusedSymbolInspection
.createQuickFix(annoName
, "methods"));
514 return highlightInfo
;
517 private boolean weAreSureThereAreNoUsages(PsiMember member
) {
518 if (myInLibrary
) return false;
519 if (!myDeadCodeEnabled
) return false;
520 if (myDeadCodeInspection
.isEntryPoint(member
)) return false;
522 String name
= member
.getName();
523 if (name
== null) return false;
524 SearchScope useScope
= member
.getUseScope();
525 if (!(useScope
instanceof GlobalSearchScope
)) return false;
526 final int[] count
= {0};
527 GlobalSearchScope scope
= (GlobalSearchScope
)useScope
;
528 // some classes may have references from within XML outside dependent modules, e.g. our actions
529 if (member
instanceof PsiClass
) scope
= scope
.uniteWith(GlobalSearchScope
.projectScope(myProject
));
531 CacheManager cacheManager
= ((PsiManagerEx
)myFile
.getManager()).getCacheManager();
532 if (!cacheManager
.processFilesWithWord(new Processor
<PsiFile
>() {
533 public boolean process(PsiFile file
) {
534 if (file
== myFile
) return true;
536 return count
[0] <= 10;
538 }, name
, UsageSearchContext
.ANY
, scope
, true)) return false;
540 //search usages if it cheap
541 //if count is 0 there is no usages since we've called myRefCountHolder.isReferenced() before
542 if (count
[0] == 0 && !canbeReferencedViaWeirdNames(member
)) return true;
544 Query
<PsiReference
> query
= member
instanceof PsiMethod
545 ? MethodReferencesSearch
.search((PsiMethod
)member
, scope
, false)
546 : ReferencesSearch
.search(member
, scope
, true);
547 return query
.findFirst() == null;
550 private static boolean canbeReferencedViaWeirdNames(PsiMember member
) {
551 if (member
instanceof PsiClass
) return false;
552 PsiFile containingFile
= member
.getContainingFile();
553 if (!(containingFile
instanceof PsiJavaFile
)) return true; // Groovy field can be referenced from Java by getter
554 if (member
instanceof PsiField
) return false; //Java field cannot be referenced by anything but its name
555 if (member
instanceof PsiMethod
) {
556 return PropertyUtil
.isSimplePropertyAccessor((PsiMethod
)member
); //Java accessors can be referenced by field name from Groovy
562 private HighlightInfo
processClass(PsiClass aClass
) {
563 int usage
= isClassUnused(aClass
);
564 if (usage
== USED
) return null;
567 HighlightDisplayKey highlightDisplayKey
;
568 HighlightInfoType highlightInfoType
;
569 if (aClass
.getContainingClass() != null && aClass
.hasModifierProperty(PsiModifier
.PRIVATE
)) {
570 pattern
= aClass
.isInterface()
571 ?
"private.inner.interface.is.not.used"
572 : "private.inner.class.is.not.used";
573 highlightDisplayKey
= myUnusedSymbolKey
;
574 highlightInfoType
= HighlightInfoType
.UNUSED_SYMBOL
;
576 else if (aClass
.getParent() instanceof PsiDeclarationStatement
) { // local class
577 pattern
= "local.class.is.not.used";
578 highlightDisplayKey
= myUnusedSymbolKey
;
579 highlightInfoType
= HighlightInfoType
.UNUSED_SYMBOL
;
581 else if (aClass
instanceof PsiTypeParameter
) {
582 pattern
= "type.parameter.is.not.used";
583 highlightDisplayKey
= myUnusedSymbolKey
;
584 highlightInfoType
= HighlightInfoType
.UNUSED_SYMBOL
;
587 pattern
= "class.is.not.used";
588 highlightDisplayKey
= myDeadCodeKey
;
589 highlightInfoType
= myDeadCodeInfoType
;
591 return formatUnusedSymbolHighlightInfo(pattern
, aClass
, "classes", highlightDisplayKey
, highlightInfoType
);
594 private static final int USED
= 1;
595 private static final int UNUSED_LOCALLY
= 2;
596 private static final int UNUSED_GLOBALLY
= 3;
597 private final TObjectIntHashMap
<PsiClass
> unusedClassCache
= new TObjectIntHashMap
<PsiClass
>();
598 private int isClassUnused(PsiClass aClass
) {
599 if (aClass
== null) return USED
;
600 int result
= unusedClassCache
.get(aClass
);
602 result
= isReallyUnused(aClass
);
603 unusedClassCache
.put(aClass
, result
);
608 private int isReallyUnused(PsiClass aClass
) {
609 if (isImplicitUsage(aClass
) || myRefCountHolder
.isReferenced(aClass
)) return USED
;
610 if (aClass
.getContainingClass() != null && aClass
.hasModifierProperty(PsiModifier
.PRIVATE
) ||
611 aClass
.getParent() instanceof PsiDeclarationStatement
||
612 aClass
instanceof PsiTypeParameter
) return UNUSED_LOCALLY
;
613 if (weAreSureThereAreNoUsages(aClass
)) return UNUSED_GLOBALLY
;
617 private HighlightInfo
formatUnusedSymbolHighlightInfo(@PropertyKey(resourceBundle
= JavaErrorMessages
.BUNDLE
) String pattern
,
618 PsiNameIdentifierOwner aClass
,
619 final String element
,
620 final HighlightDisplayKey highlightDisplayKey
,
621 final HighlightInfoType highlightInfoType
) {
622 String symbolName
= aClass
.getName();
623 String message
= JavaErrorMessages
.message(pattern
, symbolName
);
624 PsiElement identifier
= aClass
.getNameIdentifier();
625 final HighlightInfo highlightInfo
= createUnusedSymbolInfo(identifier
, message
, highlightInfoType
);
626 QuickFixAction
.registerQuickFixAction(highlightInfo
, new SafeDeleteFix(aClass
), highlightDisplayKey
);
627 SpecialAnnotationsUtil
.createAddToSpecialAnnotationFixes((PsiModifierListOwner
)aClass
, new Processor
<String
>() {
628 public boolean process(final String annoName
) {
629 QuickFixAction
.registerQuickFixAction(highlightInfo
, myUnusedSymbolInspection
.createQuickFix(annoName
, element
));
633 return highlightInfo
;
637 private HighlightInfo
processImport(PsiImportStatementBase importStatement
, HighlightDisplayKey unusedImportKey
) {
638 // jsp include directive hack
639 if (importStatement
instanceof JspxImportStatement
&& ((JspxImportStatement
)importStatement
).isForeignFileImport()) return null;
641 if (PsiUtilBase
.hasErrorElementChild(importStatement
)) return null;
643 boolean isRedundant
= myRefCountHolder
.isRedundant(importStatement
);
644 if (!isRedundant
&& !(importStatement
instanceof PsiImportStaticStatement
)) {
645 //check import from same package
646 String packageName
= ((PsiClassOwner
)importStatement
.getContainingFile()).getPackageName();
647 PsiJavaCodeReferenceElement reference
= importStatement
.getImportReference();
648 PsiElement resolved
= reference
== null ?
null : reference
.resolve();
649 if (resolved
instanceof PsiPackage
) {
650 isRedundant
= packageName
.equals(((PsiPackage
)resolved
).getQualifiedName());
652 else if (resolved
instanceof PsiClass
&& !importStatement
.isOnDemand()) {
653 String qName
= ((PsiClass
)resolved
).getQualifiedName();
655 String name
= ((PsiClass
)resolved
).getName();
656 isRedundant
= qName
.equals(packageName
+ '.' + name
);
662 return registerRedundantImport(importStatement
, unusedImportKey
);
665 int entryIndex
= myStyleManager
.findEntryIndex(importStatement
);
666 if (entryIndex
< myCurentEntryIndex
) {
667 myHasMissortedImports
= true;
669 myCurentEntryIndex
= entryIndex
;
674 private HighlightInfo
registerRedundantImport(PsiImportStatementBase importStatement
, HighlightDisplayKey unusedImportKey
) {
675 HighlightInfo info
= HighlightInfo
.createHighlightInfo(JavaHightlightInfoTypes
.UNUSED_IMPORT
, importStatement
, InspectionsBundle
.message("unused.import.statement"));
677 QuickFixAction
.registerQuickFixAction(info
, new OptimizeImportsFix(), unusedImportKey
);
678 QuickFixAction
.registerQuickFixAction(info
, new EnableOptimizeImportsOnTheFlyFix(), unusedImportKey
);
679 myHasRedundantImports
= true;
683 private boolean timeToOptimizeImports() {
684 if (!CodeInsightSettings
.getInstance().OPTIMIZE_IMPORTS_ON_THE_FLY
) return false;
686 DaemonCodeAnalyzerImpl codeAnalyzer
= (DaemonCodeAnalyzerImpl
)DaemonCodeAnalyzer
.getInstance(myProject
);
687 PsiFile file
= PsiDocumentManager
.getInstance(myProject
).getPsiFile(myDocument
);
688 // dont optimize out imports in JSP since it can be included in other JSP
689 if (file
== null || !codeAnalyzer
.isHighlightingAvailable(file
) || !(file
instanceof PsiJavaFile
) || file
instanceof JspFile
) return false;
691 if (!codeAnalyzer
.isErrorAnalyzingFinished(file
)) return false;
692 List
<HighlightInfo
> errors
= DaemonCodeAnalyzerImpl
.getHighlights(myDocument
, HighlightSeverity
.ERROR
, myProject
);
694 return errors
.isEmpty() && codeAnalyzer
.canChangeFileSilently(myFile
);
697 private static boolean isIntentionalPrivateConstructor(PsiMethod method
, PsiClass containingClass
) {
698 return method
.isConstructor() &&
699 method
.getParameterList().getParametersCount() == 0 &&
700 containingClass
!= null &&
701 containingClass
.getConstructors().length
== 1;