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.LocalSearchScope;
70 import com.intellij.psi.search.SearchScope;
71 import com.intellij.psi.search.scope.packageSet.NamedScope;
72 import com.intellij.psi.util.PsiTreeUtil;
73 import com.intellij.psi.util.PsiUtilCore;
74 import com.intellij.ui.GuiUtils;
75 import com.intellij.ui.content.*;
76 import com.intellij.util.ConcurrencyUtil;
77 import com.intellij.util.IncorrectOperationException;
78 import com.intellij.util.Processor;
79 import com.intellij.util.TripleFunction;
80 import com.intellij.util.containers.ContainerUtil;
81 import com.intellij.util.containers.HashMap;
82 import com.intellij.util.containers.HashSet;
83 import com.intellij.util.ui.UIUtil;
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 Content myContent;
108 private volatile boolean myViewClosed = true;
109 private long myInspectionStartedTimestamp;
112 private AnalysisUIOptions myUIOptions;
113 private InspectionTreeState myTreeState;
115 public GlobalInspectionContextImpl(@NotNull Project project, @NotNull NotNullLazyValue<ContentManager> contentManager) {
117 myUIOptions = AnalysisUIOptions.getInstance(project).copy();
118 myContentManager = contentManager;
122 private ContentManager getContentManager() {
123 return myContentManager.getValue();
126 public void setTreeState(InspectionTreeState treeState) {
127 myTreeState = treeState;
130 public void addView(@NotNull InspectionResultsView view,
131 @NotNull String title,
133 LOG.assertTrue(myContent == null, "GlobalInspectionContext is busy under other view now");
134 myContentManager.getValue().addContentManagerListener(new ContentManagerAdapter() {
136 public void contentRemoved(ContentManagerEvent event) {
137 if (event.getContent() == myContent) {
138 if (myView != null) {
148 myView.setUpdating(true);
150 if (myTreeState != null) {
151 myView.getTree().setTreeState(myTreeState);
153 myContent = ContentFactory.SERVICE.getInstance().createContent(view, title, false);
155 myContent.setDisposer(myView);
157 ContentManager contentManager = getContentManager();
158 contentManager.addContent(myContent);
159 contentManager.setSelectedContent(myContent);
161 ToolWindowManager.getInstance(getProject()).getToolWindow(ToolWindowId.INSPECTION).activate(null);
164 public void addView(@NotNull InspectionResultsView view) {
165 addView(view, InspectionsBundle.message(view.isSingleInspectionRun() ?
166 "inspection.results.for.inspection.toolwindow.title" :
167 "inspection.results.for.profile.toolwindow.title",
168 view.getCurrentProfileName(), getCurrentScope().getShortenName()), false);
173 public void doInspections(@NotNull final AnalysisScope scope) {
174 if (myContent != null) {
175 getContentManager().removeContent(myContent, true);
177 super.doInspections(scope);
180 public void launchInspectionsOffline(@NotNull final AnalysisScope scope,
181 @Nullable final String outputPath,
182 final boolean runGlobalToolsOnly,
183 @NotNull final List<File> inspectionsResults) {
184 performInspectionsWithProgressAndExportResults(scope, runGlobalToolsOnly, true, outputPath, inspectionsResults);
187 public void performInspectionsWithProgressAndExportResults(@NotNull final AnalysisScope scope,
188 final boolean runGlobalToolsOnly,
189 final boolean isOfflineInspections,
190 @Nullable final String outputPath,
191 @NotNull final List<File> inspectionsResults) {
193 setCurrentScope(scope);
195 final Runnable action = () -> {
196 DefaultInspectionToolPresentation.setOutputPath(outputPath);
198 performInspectionsWithProgress(scope, runGlobalToolsOnly, isOfflineInspections);
199 exportResults(inspectionsResults, outputPath);
202 DefaultInspectionToolPresentation.setOutputPath(null);
205 if (isOfflineInspections) {
206 ApplicationManager.getApplication().runReadAction(action);
213 private void exportResults(@NotNull List<File> inspectionsResults, @Nullable String outputPath) {
214 @NonNls final String ext = ".xml";
215 final Map<Element, Tools> globalTools = new HashMap<>();
216 for (Map.Entry<String,Tools> entry : getTools().entrySet()) {
217 final Tools sameTools = entry.getValue();
218 boolean hasProblems = false;
219 String toolName = entry.getKey();
220 if (sameTools != null) {
221 for (ScopeToolState toolDescr : sameTools.getTools()) {
222 InspectionToolWrapper toolWrapper = toolDescr.getTool();
223 if (toolWrapper instanceof LocalInspectionToolWrapper) {
224 hasProblems = new File(outputPath, toolName + ext).exists();
227 InspectionToolPresentation presentation = getPresentation(toolWrapper);
228 presentation.updateContent();
229 if (presentation.hasReportedProblems()) {
230 final Element root = new Element(InspectionsBundle.message("inspection.problems"));
231 globalTools.put(root, sameTools);
232 LOG.assertTrue(!hasProblems, toolName);
240 new File(outputPath).mkdirs();
241 final File file = new File(outputPath, toolName + ext);
242 inspectionsResults.add(file);
244 .writeToFile(file, ("</" + InspectionsBundle.message("inspection.problems") + ">").getBytes(CharsetToolkit.UTF8_CHARSET), true);
246 catch (IOException e) {
252 getRefManager().iterate(new RefVisitor() {
254 public void visitElement(@NotNull final RefEntity refEntity) {
255 for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
256 Tools tools = entry.getValue();
257 Element element = entry.getKey();
258 for (ScopeToolState state : tools.getTools()) {
260 InspectionToolWrapper toolWrapper = state.getTool();
261 InspectionToolPresentation presentation = getPresentation(toolWrapper);
262 presentation.exportResults(element, refEntity, d -> false);
264 catch (Throwable e) {
265 LOG.error("Problem when exporting: " + refEntity.getExternalName(), e);
272 for (Map.Entry<Element, Tools> entry : globalTools.entrySet()) {
273 final String toolName = entry.getValue().getShortName();
274 Element element = entry.getKey();
275 element.setAttribute(LOCAL_TOOL_ATTRIBUTE, Boolean.toString(false));
276 final org.jdom.Document doc = new org.jdom.Document(element);
277 PathMacroManager.getInstance(getProject()).collapsePaths(doc.getRootElement());
279 new File(outputPath).mkdirs();
280 final File file = new File(outputPath, toolName + ext);
281 inspectionsResults.add(file);
283 try (OutputStreamWriter writer = new OutputStreamWriter(new FileOutputStream(file), CharsetToolkit.UTF8_CHARSET)) {
284 JDOMUtil.writeDocument(doc, writer, "\n");
287 catch (IOException e) {
293 public void ignoreElement(@NotNull InspectionProfileEntry tool, @NotNull PsiElement element) {
294 final RefElement refElement = getRefManager().getReference(element);
295 final Tools tools = getTools().get(tool.getShortName());
297 for (ScopeToolState state : tools.getTools()) {
298 InspectionToolWrapper toolWrapper = state.getTool();
299 ignoreElementRecursively(toolWrapper, refElement);
304 public InspectionResultsView getView() {
308 private void ignoreElementRecursively(@NotNull InspectionToolWrapper toolWrapper, final RefEntity refElement) {
309 if (refElement != null) {
310 InspectionToolPresentation presentation = getPresentation(toolWrapper);
311 presentation.ignoreCurrentElement(refElement);
312 final List<RefEntity> children = refElement.getChildren();
313 for (RefEntity child : children) {
314 ignoreElementRecursively(toolWrapper, child);
320 public AnalysisUIOptions getUIOptions() {
324 public void setSplitterProportion(final float proportion) {
325 myUIOptions.SPLITTER_PROPORTION = proportion;
329 public ToggleAction createToggleAutoscrollAction() {
330 return myUIOptions.getAutoScrollToSourceHandler().createToggleAction();
334 protected void launchInspections(@NotNull final AnalysisScope scope) {
335 if (!ApplicationManager.getApplication().isUnitTestMode()) {
336 myUIOptions = AnalysisUIOptions.getInstance(getProject()).copy();
338 myViewClosed = false;
339 super.launchInspections(scope);
344 protected PerformInBackgroundOption createOption() {
345 return new PerformAnalysisInBackgroundOption(getProject());
349 protected void notifyInspectionsFinished(@NotNull final AnalysisScope scope) {
350 if (ApplicationManager.getApplication().isUnitTestMode()) return;
351 UIUtil.invokeLaterIfNeeded(() -> {
352 long elapsed = System.currentTimeMillis() - myInspectionStartedTimestamp;
353 LOG.info("Code inspection finished. Took "+elapsed+"ms");
354 if (getProject().isDisposed()) return;
356 InspectionResultsView view = myView == null ? new InspectionResultsView(this, createContentProvider()) : null;
357 if (!(myView == null ? view : myView).hasProblems()) {
358 NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message",
359 scope.getFileCount(),
360 scope.getShortenName()),
361 MessageType.INFO).notify(getProject());
364 Disposer.dispose(view);
367 else if (view != null && !view.isDisposed() && getCurrentScope() != null) {
371 if (myView != null) {
372 myView.setUpdating(false);
378 protected void runTools(@NotNull final AnalysisScope scope, boolean runGlobalToolsOnly, boolean isOfflineInspections) {
379 myInspectionStartedTimestamp = System.currentTimeMillis();
380 final ProgressIndicator progressIndicator = ProgressIndicatorProvider.getGlobalProgressIndicator();
381 if (progressIndicator == null) {
382 throw new IncorrectOperationException("Must be run under progress");
384 if (!isOfflineInspections && ApplicationManager.getApplication().isDispatchThread()) {
385 throw new IncorrectOperationException("Must not start inspections from within EDT");
387 if (ApplicationManager.getApplication().isWriteAccessAllowed()) {
388 throw new IncorrectOperationException("Must not start inspections from within write action");
390 // in offline inspection application we don't care about global read action
391 if (!isOfflineInspections && ApplicationManager.getApplication().isReadAccessAllowed()) {
392 throw new IncorrectOperationException("Must not start inspections from within global read action");
394 final InspectionManager inspectionManager = InspectionManager.getInstance(getProject());
395 ((RefManagerImpl)getRefManager()).initializeAnnotators();
396 final List<Tools> globalTools = new ArrayList<>();
397 final List<Tools> localTools = new ArrayList<>();
398 final List<Tools> globalSimpleTools = new ArrayList<>();
399 initializeTools(globalTools, localTools, globalSimpleTools);
400 appendPairedInspectionsForUnfairTools(globalTools, globalSimpleTools, localTools);
402 runGlobalTools(scope, inspectionManager, globalTools, isOfflineInspections);
404 if (runGlobalToolsOnly || localTools.isEmpty() && globalSimpleTools.isEmpty()) return;
406 SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
407 final Set<VirtualFile> localScopeFiles = searchScope instanceof LocalSearchScope ? new THashSet<>() : null;
408 for (Tools tools : globalSimpleTools) {
409 GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
410 GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
411 tool.inspectionStarted(inspectionManager, this, getPresentation(toolWrapper));
414 final boolean headlessEnvironment = ApplicationManager.getApplication().isHeadlessEnvironment();
415 final Map<String, InspectionToolWrapper> map = getInspectionWrappersMap(localTools);
417 final BlockingQueue<PsiFile> filesToInspect = new ArrayBlockingQueue<>(1000);
418 // use original progress indicator here since we don't want it to cancel on write action start
419 ProgressIndicator iteratingIndicator = new SensitiveProgressWrapper(progressIndicator);
420 Future<?> future = startIterateScopeInBackground(scope, localScopeFiles, headlessEnvironment, filesToInspect, iteratingIndicator);
422 Processor<PsiFile> processor = file -> {
423 ProgressManager.checkCanceled();
424 if (!ApplicationManagerEx.getApplicationEx().tryRunReadAction(() -> {
425 if (!file.isValid()) {
428 VirtualFile virtualFile = file.getVirtualFile();
429 if (!scope.contains(virtualFile)) {
430 LOG.error(file.getName()+"; scope: "+scope+"; "+virtualFile);
432 inspectFile(file, getEffectiveRange(searchScope, file), inspectionManager, localTools, globalSimpleTools, map);
434 throw new ProcessCanceledException();
437 boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
438 Stream.concat(getWrappersFromTools(localTools, file, includeDoNotShow).stream(),
439 getWrappersFromTools(globalSimpleTools, file, includeDoNotShow).stream())
440 .filter(wrapper -> wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)
441 .forEach(wrapper -> {
442 ProblemDescriptor[] descriptors = ((ExternalAnnotatorBatchInspection)wrapper.getTool()).checkFile(file, this, inspectionManager);
443 InspectionToolPresentation toolPresentation = getPresentation(wrapper);
444 ReadAction.run(() -> LocalDescriptorsUtil.addProblemDescriptors(Arrays.asList(descriptors), false, this, null, CONVERT, toolPresentation));
450 final Queue<PsiFile> filesFailedToInspect = new LinkedBlockingQueue<>();
452 Disposable disposable = Disposer.newDisposable();
453 ProgressIndicator wrapper = new SensitiveProgressWrapper(progressIndicator);
456 // avoid "attach listener"/"write action" race
457 ReadAction.run(() -> {
459 ProgressIndicatorUtils.forceWriteActionPriority(wrapper, disposable);
460 // there is a chance we are racing with write action, in which case just registered listener might not be called, retry.
461 if (ApplicationManagerEx.getApplicationEx().isWriteActionPending()) {
462 throw new ProcessCanceledException();
465 // use wrapper here to cancel early when write action start but do not affect the original indicator
466 ((JobLauncherImpl)JobLauncher.getInstance()).processQueue(filesToInspect, filesFailedToInspect, wrapper, TOMBSTONE, processor);
469 catch (ProcessCanceledException ignored) {
470 progressIndicator.checkCanceled();
471 // PCE may be thrown from inside wrapper when write action started
472 // go on with the write and then resume processing the rest of the queue
473 assert !ApplicationManager.getApplication().isReadAccessAllowed();
474 assert !ApplicationManager.getApplication().isDispatchThread();
476 // wait for write action to complete
477 ApplicationManager.getApplication().runReadAction(EmptyRunnable.getInstance());
480 Disposer.dispose(disposable);
485 iteratingIndicator.cancel(); // tell file scanning thread to stop
486 filesToInspect.clear(); // let file scanning thread a chance to put TOMBSTONE and complete
488 future.get(30, TimeUnit.SECONDS);
490 catch (Exception e) {
491 LOG.error("Thread dump: \n"+ThreadDumper.dumpThreadsToString(), e);
495 ProgressManager.checkCanceled();
497 for (Tools tools : globalSimpleTools) {
498 GlobalInspectionToolWrapper toolWrapper = (GlobalInspectionToolWrapper)tools.getTool();
499 GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
500 ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, map);
501 tool.inspectionFinished(inspectionManager, this, problemDescriptionProcessor);
505 addProblemsToView(globalSimpleTools);
508 private static TextRange getEffectiveRange(SearchScope searchScope, PsiFile file) {
509 if (searchScope instanceof LocalSearchScope) {
510 PsiElement[] scopeFileElements = Arrays.stream(((LocalSearchScope)searchScope).getScope())
511 .filter(e -> e.getContainingFile() == file)
512 .toArray(PsiElement[]::new);
513 if (scopeFileElements.length > 0) {
516 for (PsiElement scopeElement : scopeFileElements) {
517 TextRange elementRange = scopeElement.getTextRange();
518 start = start == -1 ? elementRange.getStartOffset() : Math.min(elementRange.getStartOffset(), start);
519 end = end == -1 ? elementRange.getEndOffset() : Math.max(elementRange.getEndOffset(), end);
521 return new TextRange(start, end);
524 return new TextRange(0, file.getTextLength());
527 private void inspectFile(@NotNull final PsiFile file,
528 @NotNull final TextRange range,
529 @NotNull final InspectionManager inspectionManager,
530 @NotNull List<Tools> localTools,
531 @NotNull List<Tools> globalSimpleTools,
532 @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
533 Document document = PsiDocumentManager.getInstance(getProject()).getDocument(file);
534 if (document == null) return;
536 VirtualFile virtualFile = file.getVirtualFile();
537 String url = ProjectUtilCore.displayUrlRelativeToProject(virtualFile, virtualFile.getPresentableUrl(), getProject(), true, false);
538 incrementJobDoneAmount(getStdJobDescriptors().LOCAL_ANALYSIS, url);
540 final LocalInspectionsPass pass = new LocalInspectionsPass(file, document, range.getStartOffset(),
541 range.getEndOffset(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
542 HighlightInfoProcessor.getEmpty());
544 boolean includeDoNotShow = includeDoNotShow(getCurrentProfile());
545 final List<LocalInspectionToolWrapper> lTools = getWrappersFromTools(localTools, file, includeDoNotShow);
546 List<LocalInspectionToolWrapper> nonExternalAnnotators = lTools.stream().filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
547 pass.doInspectInBatch(this, inspectionManager, nonExternalAnnotators);
549 List<GlobalInspectionToolWrapper> globalSTools = getWrappersFromTools(globalSimpleTools, file, includeDoNotShow);
550 final List<GlobalInspectionToolWrapper> tools = globalSTools.stream()
551 .filter(wrapper -> !(wrapper.getTool() instanceof ExternalAnnotatorBatchInspection)).collect(Collectors.toList());
552 JobLauncher.getInstance().invokeConcurrentlyUnderProgress(tools, myProgressIndicator, false, toolWrapper -> {
553 GlobalSimpleInspectionTool tool = (GlobalSimpleInspectionTool)toolWrapper.getTool();
554 ProblemsHolder holder = new ProblemsHolder(inspectionManager, file, false);
555 ProblemDescriptionsProcessor problemDescriptionProcessor = getProblemDescriptionProcessor(toolWrapper, wrappersMap);
556 tool.checkFile(file, inspectionManager, holder, this, problemDescriptionProcessor);
557 InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
558 LocalDescriptorsUtil.addProblemDescriptors(holder.getResults(), false, this, null, CONVERT, toolPresentation);
562 catch (ProcessCanceledException e) {
563 final Throwable cause = e.getCause();
567 LOG.error("In file: " + file, cause);
569 catch (IndexNotReadyException e) {
572 catch (Throwable e) {
573 LOG.error("In file: " + file.getName(), e);
576 InjectedLanguageManager.getInstance(getProject()).dropFileCaches(file);
580 protected boolean includeDoNotShow(final InspectionProfile profile) {
581 return profile.getSingleTool() != null;
584 private static final PsiFile TOMBSTONE = PsiUtilCore.NULL_PSI_FILE;
587 private Future<?> startIterateScopeInBackground(@NotNull final AnalysisScope scope,
588 @Nullable final Collection<VirtualFile> localScopeFiles,
589 final boolean headlessEnvironment,
590 @NotNull final BlockingQueue<PsiFile> outFilesToInspect,
591 @NotNull final ProgressIndicator progressIndicator) {
592 Task.Backgroundable task = new Task.Backgroundable(getProject(), "Scanning Files to Inspect") {
594 public void run(@NotNull ProgressIndicator indicator) {
596 final FileIndex fileIndex = ProjectRootManager.getInstance(getProject()).getFileIndex();
597 scope.accept(file -> {
598 ProgressManager.checkCanceled();
599 if (ProjectUtil.isProjectOrWorkspaceFile(file) || !fileIndex.isInContent(file)) return true;
601 PsiFile psiFile = ReadAction.compute(() -> {
602 if (getProject().isDisposed()) throw new ProcessCanceledException();
603 PsiFile psi = PsiManager.getInstance(getProject()).findFile(file);
604 Document document = psi == null ? null : shouldProcess(psi, headlessEnvironment, localScopeFiles);
605 if (document != null) {
610 // do not inspect binary files
611 if (psiFile != null) {
613 if (ApplicationManager.getApplication().isReadAccessAllowed()) {
614 throw new IllegalStateException("Must not have read action");
616 outFilesToInspect.put(psiFile);
618 catch (InterruptedException e) {
622 ProgressManager.checkCanceled();
626 catch (ProcessCanceledException e) {
627 // ignore, but put tombstone
631 outFilesToInspect.put(TOMBSTONE);
633 catch (InterruptedException e) {
639 return ((CoreProgressManager)ProgressManager.getInstance()).runProcessWithProgressAsynchronously(task, progressIndicator, null);
642 private Document shouldProcess(@NotNull PsiFile file, boolean headlessEnvironment, @Nullable Collection<VirtualFile> localScopeFiles) {
643 final VirtualFile virtualFile = file.getVirtualFile();
644 if (virtualFile == null) return null;
645 if (isBinary(file)) return null; //do not inspect binary files
647 if (myViewClosed && !headlessEnvironment) {
648 throw new ProcessCanceledException();
651 if (LOG.isDebugEnabled()) {
652 LOG.debug("Running local inspections on " + virtualFile.getPath());
655 if (SingleRootFileViewProvider.isTooLargeForIntelligence(virtualFile)) return null;
656 if (localScopeFiles != null && !localScopeFiles.add(virtualFile)) return null;
657 if (!ProblemHighlightFilter.shouldProcessFileInBatch(file)) return null;
659 return PsiDocumentManager.getInstance(getProject()).getDocument(file);
662 private void runGlobalTools(@NotNull final AnalysisScope scope,
663 @NotNull final InspectionManager inspectionManager,
664 @NotNull List<Tools> globalTools,
665 boolean isOfflineInspections) {
666 LOG.assertTrue(!ApplicationManager.getApplication().isReadAccessAllowed() || isOfflineInspections, "Must not run under read action, too unresponsive");
667 final List<InspectionToolWrapper> needRepeatSearchRequest = new ArrayList<>();
669 final boolean canBeExternalUsages = !(scope.getScopeType() == AnalysisScope.PROJECT && scope.isIncludeTestSource());
670 for (Tools tools : globalTools) {
671 for (ScopeToolState state : tools.getTools()) {
672 final InspectionToolWrapper toolWrapper = state.getTool();
673 final GlobalInspectionTool tool = (GlobalInspectionTool)toolWrapper.getTool();
674 final InspectionToolPresentation toolPresentation = getPresentation(toolWrapper);
676 if (tool.isGraphNeeded()) {
678 ((RefManagerImpl)getRefManager()).findAllDeclarations();
680 catch (Throwable e) {
681 getStdJobDescriptors().BUILD_GRAPH.setDoneAmount(0);
685 ApplicationManager.getApplication().runReadAction(() -> {
686 tool.runInspection(scope, inspectionManager, this, toolPresentation);
687 //skip phase when we are sure that scope already contains everything, unused declaration though needs to proceed with its suspicious code
688 if ((canBeExternalUsages || tool.getAdditionalJobs(this) != null) &&
689 tool.queryExternalUsagesRequests(inspectionManager, this, toolPresentation)) {
690 needRepeatSearchRequest.add(toolWrapper);
694 catch (ProcessCanceledException | IndexNotReadyException e) {
697 catch (Throwable e) {
703 for (GlobalInspectionContextExtension extension : myExtensions.values()) {
705 extension.performPostRunActivities(needRepeatSearchRequest, this);
707 catch (ProcessCanceledException | IndexNotReadyException e) {
710 catch (Throwable e) {
715 addProblemsToView(globalTools);
718 public ActionCallback initializeViewIfNeed() {
719 if (myView != null) {
720 return ActionCallback.DONE;
722 final Application app = ApplicationManager.getApplication();
723 final Runnable createView = () -> {
724 InspectionResultsView view = getView();
726 view = new InspectionResultsView(this, createContentProvider());
730 if (app.isUnitTestMode()) {
732 return ActionCallback.DONE;
734 return app.getInvokator().invokeLater(createView);
738 private void appendPairedInspectionsForUnfairTools(@NotNull List<Tools> globalTools,
739 @NotNull List<Tools> globalSimpleTools,
740 @NotNull List<Tools> localTools) {
741 Tools[] larray = localTools.toArray(new Tools[localTools.size()]);
742 for (Tools tool : larray) {
743 LocalInspectionToolWrapper toolWrapper = (LocalInspectionToolWrapper)tool.getTool();
744 LocalInspectionTool localTool = toolWrapper.getTool();
745 if (localTool instanceof PairedUnfairLocalInspectionTool) {
746 String batchShortName = ((PairedUnfairLocalInspectionTool)localTool).getInspectionForBatchShortName();
747 InspectionProfile currentProfile = getCurrentProfile();
748 InspectionToolWrapper batchInspection;
749 if (currentProfile == null) {
750 batchInspection = null;
753 final InspectionToolWrapper pairedWrapper = currentProfile.getInspectionTool(batchShortName, getProject());
754 batchInspection = pairedWrapper != null ? pairedWrapper.createCopy() : null;
756 if (batchInspection != null && !getTools().containsKey(batchShortName)) {
757 // add to existing inspections to run
758 InspectionProfileEntry batchTool = batchInspection.getTool();
759 final ScopeToolState defaultState = tool.getDefaultState();
760 ToolsImpl newTool = new ToolsImpl(batchInspection, defaultState.getLevel(), true, defaultState.isEnabled());
761 for (ScopeToolState state : tool.getTools()) {
762 final NamedScope scope = state.getScope(getProject());
764 newTool.addTool(scope, batchInspection, state.isEnabled(), state.getLevel());
767 if (batchTool instanceof LocalInspectionTool) localTools.add(newTool);
768 else if (batchTool instanceof GlobalSimpleInspectionTool) globalSimpleTools.add(newTool);
769 else if (batchTool instanceof GlobalInspectionTool) globalTools.add(newTool);
770 else throw new AssertionError(batchTool);
771 myTools.put(batchShortName, newTool);
772 batchInspection.initialize(this);
779 private static <T extends InspectionToolWrapper> List<T> getWrappersFromTools(@NotNull List<Tools> localTools,
780 @NotNull PsiFile file,
781 boolean includeDoNotShow) {
782 final List<T> lTools = new ArrayList<>();
783 for (Tools tool : localTools) {
784 //noinspection unchecked
785 final T enabledTool = (T)tool.getEnabledTool(file, includeDoNotShow);
786 if (enabledTool != null) {
787 lTools.add(enabledTool);
794 private ProblemDescriptionsProcessor getProblemDescriptionProcessor(@NotNull final GlobalInspectionToolWrapper toolWrapper,
795 @NotNull final Map<String, InspectionToolWrapper> wrappersMap) {
796 return new ProblemDescriptionsProcessor() {
798 public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) {
799 for (CommonProblemDescriptor problemDescriptor : commonProblemDescriptors) {
800 if (!(problemDescriptor instanceof ProblemDescriptor)) {
803 ProblemGroup problemGroup = ((ProblemDescriptor)problemDescriptor).getProblemGroup();
805 InspectionToolWrapper targetWrapper = problemGroup == null ? toolWrapper : wrappersMap.get(problemGroup.getProblemName());
806 if (targetWrapper != null) { // Else it's switched off
807 InspectionToolPresentation toolPresentation = getPresentation(targetWrapper);
808 toolPresentation.addProblemElement(refEntity, problemDescriptor);
816 private static Map<String, InspectionToolWrapper> getInspectionWrappersMap(@NotNull List<Tools> tools) {
817 Map<String, InspectionToolWrapper> name2Inspection = new HashMap<>(tools.size());
818 for (Tools tool : tools) {
819 InspectionToolWrapper toolWrapper = tool.getTool();
820 name2Inspection.put(toolWrapper.getShortName(), toolWrapper);
823 return name2Inspection;
826 private static final TripleFunction<LocalInspectionTool,PsiElement,GlobalInspectionContext,RefElement> CONVERT =
827 (tool, elt, context) -> {
828 PsiNamedElement problemElement = PsiTreeUtil.getNonStrictParentOfType(elt, PsiFile.class);
830 RefElement refElement = context.getRefManager().getReference(problemElement);
831 if (refElement == null && problemElement != null) { // no need to lose collected results
832 refElement = GlobalInspectionContextUtil.retrieveRefElement(elt, context);
839 public void close(boolean noSuspiciousCodeFound) {
840 if (!noSuspiciousCodeFound) {
841 if (myView.isRerun()) {
845 if (myView == null) {
849 AnalysisUIOptions.getInstance(getProject()).save(myUIOptions);
850 if (myContent != null) {
851 final ContentManager contentManager = getContentManager();
852 contentManager.removeContent(myContent, true);
856 ((InspectionManagerEx)InspectionManager.getInstance(getProject())).closeRunningContext(this);
857 for (Tools tools : getTools().values()) {
858 for (ScopeToolState state : tools.getTools()) {
859 InspectionToolWrapper toolWrapper = state.getTool();
860 getPresentation(toolWrapper).finalCleanup();
863 super.close(noSuspiciousCodeFound);
867 public void cleanup() {
868 if (myView != null) {
869 myView.setUpdating(false);
871 myPresentationMap.clear();
876 public void refreshViews() {
877 if (myView != null) {
878 myView.getTree().queueUpdate();
882 private final ConcurrentMap<InspectionToolWrapper, InspectionToolPresentation> myPresentationMap = ContainerUtil.newConcurrentMap();
884 public InspectionToolPresentation getPresentation(@NotNull InspectionToolWrapper toolWrapper) {
885 InspectionToolPresentation presentation = myPresentationMap.get(toolWrapper);
886 if (presentation == null) {
887 String presentationClass = StringUtil.notNullize(toolWrapper.myEP == null ? null : toolWrapper.myEP.presentation, DefaultInspectionToolPresentation.class.getName());
890 Constructor<?> constructor = Class.forName(presentationClass).getConstructor(InspectionToolWrapper.class, GlobalInspectionContextImpl.class);
891 presentation = (InspectionToolPresentation)constructor.newInstance(toolWrapper, this);
893 catch (Exception e) {
895 throw new RuntimeException(e);
897 presentation = ConcurrencyUtil.cacheOrGet(myPresentationMap, toolWrapper, presentation);
903 public void codeCleanup(@NotNull final AnalysisScope scope,
904 @NotNull final InspectionProfile profile,
905 @Nullable final String commandName,
906 @Nullable final Runnable postRunnable,
907 final boolean modal) {
908 String title = "Inspect Code...";
909 Task task = modal ? new Task.Modal(getProject(), title, true) {
911 public void run(@NotNull ProgressIndicator indicator) {
912 cleanup(scope, profile, postRunnable, commandName);
914 } : new Task.Backgroundable(getProject(), title, true) {
916 public void run(@NotNull ProgressIndicator indicator) {
917 cleanup(scope, profile, postRunnable, commandName);
920 ProgressManager.getInstance().run(task);
923 private void cleanup(@NotNull final AnalysisScope scope,
924 @NotNull InspectionProfile profile,
925 @Nullable final Runnable postRunnable,
926 @Nullable final String commandName) {
927 setCurrentScope(scope);
928 final int fileCount = scope.getFileCount();
929 final ProgressIndicator progressIndicator = ProgressManager.getInstance().getProgressIndicator();
931 final SearchScope searchScope = ReadAction.compute(scope::toSearchScope);
932 final TextRange range;
933 if (searchScope instanceof LocalSearchScope) {
934 final PsiElement[] elements = ((LocalSearchScope)searchScope).getScope();
935 range = elements.length == 1 ? ReadAction.compute(elements[0]::getTextRange) : null;
940 final Iterable<Tools> inspectionTools = ContainerUtil.filter(profile.getAllEnabledInspectionTools(getProject()), tools -> {
941 assert tools != null;
942 return tools.getTool().isCleanupTool();
944 boolean includeDoNotShow = includeDoNotShow(profile);
945 final RefManagerImpl refManager = (RefManagerImpl)getRefManager();
946 refManager.inspectionReadActionStarted();
947 List<ProblemDescriptor> descriptors = new ArrayList<>();
948 Set<PsiFile> files = new HashSet<>();
950 scope.accept(new PsiElementVisitor() {
953 public void visitFile(PsiFile file) {
954 if (progressIndicator != null) {
955 progressIndicator.setFraction((double)++myCount / fileCount);
957 if (isBinary(file)) return;
958 final List<LocalInspectionToolWrapper> lTools = new ArrayList<>();
959 for (final Tools tools : inspectionTools) {
960 InspectionToolWrapper tool = tools.getEnabledTool(file, includeDoNotShow);
961 if (tool instanceof GlobalInspectionToolWrapper) {
962 tool = ((GlobalInspectionToolWrapper)tool).getSharedLocalInspectionToolWrapper();
965 lTools.add((LocalInspectionToolWrapper)tool);
966 tool.initialize(GlobalInspectionContextImpl.this);
970 if (!lTools.isEmpty()) {
972 final LocalInspectionsPass pass = new LocalInspectionsPass(file, PsiDocumentManager.getInstance(getProject()).getDocument(file), range != null ? range.getStartOffset() : 0,
973 range != null ? range.getEndOffset() : file.getTextLength(), LocalInspectionsPass.EMPTY_PRIORITY_RANGE, true,
974 HighlightInfoProcessor.getEmpty());
975 Runnable runnable = () -> pass.doInspectInBatch(GlobalInspectionContextImpl.this, InspectionManager.getInstance(getProject()), lTools);
976 ApplicationManager.getApplication().runReadAction(runnable);
978 final Set<ProblemDescriptor> localDescriptors = new TreeSet<>(CommonProblemDescriptor.DESCRIPTOR_COMPARATOR);
979 for (LocalInspectionToolWrapper tool : lTools) {
980 InspectionToolPresentation toolPresentation = getPresentation(tool);
981 for (CommonProblemDescriptor descriptor : toolPresentation.getProblemDescriptors()) {
982 if (descriptor instanceof ProblemDescriptor) {
983 localDescriptors.add((ProblemDescriptor)descriptor);
988 if (searchScope instanceof LocalSearchScope) {
989 for (Iterator<ProblemDescriptor> iterator = localDescriptors.iterator(); iterator.hasNext(); ) {
990 final ProblemDescriptor descriptor = iterator.next();
991 final TextRange infoRange = descriptor instanceof ProblemDescriptorBase ? ((ProblemDescriptorBase)descriptor).getTextRange() : null;
992 if (infoRange != null && !((LocalSearchScope)searchScope).containsRange(file, infoRange)) {
997 if (!localDescriptors.isEmpty()) {
998 descriptors.addAll(localDescriptors);
1003 myPresentationMap.clear();
1010 refManager.inspectionReadActionFinished();
1013 if (files.isEmpty()) {
1014 GuiUtils.invokeLaterIfNeeded(() -> {
1015 if (commandName != null) {
1016 NOTIFICATION_GROUP.createNotification(InspectionsBundle.message("inspection.no.problems.message", scope.getFileCount(), scope.getDisplayName()), MessageType.INFO).notify(getProject());
1018 if (postRunnable != null) {
1021 }, ModalityState.defaultModalityState());
1025 Runnable runnable = () -> {
1026 if (!FileModificationService.getInstance().preparePsiElementsForWrite(files)) return;
1027 CleanupInspectionIntention.applyFixesNoSort(getProject(), "Code Cleanup", descriptors, null);
1028 if (postRunnable != null) {
1032 TransactionGuard.submitTransaction(getProject(), runnable);
1035 private static boolean isBinary(@NotNull PsiFile file) {
1036 return file instanceof PsiBinaryFile || file.getFileType().isBinary();
1039 public boolean isViewClosed() {
1040 return myViewClosed;
1043 private InspectionRVContentProvider createContentProvider() {
1044 return new InspectionRVContentProviderImpl(getProject());
1047 private void addProblemsToView(List<Tools> tools) {
1048 if (ApplicationManager.getApplication().isUnitTestMode() || ApplicationManager.getApplication().isHeadlessEnvironment()) {
1051 if (myView == null && !ReadAction.compute(() -> InspectionResultsView.hasProblems(tools, this, createContentProvider())).booleanValue()) {
1054 initializeViewIfNeed().doWhenDone(() -> myView.addTools(tools));