2 * Copyright 2000-2017 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.
17 package com.intellij.codeInspection.ex;
19 import com.intellij.analysis.AnalysisScope;
20 import com.intellij.analysis.AnalysisUIOptions;
21 import com.intellij.analysis.PerformAnalysisInBackgroundOption;
22 import com.intellij.codeInsight.FileModificationService;
23 import com.intellij.codeInsight.daemon.ProblemHighlightFilter;
24 import com.intellij.codeInsight.daemon.impl.HighlightInfoProcessor;
25 import com.intellij.codeInsight.daemon.impl.LocalInspectionsPass;
26 import com.intellij.codeInspection.*;
27 import com.intellij.codeInspection.actions.CleanupInspectionIntention;
28 import com.intellij.codeInspection.lang.GlobalInspectionContextExtension;
29 import com.intellij.codeInspection.reference.RefElement;
30 import com.intellij.codeInspection.reference.RefEntity;
31 import com.intellij.codeInspection.reference.RefManagerImpl;
32 import com.intellij.codeInspection.reference.RefVisitor;
33 import com.intellij.codeInspection.ui.DefaultInspectionToolPresentation;
34 import com.intellij.codeInspection.ui.InspectionResultsView;
35 import com.intellij.codeInspection.ui.InspectionToolPresentation;
36 import com.intellij.codeInspection.ui.InspectionTreeState;
37 import com.intellij.concurrency.JobLauncher;
38 import com.intellij.concurrency.JobLauncherImpl;
39 import com.intellij.concurrency.SensitiveProgressWrapper;
40 import com.intellij.diagnostic.ThreadDumper;
41 import com.intellij.lang.annotation.ProblemGroup;
42 import com.intellij.lang.injection.InjectedLanguageManager;
43 import com.intellij.notification.NotificationGroup;
44 import com.intellij.openapi.Disposable;
45 import com.intellij.openapi.actionSystem.ToggleAction;
46 import com.intellij.openapi.application.*;
47 import com.intellij.openapi.application.ex.ApplicationManagerEx;
48 import com.intellij.openapi.components.PathMacroManager;
49 import com.intellij.openapi.diagnostic.Logger;
50 import com.intellij.openapi.editor.Document;
51 import com.intellij.openapi.progress.*;
52 import com.intellij.openapi.progress.impl.CoreProgressManager;
53 import com.intellij.openapi.progress.util.ProgressIndicatorUtils;
54 import com.intellij.openapi.project.IndexNotReadyException;
55 import com.intellij.openapi.project.Project;
56 import com.intellij.openapi.project.ProjectUtil;
57 import com.intellij.openapi.project.ProjectUtilCore;
58 import com.intellij.openapi.roots.FileIndex;
59 import com.intellij.openapi.roots.ProjectRootManager;
60 import com.intellij.openapi.ui.MessageType;
61 import com.intellij.openapi.util.*;
62 import com.intellij.openapi.util.io.FileUtil;
63 import com.intellij.openapi.util.text.StringUtil;
64 import com.intellij.openapi.vfs.CharsetToolkit;
65 import com.intellij.openapi.vfs.VirtualFile;
66 import com.intellij.openapi.wm.ToolWindowId;
67 import com.intellij.openapi.wm.ToolWindowManager;
68 import com.intellij.psi.*;
69 import com.intellij.psi.search.GlobalSearchScopesCore;
70 import com.intellij.psi.search.LocalSearchScope;
71 import com.intellij.psi.search.SearchScope;
72 import com.intellij.psi.search.scope.packageSet.NamedScope;
73 import com.intellij.psi.util.PsiTreeUtil;
74 import com.intellij.psi.util.PsiUtilCore;
75 import com.intellij.ui.GuiUtils;
76 import com.intellij.ui.content.*;
77 import com.intellij.util.ConcurrencyUtil;
78 import com.intellij.util.IncorrectOperationException;
79 import com.intellij.util.Processor;
80 import com.intellij.util.TripleFunction;
81 import com.intellij.util.containers.ContainerUtil;
82 import com.intellij.util.containers.HashMap;
83 import com.intellij.util.containers.HashSet;
84 import gnu.trove.THashSet;
85 import org.jdom.Element;
86 import org.jetbrains.annotations.NonNls;
87 import org.jetbrains.annotations.NotNull;
88 import org.jetbrains.annotations.Nullable;
91 import java.io.FileOutputStream;
92 import java.io.IOException;
93 import java.io.OutputStreamWriter;
94 import java.lang.reflect.Constructor;
96 import java.util.concurrent.*;
97 import java.util.stream.Collectors;
98 import java.util.stream.Stream;
100 public class GlobalInspectionContextImpl extends GlobalInspectionContextBase implements GlobalInspectionContext {
101 private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.ex.GlobalInspectionContextImpl");
103 public static final NotificationGroup NOTIFICATION_GROUP = NotificationGroup.toolWindowGroup("Inspection Results", ToolWindowId.INSPECTION);
105 private final NotNullLazyValue<ContentManager> myContentManager;
106 private volatile InspectionResultsView myView;
107 private volatile String myOutputPath;
108 private Content myContent;
109 private volatile boolean myViewClosed = true;
110 private long myInspectionStartedTimestamp;
113 private AnalysisUIOptions myUIOptions;
114 private InspectionTreeState myTreeState;
116 public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue<ContentManager> contentManager) {
118 myUIOptions = AnalysisUIOptions.getInstance(project).copy();
119 myContentManager = contentManager;
123 private ContentManager getContentManager() {
124 return myContentManager.getValue();
127 public void setTreeState(InspectionTreeState treeState) {
128 myTreeState = treeState;
131 public void addView(@NotNull InspectionResultsView view,
132 @NotNull String title,
134 LOG.assertTrue(myContent == null, "GlobalInspectionContext is busy under other view now");
135 myContentManager.getValue().addContentManagerListener(new ContentManagerAdapter() {
137 public void contentRemoved(ContentManagerEvent event) {
138 if (event.getContent() == myContent) {
139 if (myView != null) {
149 myView.setUpdating(true);
151 if (myTreeState != null) {
152 myView.getTree().setTreeState(myTreeState);
154 myContent = ContentFactory.SERVICE.getInstance().createContent(view, title, false);
156 myContent.setDisposer(myView);
158 ContentManager contentManager = getContentManager();
159 contentManager.addContent(myContent);
160 contentManager.setSelectedContent(myContent);
162 ToolWindowManager.getInstance(getProject()).getToolWindow(ToolWindowId.INSPECTION).activate(null);
165 public void addView(@NotNull InspectionResultsView view) {
166 addView(view, InspectionsBundle.message(view.isSingleInspectionRun() ?
167 "inspection.results.for.inspection.toolwindow.title" :
168 "inspection.results.for.profile.toolwindow.title",
169 view.getCurrentProfileName(), getCurrentScope().getShortenName()), false);
174 public void doInspections(@NotNull final AnalysisScope scope) {
175 if (myContent != null) {
176 getContentManager().removeContent(myContent, true);
178 super.doInspections(scope);
181 public void launchInspectionsOffline(@NotNull final AnalysisScope scope,
182 @Nullable final String outputPath,
183 final boolean runGlobalToolsOnly,
184 @NotNull final List<File> inspectionsResults) {
185 performInspectionsWithProgressAndExportResults(scope, runGlobalToolsOnly, true, outputPath, inspectionsResults);
188 public void performInspectionsWithProgressAndExportResults(@NotNull final AnalysisScope scope,
189 final boolean runGlobalToolsOnly,
190 final boolean isOfflineInspections,
191 @Nullable final String outputPath,
192 @NotNull final List<File> inspectionsResults) {
194 setCurrentScope(scope);
196 final Runnable action = () -> {
197 myOutputPath = outputPath;
199 performInspectionsWithProgress(scope, runGlobalToolsOnly, isOfflineInspections);
200 exportResults(inspectionsResults, outputPath);
206 if (isOfflineInspections) {
207 ApplicationManager.getApplication().runReadAction(action);
214 private void exportResults(@NotNull List<File> inspectionsResults, @Nullable String outputPath) {
215 @NonNls final String ext = ".xml";
216 final Map<Element, Tools> globalTools = new HashMap<>();
217 for (Map.Entry<String,Tools> entry : getTools().entrySet()) {
218 final Tools sameTools = entry.getValue();
219 boolean hasProblems = false;
220 String toolName = entry.getKey();
221 if (sameTools != null) {
222 for (ScopeToolState toolDescr : sameTools.getTools()) {
223 InspectionToolWrapper toolWrapper = toolDescr.getTool();
224 if (toolWrapper instanceof LocalInspectionToolWrapper) {
225 hasProblems = new File(outputPath, toolName + ext).exists();
228 InspectionToolPresentation presentation = getPresentation(toolWrapper);
229 presentation.updateContent();
230 if (presentation.hasReportedProblems()) {
231 final Element root = new Element(InspectionsBundle.message("inspection.problems"));
232 globalTools.put(root, sameTools);
233 LOG.assertTrue(!hasProblems, toolName);
241 new File(outputPath).mkdirs();
242 final File file = new File(outputPath, toolName + ext);
243 inspectionsResults.add(file);
245 .writeToFile(file, ("</" + InspectionsBundle.message("inspection.problems") + ">").getBytes(CharsetToolkit.UTF8_CHARSET), true);
247 catch (IOException e) {
253 getRefManager().iterate(new RefVisitor() {
255 public void visitElement(@NotNull final RefEntity refEntity) {
256 for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
257 Tools tools = entry.getValue();
258 Element element = entry.getKey();
259 for (ScopeToolState state : tools.getTools()) {
261 InspectionToolWrapper toolWrapper = state.getTool();
262 InspectionToolPresentation presentation = getPresentation(toolWrapper);
263 presentation.exportResults(element, refEntity, d -> false);
265 catch (Throwable e) {
266 LOG.error("Problem when exporting: " + refEntity.getExternalName(), e);
273 for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
274 final String toolName = entry.getValue().getShortName();
275 Element element = entry.getKey();
276 element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false));
277 final org.jdom.Document doc = new org.jdom.Document(element);
278 PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement());
280 new File(outputPath).mkdirs();
281 final File file = new File(outputPath, toolName + ext);
282 inspectionsResults.add(file);
284 try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET)) {
285 JDOMUtil.writeDocument(doc, writer, "\n");
288 catch (IOException e) {
294 public void resolveElement(@NotNull InspectionProfileEntry tool, @NotNull PsiElement element) {
295 final RefElement refElement = getRefManager().getReference(element);
296 if (refElement == null) return;
297 final Tools tools = getTools().get(tool.getShortName());
299 for (ScopeToolState state : tools.getTools()) {
300 InspectionToolWrapper toolWrapper = state.getTool();
301 InspectionToolPresentation presentation = getPresentationOrNull(toolWrapper);
302 if (presentation != null) {
303 resolveElementRecursively(presentation, refElement);
309 public InspectionResultsView getView() {
313 public String getOutputPath() {
317 private static void resolveElementRecursively(@NotNull InspectionToolPresentation presentation, @NotNull RefEntity refElement) {
318 presentation.suppressProblem(refElement);
319 final List<RefEntity> children = refElement.getChildren();
320 for (RefEntity child : children) {
321 resolveElementRecursively(presentation, child);
326 public AnalysisUIOptions getUIOptions() {
330 public void setSplitterProportion(final float proportion) {
331 myUIOptions.SPLITTER_PROPORTION = proportion;
335 public ToggleAction createToggleAutoscrollAction() {
336 return myUIOptions.getAutoScrollToSourceHandler().createToggleAction();
340 protected void launchInspections(@NotNull final AnalysisScope scope) {
341 if (!ApplicationManager.getApplication().isUnitTestMode()) {
342 myUIOptions = AnalysisUIOptions.getInstance(getProject()).copy();
344 myViewClosed = false;
345 super.launchInspections(scope);
350 protected PerformInBackgroundOption createOption() {
351 return new PerformAnalysisInBackgroundOption(getProject());
355 protected void notifyInspectionsFinished(@NotNull final AnalysisScope scope) {
356 if (ApplicationManager.getApplication().isUnitTestMode()) return;
357 LOG.assertTrue(ApplicationManager.getApplication().isDispatchThread());
358 long elapsed = System.currentTimeMillis() - myInspectionStartedTimestamp;
359 LOG.info("Code inspection finished. Took " + elapsed + "ms");
360 if (getProject().isDisposed()) return;
362 InspectionResultsView view = myView == null ? new InspectionResultsView(this, createContentProvider()) : null;
363 if (!(myView == null ? view : myView).hasProblems()) {
364 NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message",
365 scope.getFileCount(),
366 scope.getShortenName()),
367 MessageType.INFO).notify(getProject());
370 Disposer.dispose(view);
373 else if (view != null && !view.isDisposed() && getCurrentScope() != null) {
377 if (myView != null) {
378 myView.setUpdating(false);
383 protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) {
384 myInspectionStartedTimestamp = System.currentTimeMillis();
385 final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
386 if (progressIndicator == null) {
387 throw new IncorrectOperationException("Must be run under progress");
389 if (!isOfflineInspections && ApplicationManager.getApplication().isDispatchThread()) {
390 throw new IncorrectOperationException("Must not start inspections from within EDT");
392 if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
393 throw new IncorrectOperationException("Must not start inspections from within write action");
395 // in offline inspection application we don't care about global read action
396 if (!isOfflineInspections && ApplicationManager.getApplication().isReadAccessAllowed()) {
397 throw new IncorrectOperationException("Must not start inspections from within global read action");
399 final InspectionManager inspectionManager = InspectionManager.getInstance(getProject());
400 ((RefManagerImpl)getRefManager()).initializeAnnotators();
401 final List<Tools> globalTools = new ArrayList<>();
402 final List<Tools> localTools = new ArrayList<>();
403 final List<Tools> globalSimpleTools = new ArrayList<>();
404 initializeTools(globalTools, localTools, globalSimpleTools);
405 appendPairedInspectionsForUnfairTools(globalTools, globalSimpleTools, localTools);
407 runGlobalTools(scope, inspectionManager, globalTools, isOfflineInspections);
409 if (runGlobalToolsOnly || localTools.isEmpty() && globalSimpleTools.isEmpty()) return;
411 SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
412 final Set<VirtualFile> localScopeFiles = searchScope instanceof LocalSearchScope ? new THashSet<>() : null;
413 for (Tools tools : globalSimpleTools) {
414 GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
415 GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
416 tool.inspectionStarted(inspectionManager, this, getPresentation(toolWrapper));
419 final boolean headlessEnvironment = ApplicationManager.getApplication().isHeadlessEnvironment();
420 final Map<String, InspectionToolWrapper> map = getInspectionWrappersMap(localTools);
422 final BlockingQueue<PsiFile> filesToInspect = new ArrayBlockingQueue<>(1000);
423 // use original progress indicator here since we don't want it to cancel on write action start
424 ProgressIndicator iteratingIndicator = new SensitiveProgressWrapper(progressIndicator);
425 Future<?> future = startIterateScopeInBackground(scope, localScopeFiles, headlessEnvironment, filesToInspect, iteratingIndicator);
427 Processor<PsiFile> processor = file -> {
428 ProgressManager.checkCanceled();
429 if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(() -> {
430 if (!file.isValid()) {
433 VirtualFile virtualFile = file.getVirtualFile();
434 if (!scope.contains(virtualFile)) {
435 LOG.error(file.getName()+"; scope: "+scope+"; "+virtualFile);
437 inspectFile(file, getEffectiveRange(searchScope, file), inspectionManager, localTools, globalSimpleTools, map);
439 throw new ProcessCanceledException();
442 boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
443 Stream.concat(getWrappersFromTools(localTools, file, includeDoNotShow).stream(),
444 getWrappersFromTools(globalSimpleTools, file, includeDoNotShow).stream())
445 .filter(wrapper -> wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)
446 .forEach(wrapper -> {
447 ProblemDescriptor[] descriptors = ((ExternalAnnotatorBatchInspection)wrapper.getTool()).checkFile(file, this, inspectionManager);
448 InspectionToolPresentation toolPresentation = getPresentation(wrapper);
449 ReadAction.run(() -> LocalDescriptorsUtil.addProblemDescriptors(Arrays.asList(descriptors), false, this, null, CONVERT, toolPresentation));
455 final Queue<PsiFile> filesFailedToInspect = new LinkedBlockingQueue<>();
457 Disposable disposable = Disposer.newDisposable();
458 ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator);
461 // avoid "attach listener"/"write action" race
462 ReadAction.run(() -> {
464 ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable);
465 // there is a chance we are racing with write action, in which case just registered listener might not be called, retry.
466 if (ApplicationManagerEx.getApplicationEx().isWriteActionPending()) {
467 throw new ProcessCanceledException();
470 // use wrapper here to cancel early when write action start but do not affect the original indicator
471 ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor);
474 catch (ProcessCanceledException ignored) {
475 progressIndicator.checkCanceled();
476 // PCE may be thrown from inside wrapper when write action started
477 // go on with the write and then resume processing the rest of the queue
478 assert !ApplicationManager.getApplication().isReadAccessAllowed();
479 assert !ApplicationManager.getApplication().isDispatchThread();
481 // wait for write action to complete
482 ApplicationManager.getApplication().runReadAction(EmptyRunnable.getInstance());
485 Disposer.dispose(disposable);
490 iteratingIndicator.cancel(); // tell file scanning thread to stop
491 filesToInspect.clear(); // let file scanning thread a chance to put TOMBSTONE and complete
493 future.get(30, TimeUnit.SECONDS);
495 catch (Exception e) {
496 LOG.error("Thread dump: \n"+ThreadDumper.dumpThreadsToString(), e);
500 ProgressManager.checkCanceled();
502 for (Tools tools : globalSimpleTools) {
503 GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
504 GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
505 ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, map);
506 tool.inspectionFinished(inspectionManager, this, problemDescriptionProcessor);
510 addProblemsToView(globalSimpleTools);
513 private static TextRange getEffectiveRange(SearchScope searchScope, PsiFile file) {
514 if (searchScope instanceof LocalSearchScope) {
515 PsiElement[] scopeFileElements = Arrays.stream(((LocalSearchScope)searchScope).getScope())
516 .filter(e -> e.getContainingFile() == file)
517 .toArray(PsiElement[]::new);
518 if (scopeFileElements.length > 0) {
521 for (PsiElement scopeElement : scopeFileElements) {
522 TextRange elementRange = scopeElement.getTextRange();
523 start = start == -1 ? elementRange.getStartOffset() : Math.min(elementRange.getStartOffset(), start);
524 end = end == -1 ? elementRange.getEndOffset() : Math.max(elementRange.getEndOffset(), end);
526 return new TextRange(start, end);
529 return new TextRange(0, file.getTextLength());
532 private void inspectFile(@NotNull final PsiFile file,
533 @NotNull final TextRange range,
534 @NotNull final InspectionManager inspectionManager,
535 @NotNull List<Tools> localTools,
536 @NotNull List<Tools> globalSimpleTools,
537 @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
538 Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
539 if (document == null) return;
541 VirtualFile virtualFile = file.getVirtualFile();
542 String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false);
543 incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url);
545 final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, range.getStartOffset(),
546 range.getEndOffset(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
547 HighlightInfoProcessor.getEmpty());
549 boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
550 final List<LocalInspectionToolWrapper> lTools = getWrappersFromTools(localTools, file, includeDoNotShow);
551 List<LocalInspectionToolWrapper> nonExternalAnnotators = lTools.stream().filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
552 pass.doInspectInBatch(this, inspectionManager, nonExternalAnnotators);
554 List<GlobalInspectionToolWrapper> globalSTools = getWrappersFromTools(globalSimpleTools, file, includeDoNotShow);
555 final List<GlobalInspectionToolWrapper> tools = globalSTools.stream()
556 .filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
557 JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tools, myProgressIndicator, false, toolWrapper -> {
558 GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
559 ProblemsHolder holder = new ProblemsHolder(inspectionManager, file, false);
560 ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, wrappersMap);
561 tool.checkFile(file, inspectionManager, holder, this, problemDescriptionProcessor);
562 InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
563 LocalDescriptorsUtil.addProblemDescriptors(holder.getResults(), false, this, null, CONVERT, toolPresentation);
567 catch (ProcessCanceledException e) {
568 final Throwable cause = e.getCause();
572 LOG.error("In file: " + file, cause);
574 catch (IndexNotReadyException e) {
577 catch (Throwable e) {
578 LOG.error("In file: " + file.getName(), e);
581 InjectedLanguageManager.getInstance(getProject()).dropFileCaches(file);
585 protected boolean includeDoNotShow(final InspectionProfile profile) {
586 return profile.getSingleTool() != null;
589 private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE;
592 private Future<?> startIterateScopeInBackground(@NotNull final AnalysisScope scope,
593 @Nullable final Collection<VirtualFile> localScopeFiles,
594 final boolean headlessEnvironment,
595 @NotNull final BlockingQueue<PsiFile> outFilesToInspect,
596 @NotNull final ProgressIndicator progressIndicator) {
597 Task.Backgroundable task = new Task.Backgroundable(getProject(), "Scanning Files to Inspect") {
599 public void run(@NotNull ProgressIndicator indicator) {
601 final FileIndex fileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
602 scope.accept(file -> {
603 ProgressManager.checkCanceled();
604 if (ProjectUtil.isProjectOrWorkspaceFile(file) || !fileIndex.isInContent(file)) return true;
606 PsiFile psiFile = ReadAction.compute(() -> {
607 if (getProject().isDisposed()) throw new ProcessCanceledException();
608 PsiFile psi = PsiManager.getInstance(getProject()).findFile(file);
609 Document document = psi == null ? null : shouldProcess(psi, headlessEnvironment, localScopeFiles);
610 if (document != null) {
615 // do not inspect binary files
616 if (psiFile != null) {
618 if (ApplicationManager.getApplication().isReadAccessAllowed()) {
619 throw new IllegalStateException("Must not have read action");
621 outFilesToInspect.put(psiFile);
623 catch (InterruptedException e) {
627 ProgressManager.checkCanceled();
631 catch (ProcessCanceledException e) {
632 // ignore, but put tombstone
636 outFilesToInspect.put(TOMBSTONE);
638 catch (InterruptedException e) {
644 return ((CoreProgressManager)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(task, progressIndicator, null);
647 private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection<VirtualFile> localScopeFiles) {
648 final VirtualFile virtualFile = file.getVirtualFile();
649 if (virtualFile == null) return null;
650 if (isBinary(file)) return null; //do not inspect binary files
652 if (myViewClosed && !headlessEnvironment) {
653 throw new ProcessCanceledException();
656 if (LOG.isDebugEnabled()) {
657 LOG.debug("Running local inspections on " + virtualFile.getPath());
660 if (SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile)) return null;
661 if (localScopeFiles != null && !localScopeFiles.add(virtualFile)) return null;
662 if (!ProblemHighlightFilter.shouldProcessFileInBatch(file)) return null;
664 return PsiDocumentManager.getInstance(getProject()).getDocument(file);
667 private void runGlobalTools(@NotNull final AnalysisScope scope,
668 @NotNull final InspectionManager inspectionManager,
669 @NotNull List<Tools> globalTools,
670 boolean isOfflineInspections) {
671 LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive");
672 final List<InspectionToolWrapper> needRepeatSearchRequest = new ArrayList<>();
674 final boolean canBeExternalUsages = !(scope.getScopeType() == AnalysisScope.PROJECT && scope.isIncludeTestSource());
675 for (Tools tools : globalTools) {
676 for (ScopeToolState state : tools.getTools()) {
677 if (!state.isEnabled()) continue;
678 NamedScope stateScope = state.getScope(getProject());
679 if (stateScope == null) continue;
680 AnalysisScope scopeForState = new AnalysisScope(GlobalSearchScopesCore.filterScope(getProject(), stateScope), getProject());
681 final InspectionToolWrapper toolWrapper = state.getTool();
682 final GlobalInspectionTool tool = (GlobalInspectionTool)toolWrapper.getTool();
683 final InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
685 if (tool.isGraphNeeded()) {
687 ((RefManagerImpl)getRefManager()).findAllDeclarations();
689 catch (Throwable e) {
690 getStdJobDescriptors().BUILD_GRAPH.setDoneAmount(0);
694 ApplicationManager.getApplication().runReadAction(() -> {
695 tool.runInspection(scopeForState, inspectionManager, this, toolPresentation);
696 //skip phase when we are sure that scope already contains everything, unused declaration though needs to proceed with its suspicious code
697 if ((canBeExternalUsages || tool.getAdditionalJobs(this) != null) &&
698 tool.queryExternalUsagesRequests(inspectionManager, this, toolPresentation)) {
699 needRepeatSearchRequest.add(toolWrapper);
703 catch (ProcessCanceledException | IndexNotReadyException e) {
706 catch (Throwable e) {
712 for (GlobalInspectionContextExtension extension : myExtensions.values()) {
714 extension.performPostRunActivities(needRepeatSearchRequest, this);
716 catch (ProcessCanceledException | IndexNotReadyException e) {
719 catch (Throwable e) {
724 addProblemsToView(globalTools);
727 public ActionCallback initializeViewIfNeed() {
728 if (myView != null) {
729 return ActionCallback.DONE;
731 final Application app = ApplicationManager.getApplication();
732 final Runnable createView = () -> {
733 InspectionResultsView view = getView();
735 view = new InspectionResultsView(this, createContentProvider());
739 if (app.isUnitTestMode()) {
741 return ActionCallback.DONE;
743 return app.getInvokator().invokeLater(createView);
747 private void appendPairedInspectionsForUnfairTools(@NotNull List<Tools> globalTools,
748 @NotNull List<Tools> globalSimpleTools,
749 @NotNull List<Tools> localTools) {
750 Tools[] larray = localTools.toArray(new Tools[localTools.size()]);
751 for (Tools tool : larray) {
752 LocalInspectionToolWrapper toolWrapper = (LocalInspectionToolWrapper)tool.getTool();
753 LocalInspectionTool localTool = toolWrapper.getTool();
754 if (localTool instanceof PairedUnfairLocalInspectionTool) {
755 String batchShortName = ((PairedUnfairLocalInspectionTool)localTool).getInspectionForBatchShortName();
756 InspectionProfile currentProfile = getCurrentProfile();
757 InspectionToolWrapper batchInspection;
758 if (currentProfile == null) {
759 batchInspection = null;
762 final InspectionToolWrapper pairedWrapper = currentProfile.getInspectionTool(batchShortName, getProject());
763 batchInspection = pairedWrapper != null ? pairedWrapper.createCopy() : null;
765 if (batchInspection != null && !getTools().containsKey(batchShortName)) {
766 // add to existing inspections to run
767 InspectionProfileEntry batchTool = batchInspection.getTool();
768 final ScopeToolState defaultState = tool.getDefaultState();
769 ToolsImpl newTool = new ToolsImpl(batchInspection, defaultState.getLevel(), true, defaultState.isEnabled());
770 for (ScopeToolState state : tool.getTools()) {
771 final NamedScope scope = state.getScope(getProject());
773 newTool.addTool(scope, batchInspection, state.isEnabled(), state.getLevel());
776 if (batchTool instanceof LocalInspectionTool) localTools.add(newTool);
777 else if (batchTool instanceof GlobalSimpleInspectionTool) globalSimpleTools.add(newTool);
778 else if (batchTool instanceof GlobalInspectionTool) globalTools.add(newTool);
779 else throw new AssertionError(batchTool);
780 myTools.put(batchShortName, newTool);
781 batchInspection.initialize(this);
788 private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(@NotNull List<Tools> localTools,
789 @NotNull PsiFile file,
790 boolean includeDoNotShow) {
791 final List<T> lTools = new ArrayList<>();
792 for (Tools tool : localTools) {
793 //noinspection unchecked
794 final T enabledTool = (T)tool.getEnabledTool(file, includeDoNotShow);
795 if (enabledTool != null) {
796 lTools.add(enabledTool);
803 private ProblemDescriptionsProcessor getProblemDescriptionProcessor(@NotNull final GlobalInspectionToolWrapper toolWrapper,
804 @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
805 return new ProblemDescriptionsProcessor() {
807 public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) {
808 for (CommonProblemDescriptor problemDescriptor : commonProblemDescriptors) {
809 if (!(problemDescriptor instanceof ProblemDescriptor)) {
812 ProblemGroup problemGroup = ((ProblemDescriptor)problemDescriptor).getProblemGroup();
814 InspectionToolWrapper targetWrapper = problemGroup == null ? toolWrapper : wrappersMap.get(problemGroup.getProblemName());
815 if (targetWrapper != null) { // Else it's switched off
816 InspectionToolPresentation toolPresentation = getPresentation(targetWrapper);
817 toolPresentation.addProblemElement(refEntity, problemDescriptor);
825 private static Map<String, InspectionToolWrapper> getInspectionWrappersMap(@NotNull List<Tools> tools) {
826 Map<String, InspectionToolWrapper> name2Inspection = new HashMap<>(tools.size());
827 for (Tools tool : tools) {
828 InspectionToolWrapper toolWrapper = tool.getTool();
829 name2Inspection.put(toolWrapper.getShortName(), toolWrapper);
832 return name2Inspection;
835 private static final TripleFunction<LocalInspectionTool,PsiElement,GlobalInspectionContext,RefElement> CONVERT =
836 (tool, elt, context) -> {
837 PsiNamedElement problemElement = PsiTreeUtil.getNonStrictParentOfType(elt, PsiFile.class);
839 RefElement refElement = context.getRefManager().getReference(problemElement);
840 if (refElement == null && problemElement != null) { // no need to lose collected results
841 refElement = GlobalInspectionContextUtil.retrieveRefElement(elt, context);
848 public void close(boolean noSuspiciousCodeFound) {
849 if (!noSuspiciousCodeFound) {
850 if (myView.isRerun()) {
854 if (myView == null) {
858 AnalysisUIOptions.getInstance(getProject()).save(myUIOptions);
859 if (myContent != null) {
860 final ContentManager contentManager = getContentManager();
861 contentManager.removeContent(myContent, true);
865 ((InspectionManagerEx)InspectionManager.getInstance(getProject())).closeRunningContext(this);
866 myPresentationMap.clear();
867 super.close(noSuspiciousCodeFound);
871 public void cleanup() {
872 if (myView != null) {
873 myView.setUpdating(false);
875 myPresentationMap.clear();
880 public void refreshViews() {
881 if (myView != null) {
882 myView.getTree().queueUpdate();
886 private final ConcurrentMap<InspectionToolWrapper, InspectionToolPresentation> myPresentationMap = ContainerUtil.newConcurrentMap();
889 public InspectionToolPresentation getPresentationOrNull(@NotNull InspectionToolWrapper toolWrapper) {
890 return myPresentationMap.get(toolWrapper);
893 public InspectionToolPresentation getPresentation(@NotNull InspectionToolWrapper toolWrapper) {
894 InspectionToolPresentation presentation = myPresentationMap.get(toolWrapper);
895 if (presentation == null) {
896 String presentationClass = StringUtil.notNullize(toolWrapper.myEP == null ? null : toolWrapper.myEP.presentation, DefaultInspectionToolPresentation.class.getName());
899 Constructor<?> constructor = Class.forName(presentationClass).getConstructor(InspectionToolWrapper.class, GlobalInspectionContextImpl.class);
900 presentation = (InspectionToolPresentation)constructor.newInstance(toolWrapper, this);
902 catch (Exception e) {
904 throw new RuntimeException(e);
906 presentation = ConcurrencyUtil.cacheOrGet(myPresentationMap, toolWrapper, presentation);
912 public void codeCleanup(@NotNull final AnalysisScope scope,
913 @NotNull final InspectionProfile profile,
914 @Nullable final String commandName,
915 @Nullable final Runnable postRunnable,
916 final boolean modal) {
917 String title = "Inspect Code...";
918 Task task = modal ? new Task.Modal(getProject(), title, true) {
920 public void run(@NotNull ProgressIndicator indicator) {
921 cleanup(scope, profile, postRunnable, commandName);
923 } : new Task.Backgroundable(getProject(), title, true) {
925 public void run(@NotNull ProgressIndicator indicator) {
926 cleanup(scope, profile, postRunnable, commandName);
929 ProgressManager.getInstance().run(task);
932 private void cleanup(@NotNull final AnalysisScope scope,
933 @NotNull InspectionProfile profile,
934 @Nullable final Runnable postRunnable,
935 @Nullable final String commandName) {
936 setCurrentScope(scope);
937 final int fileCount = scope.getFileCount();
938 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
940 final SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
941 final TextRange range;
942 if (searchScope instanceof LocalSearchScope) {
943 final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
944 range = elements.length == 1 ? ReadAction.compute(elements[0]::getTextRange) : null;
949 final Iterable<Tools> inspectionTools = ContainerUtil.filter(profile.getAllEnabledInspectionTools(getProject()), tools -> {
950 assert tools != null;
951 return tools.getTool().isCleanupTool();
953 boolean includeDoNotShow = includeDoNotShow(profile);
954 final RefManagerImpl refManager = (RefManagerImpl)getRefManager();
955 refManager.inspectionReadActionStarted();
956 List<ProblemDescriptor> descriptors = new ArrayList<>();
957 Set<PsiFile> files = new HashSet<>();
959 scope.accept(new PsiElementVisitor() {
962 public void visitFile(PsiFile file) {
963 if (progressIndicator != null) {
964 progressIndicator.setFraction((double)++myCount / fileCount);
966 if (isBinary(file)) return;
967 final List<LocalInspectionToolWrapper> lTools = new ArrayList<>();
968 for (final Tools tools : inspectionTools) {
969 InspectionToolWrapper tool = tools.getEnabledTool(file, includeDoNotShow);
970 if (tool instanceof GlobalInspectionToolWrapper) {
971 tool = ((GlobalInspectionToolWrapper)tool).getSharedLocalInspectionToolWrapper();
974 lTools.add((LocalInspectionToolWrapper)tool);
975 tool.initialize(GlobalInspectionContextImpl.this);
979 if (!lTools.isEmpty()) {
981 final LocalInspectionsPass pass = new LocalInspectionsPass(file, PsiDocumentManager.getInstance(getProject()).getDocument(file), range != null ? range.getStartOffset() : 0,
982 range != null ? range.getEndOffset() : file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
983 HighlightInfoProcessor.getEmpty());
984 Runnable runnable = () -> pass.doInspectInBatch(GlobalInspectionContextImpl.this, InspectionManager.getInstance(getProject()), lTools);
985 ApplicationManager.getApplication().runReadAction(runnable);
987 final Set<ProblemDescriptor> localDescriptors = new TreeSet<>(CommonProblemDescriptor.DESCRIPTOR_COMPARATOR);
988 for (LocalInspectionToolWrapper tool : lTools) {
989 InspectionToolPresentation toolPresentation = getPresentation(tool);
990 for (CommonProblemDescriptor descriptor : toolPresentation.getProblemDescriptors()) {
991 if (descriptor instanceof ProblemDescriptor) {
992 localDescriptors.add((ProblemDescriptor)descriptor);
997 if (searchScope instanceof LocalSearchScope) {
998 for (Iterator<ProblemDescriptor> iterator = localDescriptors.iterator(); iterator.hasNext(); ) {
999 final ProblemDescriptor descriptor = iterator.next();
1000 final TextRange infoRange = descriptor instanceof ProblemDescriptorBase ? ((ProblemDescriptorBase)descriptor).getTextRange() : null;
1001 if (infoRange != null && !((LocalSearchScope)searchScope).containsRange(file, infoRange)) {
1006 if (!localDescriptors.isEmpty()) {
1007 descriptors.addAll(localDescriptors);
1012 myPresentationMap.clear();
1019 refManager.inspectionReadActionFinished();
1022 if (files.isEmpty()) {
1023 GuiUtils.invokeLaterIfNeeded(() -> {
1024 if (commandName != null) {
1025 NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message", scope.getFileCount(), scope.getDisplayName()), MessageType.INFO).notify(getProject());
1027 if (postRunnable != null) {
1030 }, ModalityState.defaultModalityState());
1034 Runnable runnable = () -> {
1035 if (!FileModificationService.getInstance().preparePsiElementsForWrite(files)) return;
1036 CleanupInspectionIntention.applyFixesNoSort(getProject(), "Code Cleanup", descriptors, null, false);
1037 if (postRunnable != null) {
1041 TransactionGuard.submitTransaction(getProject(), runnable);
1044 private static boolean isBinary(@NotNull PsiFile file) {
1045 return file instanceof PsiBinaryFile || file.getFileType().isBinary();
1048 public boolean isViewClosed() {
1049 return myViewClosed;
1052 private InspectionRVContentProvider createContentProvider() {
1053 return new InspectionRVContentProviderImpl(getProject());
1056 private void addProblemsToView(List<Tools> tools) {
1057 if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
1060 if (myView == null && !ReadAction.compute(() -> InspectionResultsView.hasProblems(tools, this, createContentProvider())).booleanValue()) {
1063 initializeViewIfNeed().doWhenDone(() -> myView.addTools(tools));