Java tutorial
/* * Copyright 2000-2013 JetBrains s.r.o. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.intellij.codeInsight.daemon.impl; import com.intellij.codeHighlighting.Pass; import com.intellij.codeInsight.daemon.DaemonBundle; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.impl.analysis.HighlightingLevelManager; import com.intellij.codeInsight.daemon.impl.quickfix.QuickFixAction; import com.intellij.codeInsight.intention.EmptyIntentionAction; import com.intellij.codeInspection.*; import com.intellij.codeInspection.ex.*; import com.intellij.codeInspection.ui.InspectionToolPresentation; import com.intellij.concurrency.JobLauncher; import com.intellij.injected.editor.DocumentWindow; import com.intellij.lang.Language; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.lang.injection.InjectedLanguageManager; import com.intellij.openapi.actionSystem.IdeActions; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.RangeMarker; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.keymap.Keymap; import com.intellij.openapi.keymap.KeymapManager; import com.intellij.openapi.keymap.KeymapUtil; import com.intellij.openapi.progress.ProcessCanceledException; import com.intellij.openapi.progress.ProgressIndicator; import com.intellij.openapi.progress.ProgressManager; import com.intellij.openapi.project.DumbAware; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.util.*; import com.intellij.openapi.util.text.StringUtil; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.profile.codeInspection.InspectionProjectProfileManagerImpl; import com.intellij.profile.codeInspection.SeverityProvider; import com.intellij.psi.*; import com.intellij.psi.impl.source.tree.injected.InjectedLanguageUtil; import com.intellij.util.ConcurrencyUtil; import com.intellij.util.Function; import com.intellij.util.Processor; import com.intellij.util.containers.ConcurrentHashMap; import com.intellij.util.containers.MultiMap; import com.intellij.util.containers.TransferToEDTQueue; import com.intellij.util.ui.UIUtil; import com.intellij.xml.util.XmlStringUtil; import gnu.trove.THashMap; import gnu.trove.THashSet; import org.jetbrains.annotations.NonNls; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.*; import java.util.concurrent.ConcurrentMap; /** * @author max */ public class LocalInspectionsPass extends ProgressableTextEditorHighlightingPass implements DumbAware { private static final Logger LOG = Logger .getInstance("#com.intellij.codeInsight.daemon.impl.LocalInspectionsPass"); public static final TextRange EMPTY_PRIORITY_RANGE = TextRange.EMPTY_RANGE; private static final Condition<PsiFile> FILE_FILTER = new Condition<PsiFile>() { @Override public boolean value(PsiFile file) { return HighlightingLevelManager.getInstance(file.getProject()).shouldInspect(file); } }; private final int myStartOffset; private final int myEndOffset; private final TextRange myPriorityRange; private final boolean myIgnoreSuppressed; private final ConcurrentMap<PsiFile, List<InspectionResult>> result = new ConcurrentHashMap<PsiFile, List<InspectionResult>>(); private static final String PRESENTABLE_NAME = DaemonBundle.message("pass.inspection"); private volatile List<HighlightInfo> myInfos = Collections.emptyList(); private final String myShortcutText; private final SeverityRegistrar mySeverityRegistrar; private final InspectionProfileWrapper myProfileWrapper; private boolean myFailFastOnAcquireReadAction; public LocalInspectionsPass(@NotNull PsiFile file, @Nullable Document document, int startOffset, int endOffset, @NotNull TextRange priorityRange, boolean ignoreSuppressed, @NotNull HighlightInfoProcessor highlightInfoProcessor) { super(file.getProject(), document, PRESENTABLE_NAME, file, null, new TextRange(startOffset, endOffset), true, highlightInfoProcessor); myStartOffset = startOffset; myEndOffset = endOffset; myPriorityRange = priorityRange; myIgnoreSuppressed = ignoreSuppressed; setId(Pass.LOCAL_INSPECTIONS); final KeymapManager keymapManager = KeymapManager.getInstance(); if (keymapManager != null) { final Keymap keymap = keymapManager.getActiveKeymap(); myShortcutText = keymap == null ? "" : "(" + KeymapUtil .getShortcutsText(keymap.getShortcuts(IdeActions.ACTION_SHOW_ERROR_DESCRIPTION)) + ")"; } else { myShortcutText = ""; } InspectionProfileWrapper profileToUse = InspectionProjectProfileManagerImpl.getInstanceImpl(myProject) .getProfileWrapper(); Function<InspectionProfileWrapper, InspectionProfileWrapper> custom = file .getUserData(InspectionProfileWrapper.CUSTOMIZATION_KEY); if (custom != null) { profileToUse = custom.fun(profileToUse); } myProfileWrapper = profileToUse; assert myProfileWrapper != null; mySeverityRegistrar = ((SeverityProvider) myProfileWrapper.getInspectionProfile().getProfileManager()) .getSeverityRegistrar(); // initial guess setProgressLimit(300 * 2); } @Override protected void collectInformationWithProgress(@NotNull ProgressIndicator progress) { try { if (!HighlightingLevelManager.getInstance(myProject).shouldInspect(myFile)) return; final InspectionManagerEx iManager = (InspectionManagerEx) InspectionManager.getInstance(myProject); final InspectionProfileWrapper profile = myProfileWrapper; inspect(getInspectionTools(profile), iManager, true, true, DumbService.isDumb(myProject), progress); } finally { disposeDescriptors(); } } private void disposeDescriptors() { result.clear(); } public void doInspectInBatch(@NotNull final GlobalInspectionContextImpl context, @NotNull final InspectionManagerEx iManager, @NotNull final List<LocalInspectionToolWrapper> toolWrappers) { final ProgressIndicator progress = ProgressManager.getInstance().getProgressIndicator(); inspect(new ArrayList<LocalInspectionToolWrapper>(toolWrappers), iManager, false, false, false, progress); addDescriptorsFromInjectedResults(iManager, context); List<InspectionResult> resultList = result.get(myFile); if (resultList == null) return; for (InspectionResult inspectionResult : resultList) { LocalInspectionToolWrapper toolWrapper = inspectionResult.tool; for (ProblemDescriptor descriptor : inspectionResult.foundProblems) { addDescriptors(toolWrapper, descriptor, context); } } } private void addDescriptors(@NotNull LocalInspectionToolWrapper toolWrapper, @NotNull ProblemDescriptor descriptor, @NotNull GlobalInspectionContextImpl context) { InspectionToolPresentation toolPresentation = context.getPresentation(toolWrapper); LocalDescriptorsUtil.addProblemDescriptors(Collections.singletonList(descriptor), toolPresentation, myIgnoreSuppressed, context, toolWrapper.getTool()); } private void addDescriptorsFromInjectedResults(@NotNull InspectionManagerEx iManager, @NotNull GlobalInspectionContextImpl context) { InjectedLanguageManager ilManager = InjectedLanguageManager.getInstance(myProject); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); for (Map.Entry<PsiFile, List<InspectionResult>> entry : result.entrySet()) { PsiFile file = entry.getKey(); if (file == myFile) continue; // not injected DocumentWindow documentRange = (DocumentWindow) documentManager.getDocument(file); List<InspectionResult> resultList = entry.getValue(); for (InspectionResult inspectionResult : resultList) { LocalInspectionToolWrapper toolWrapper = inspectionResult.tool; for (ProblemDescriptor descriptor : inspectionResult.foundProblems) { PsiElement psiElement = descriptor.getPsiElement(); if (psiElement == null) continue; if (SuppressionUtil.inspectionResultSuppressed(psiElement, toolWrapper.getTool())) continue; List<TextRange> editables = ilManager.intersectWithAllEditableFragments(file, ((ProblemDescriptorBase) descriptor).getTextRange()); for (TextRange editable : editables) { TextRange hostRange = documentRange.injectedToHost(editable); QuickFix[] fixes = descriptor.getFixes(); LocalQuickFix[] localFixes = null; if (fixes != null) { localFixes = new LocalQuickFix[fixes.length]; for (int k = 0; k < fixes.length; k++) { QuickFix fix = fixes[k]; localFixes[k] = (LocalQuickFix) fix; } } ProblemDescriptor patchedDescriptor = iManager.createProblemDescriptor(myFile, hostRange, descriptor.getDescriptionTemplate(), descriptor.getHighlightType(), true, localFixes); addDescriptors(toolWrapper, patchedDescriptor, context); } } } } } private void inspect(@NotNull final List<LocalInspectionToolWrapper> toolWrappers, @NotNull final InspectionManagerEx iManager, final boolean isOnTheFly, boolean failFastOnAcquireReadAction, boolean checkDumbAwareness, @NotNull final ProgressIndicator progress) { myFailFastOnAcquireReadAction = failFastOnAcquireReadAction; if (toolWrappers.isEmpty()) return; List<PsiElement> inside = new ArrayList<PsiElement>(); List<PsiElement> outside = new ArrayList<PsiElement>(); Divider.divideInsideAndOutside(myFile, myStartOffset, myEndOffset, myPriorityRange, inside, new ArrayList<ProperTextRange>(), outside, new ArrayList<ProperTextRange>(), true, FILE_FILTER); MultiMap<LocalInspectionToolWrapper, String> toolToLanguages = getToolsForElements(toolWrappers, checkDumbAwareness, inside, outside); setProgressLimit(toolToLanguages.size() * 2L); final LocalInspectionToolSession session = new LocalInspectionToolSession(myFile, myStartOffset, myEndOffset); List<InspectionContext> init = visitPriorityElementsAndInit(toolToLanguages, iManager, isOnTheFly, progress, inside, session, toolWrappers, checkDumbAwareness); visitRestElementsAndCleanup(progress, outside, session, init); inspectInjectedPsi(outside, isOnTheFly, progress, iManager, false, checkDumbAwareness, toolWrappers); progress.checkCanceled(); myInfos = new ArrayList<HighlightInfo>(); addHighlightsFromResults(myInfos, progress); } @NotNull private static MultiMap<LocalInspectionToolWrapper, String> getToolsForElements( @NotNull List<LocalInspectionToolWrapper> toolWrappers, boolean checkDumbAwareness, @NotNull List<PsiElement> inside, @NotNull List<PsiElement> outside) { Set<Language> languages = new SmartHashSet<Language>(); Map<String, Language> langIds = new SmartHashMap<String, Language>(); for (PsiElement element : inside) { Language language = element.getLanguage(); if (languages.add(language)) { langIds.put(language.getID(), language); } } for (PsiElement element : outside) { Language language = element.getLanguage(); if (languages.add(language)) { langIds.put(language.getID(), language); } } MultiMap<LocalInspectionToolWrapper, String> toolToLanguages = new MultiMap<LocalInspectionToolWrapper, String>() { @Override protected Collection<String> createCollection() { return new SmartHashSet<String>(); } @Override protected Collection<String> createEmptyCollection() { return Collections.emptySet(); } }; for (LocalInspectionToolWrapper wrapper : toolWrappers) { String language = wrapper.getLanguage(); if (language == null) { LocalInspectionTool tool = wrapper.getTool(); if (!checkDumbAwareness || tool instanceof DumbAware) { toolToLanguages.put(wrapper, null); } continue; } Language lang = langIds.get(language); if (lang != null) { LocalInspectionTool tool = wrapper.getTool(); if (!checkDumbAwareness || tool instanceof DumbAware) { toolToLanguages.putValue(wrapper, language); } } } return toolToLanguages; } @NotNull private List<InspectionContext> visitPriorityElementsAndInit( @NotNull MultiMap<LocalInspectionToolWrapper, String> toolToLanguages, @NotNull final InspectionManagerEx iManager, final boolean isOnTheFly, @NotNull final ProgressIndicator indicator, @NotNull final List<PsiElement> elements, @NotNull final LocalInspectionToolSession session, @NotNull List<LocalInspectionToolWrapper> wrappers, boolean checkDumbAwareness) { final List<InspectionContext> init = new ArrayList<InspectionContext>(); List<Map.Entry<LocalInspectionToolWrapper, Collection<String>>> entries = new ArrayList<Map.Entry<LocalInspectionToolWrapper, Collection<String>>>( toolToLanguages.entrySet()); Processor<Map.Entry<LocalInspectionToolWrapper, Collection<String>>> processor = new Processor<Map.Entry<LocalInspectionToolWrapper, Collection<String>>>() { @Override public boolean process(final Map.Entry<LocalInspectionToolWrapper, Collection<String>> pair) { return runToolOnElements(pair.getKey(), pair.getValue(), iManager, isOnTheFly, indicator, elements, session, init); } }; boolean result = JobLauncher.getInstance().invokeConcurrentlyUnderProgress(entries, indicator, myFailFastOnAcquireReadAction, processor); if (!result) throw new ProcessCanceledException(); inspectInjectedPsi(elements, isOnTheFly, indicator, iManager, true, checkDumbAwareness, wrappers); return init; } private boolean runToolOnElements(@NotNull final LocalInspectionToolWrapper toolWrapper, Collection<String> languages, @NotNull final InspectionManagerEx iManager, final boolean isOnTheFly, @NotNull final ProgressIndicator indicator, @NotNull final List<PsiElement> elements, @NotNull final LocalInspectionToolSession session, @NotNull List<InspectionContext> init) { indicator.checkCanceled(); ApplicationManager.getApplication().assertReadAccessAllowed(); LocalInspectionTool tool = toolWrapper.getTool(); final boolean[] applyIncrementally = { isOnTheFly }; ProblemsHolder holder = new ProblemsHolder(iManager, myFile, isOnTheFly) { @Override public void registerProblem(@NotNull ProblemDescriptor descriptor) { super.registerProblem(descriptor); if (applyIncrementally[0]) { addDescriptorIncrementally(descriptor, toolWrapper, indicator); } } }; PsiElementVisitor visitor = InspectionEngine.createVisitorAndAcceptElements(tool, holder, isOnTheFly, session, elements, languages); synchronized (init) { init.add(new InspectionContext(toolWrapper, holder, visitor, languages)); } advanceProgress(1); if (holder.hasResults()) { appendDescriptors(myFile, holder.getResults(), toolWrapper); } applyIncrementally[0] = false; // do not apply incrementally outside visible range return true; } private void visitRestElementsAndCleanup(@NotNull final ProgressIndicator indicator, @NotNull final List<PsiElement> elements, @NotNull final LocalInspectionToolSession session, @NotNull List<InspectionContext> init) { Processor<InspectionContext> processor = new Processor<InspectionContext>() { @Override public boolean process(InspectionContext context) { indicator.checkCanceled(); ApplicationManager.getApplication().assertReadAccessAllowed(); InspectionEngine.acceptElements(elements, context.visitor, context.languageIds); advanceProgress(1); context.tool.getTool().inspectionFinished(session, context.holder); if (context.holder.hasResults()) { appendDescriptors(myFile, context.holder.getResults(), context.tool); } return true; } }; boolean result = JobLauncher.getInstance().invokeConcurrentlyUnderProgress(init, indicator, myFailFastOnAcquireReadAction, processor); if (!result) { throw new ProcessCanceledException(); } } void inspectInjectedPsi(@NotNull final List<PsiElement> elements, final boolean onTheFly, @NotNull final ProgressIndicator indicator, @NotNull final InspectionManagerEx iManager, final boolean inVisibleRange, final boolean checkDumbAwareness, @NotNull final List<LocalInspectionToolWrapper> wrappers) { final Set<PsiFile> injected = new THashSet<PsiFile>(); for (PsiElement element : elements) { InjectedLanguageUtil.enumerate(element, myFile, false, new PsiLanguageInjectionHost.InjectedPsiVisitor() { @Override public void visit(@NotNull PsiFile injectedPsi, @NotNull List<PsiLanguageInjectionHost.Shred> places) { injected.add(injectedPsi); } }); } if (injected.isEmpty()) return; if (!JobLauncher.getInstance().invokeConcurrentlyUnderProgress(new ArrayList<PsiFile>(injected), indicator, myFailFastOnAcquireReadAction, new Processor<PsiFile>() { @Override public boolean process(final PsiFile injectedPsi) { doInspectInjectedPsi(injectedPsi, onTheFly, indicator, iManager, inVisibleRange, wrappers, checkDumbAwareness); return true; } })) throw new ProcessCanceledException(); } @Nullable private HighlightInfo highlightInfoFromDescriptor(@NotNull ProblemDescriptor problemDescriptor, @NotNull HighlightInfoType highlightInfoType, @NotNull String message, String toolTip, PsiElement psiElement) { TextRange textRange = ((ProblemDescriptorBase) problemDescriptor).getTextRange(); if (textRange == null || psiElement == null) return null; boolean isFileLevel = psiElement instanceof PsiFile && textRange.equals(psiElement.getTextRange()); final HighlightSeverity severity = highlightInfoType.getSeverity(psiElement); TextAttributes attributes = mySeverityRegistrar.getTextAttributesBySeverity(severity); HighlightInfo.Builder b = HighlightInfo.newHighlightInfo(highlightInfoType) .range(psiElement, textRange.getStartOffset(), textRange.getEndOffset()).description(message) .severity(severity); if (toolTip != null) b.escapedToolTip(toolTip); if (attributes != null) b.textAttributes(attributes); if (problemDescriptor.isAfterEndOfLine()) b.endOfLine(); if (isFileLevel) b.fileLevelAnnotation(); if (problemDescriptor.getProblemGroup() != null) b.problemGroup(problemDescriptor.getProblemGroup()); return b.create(); } private final Map<TextRange, RangeMarker> ranges2markersCache = new THashMap<TextRange, RangeMarker>(); private final TransferToEDTQueue<Trinity<ProblemDescriptor, LocalInspectionToolWrapper, ProgressIndicator>> myTransferToEDTQueue = new TransferToEDTQueue<Trinity<ProblemDescriptor, LocalInspectionToolWrapper, ProgressIndicator>>( "Apply inspection results", new Processor<Trinity<ProblemDescriptor, LocalInspectionToolWrapper, ProgressIndicator>>() { private final InspectionProfile inspectionProfile = InspectionProjectProfileManager .getInstance(myProject).getInspectionProfile(); private final InjectedLanguageManager ilManager = InjectedLanguageManager.getInstance(myProject); private final List<HighlightInfo> infos = new ArrayList<HighlightInfo>(2); private final PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); @Override public boolean process( Trinity<ProblemDescriptor, LocalInspectionToolWrapper, ProgressIndicator> trinity) { ProgressIndicator indicator = trinity.getThird(); if (indicator.isCanceled()) { return false; } ProblemDescriptor descriptor = trinity.first; LocalInspectionToolWrapper tool = trinity.second; PsiElement psiElement = descriptor.getPsiElement(); if (psiElement == null) return true; PsiFile file = psiElement.getContainingFile(); Document thisDocument = documentManager.getDocument(file); HighlightSeverity severity = inspectionProfile .getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), file).getSeverity(); infos.clear(); createHighlightsForDescriptor(infos, emptyActionRegistered, ilManager, file, thisDocument, tool, severity, descriptor, psiElement); for (HighlightInfo info : infos) { final EditorColorsScheme colorsScheme = getColorsScheme(); UpdateHighlightersUtil.addHighlighterToEditorIncrementally(myProject, myDocument, myFile, myStartOffset, myEndOffset, info, colorsScheme, getId(), ranges2markersCache); } return true; } }, myProject.getDisposed(), 200); private final Set<Pair<TextRange, String>> emptyActionRegistered = Collections .synchronizedSet(new THashSet<Pair<TextRange, String>>()); private void addDescriptorIncrementally(@NotNull final ProblemDescriptor descriptor, @NotNull final LocalInspectionToolWrapper tool, @NotNull final ProgressIndicator indicator) { if (myIgnoreSuppressed && SuppressionUtil.inspectionResultSuppressed(descriptor.getPsiElement(), tool.getTool())) { return; } myTransferToEDTQueue.offer(Trinity.create(descriptor, tool, indicator)); } private void appendDescriptors(@NotNull PsiFile file, @NotNull List<ProblemDescriptor> descriptors, @NotNull LocalInspectionToolWrapper tool) { for (ProblemDescriptor descriptor : descriptors) { if (descriptor == null) { LOG.error("null descriptor. all descriptors(" + descriptors.size() + "): " + descriptors + "; file: " + file + " (" + file.getVirtualFile() + "); tool: " + tool); } } InspectionResult res = new InspectionResult(tool, descriptors); appendResult(file, res); } private void appendResult(@NotNull PsiFile file, @NotNull InspectionResult res) { List<InspectionResult> resultList = result.get(file); if (resultList == null) { resultList = ConcurrencyUtil.cacheOrGet(result, file, new ArrayList<InspectionResult>()); } synchronized (resultList) { resultList.add(res); } } @Override protected void applyInformationWithProgress() { UpdateHighlightersUtil.setHighlightersToEditor(myProject, myDocument, myStartOffset, myEndOffset, myInfos, getColorsScheme(), getId()); } private void addHighlightsFromResults(@NotNull List<HighlightInfo> outInfos, @NotNull ProgressIndicator indicator) { InspectionProfile inspectionProfile = InspectionProjectProfileManager.getInstance(myProject) .getInspectionProfile(); PsiDocumentManager documentManager = PsiDocumentManager.getInstance(myProject); InjectedLanguageManager ilManager = InjectedLanguageManager.getInstance(myProject); Set<Pair<TextRange, String>> emptyActionRegistered = new THashSet<Pair<TextRange, String>>(); for (Map.Entry<PsiFile, List<InspectionResult>> entry : result.entrySet()) { indicator.checkCanceled(); PsiFile file = entry.getKey(); Document documentRange = documentManager.getDocument(file); if (documentRange == null) continue; List<InspectionResult> resultList = entry.getValue(); synchronized (resultList) { for (InspectionResult inspectionResult : resultList) { indicator.checkCanceled(); LocalInspectionToolWrapper tool = inspectionResult.tool; HighlightSeverity severity = inspectionProfile .getErrorLevel(HighlightDisplayKey.find(tool.getShortName()), file).getSeverity(); for (ProblemDescriptor descriptor : inspectionResult.foundProblems) { indicator.checkCanceled(); PsiElement element = descriptor.getPsiElement(); createHighlightsForDescriptor(outInfos, emptyActionRegistered, ilManager, file, documentRange, tool, severity, descriptor, element); } } } } } private void createHighlightsForDescriptor(@NotNull List<HighlightInfo> outInfos, @NotNull Set<Pair<TextRange, String>> emptyActionRegistered, @NotNull InjectedLanguageManager ilManager, @NotNull PsiFile file, @NotNull Document documentRange, @NotNull LocalInspectionToolWrapper tool, @NotNull HighlightSeverity severity, @NotNull ProblemDescriptor descriptor, PsiElement element) { if (element == null) return; if (myIgnoreSuppressed && SuppressionUtil.inspectionResultSuppressed(element, tool.getTool())) return; HighlightInfoType level = ProblemDescriptorUtil.highlightTypeFromDescriptor(descriptor, severity, mySeverityRegistrar); HighlightInfo info = createHighlightInfo(descriptor, tool, level, emptyActionRegistered, element); if (info == null) return; if (file == myFile) { // not injected outInfos.add(info); return; } // todo we got to separate our "internal" prefixes/suffixes from user-defined ones // todo in the latter case the errors should be highlighted, otherwise not List<TextRange> editables = ilManager.intersectWithAllEditableFragments(file, new TextRange(info.startOffset, info.endOffset)); for (TextRange editable : editables) { TextRange hostRange = ((DocumentWindow) documentRange).injectedToHost(editable); int start = hostRange.getStartOffset(); int end = hostRange.getEndOffset(); HighlightInfo.Builder builder = HighlightInfo.newHighlightInfo(info.type).range(element, start, end); String description = info.getDescription(); if (description != null) { builder.description(description); } String toolTip = info.getToolTip(); if (toolTip != null) { builder.escapedToolTip(toolTip); } HighlightInfo patched = builder.createUnconditionally(); if (patched.startOffset != patched.endOffset || info.startOffset == info.endOffset) { patched.setFromInjection(true); registerQuickFixes(tool, descriptor, patched, emptyActionRegistered); outInfos.add(patched); } } } @Nullable private HighlightInfo createHighlightInfo(@NotNull ProblemDescriptor descriptor, @NotNull LocalInspectionToolWrapper tool, @NotNull HighlightInfoType level, @NotNull Set<Pair<TextRange, String>> emptyActionRegistered, @NotNull PsiElement element) { @NonNls String message = ProblemDescriptorUtil.renderDescriptionMessage(descriptor, element); final HighlightDisplayKey key = HighlightDisplayKey.find(tool.getShortName()); final InspectionProfile inspectionProfile = myProfileWrapper.getInspectionProfile(); if (!inspectionProfile.isToolEnabled(key, myFile)) return null; HighlightInfoType type = new HighlightInfoType.HighlightInfoTypeImpl(level.getSeverity(element), level.getAttributesKey()); final String plainMessage = message.startsWith("<html>") ? StringUtil.unescapeXml(XmlStringUtil.stripHtml(message).replaceAll("<[^>]*>", "")) : message; @NonNls final String link = " <a " + "href=\"#inspection/" + tool.getShortName() + "\"" + (UIUtil.isUnderDarcula() ? " color=\"7AB4C9\" " : "") + ">" + DaemonBundle.message("inspection.extended.description") + "</a> " + myShortcutText; @NonNls String tooltip = null; if (descriptor.showTooltip()) { if (message.startsWith("<html>")) { tooltip = XmlStringUtil.wrapInHtml(XmlStringUtil.stripHtml(message) + link); } else { tooltip = XmlStringUtil.wrapInHtml(XmlStringUtil.escapeString(message) + link); } } HighlightInfo highlightInfo = highlightInfoFromDescriptor(descriptor, type, plainMessage, tooltip, element); if (highlightInfo != null) { registerQuickFixes(tool, descriptor, highlightInfo, emptyActionRegistered); } return highlightInfo; } private static void registerQuickFixes(@NotNull LocalInspectionToolWrapper tool, @NotNull ProblemDescriptor descriptor, @NotNull HighlightInfo highlightInfo, @NotNull Set<Pair<TextRange, String>> emptyActionRegistered) { final HighlightDisplayKey key = HighlightDisplayKey.find(tool.getShortName()); boolean needEmptyAction = true; final QuickFix[] fixes = descriptor.getFixes(); if (fixes != null && fixes.length > 0) { for (int k = 0; k < fixes.length; k++) { if (fixes[k] != null) { // prevent null fixes from var args QuickFixAction.registerQuickFixAction(highlightInfo, QuickFixWrapper.wrap(descriptor, k), key); needEmptyAction = false; } } } HintAction hintAction = descriptor instanceof ProblemDescriptorImpl ? ((ProblemDescriptorImpl) descriptor).getHintAction() : null; if (hintAction != null) { QuickFixAction.registerQuickFixAction(highlightInfo, hintAction, key); needEmptyAction = false; } if (((ProblemDescriptorBase) descriptor).getEnforcedTextAttributes() != null) { needEmptyAction = false; } if (needEmptyAction && emptyActionRegistered .add(Pair.<TextRange, String>create(highlightInfo.getFixTextRange(), tool.getShortName()))) { EmptyIntentionAction emptyIntentionAction = new EmptyIntentionAction(tool.getDisplayName()); QuickFixAction.registerQuickFixAction(highlightInfo, emptyIntentionAction, key); } } @NotNull private static List<PsiElement> getElementsFrom(@NotNull PsiFile file) { final FileViewProvider viewProvider = file.getViewProvider(); final Set<PsiElement> result = new LinkedHashSet<PsiElement>(); final PsiElementVisitor visitor = new PsiRecursiveElementVisitor() { @Override public void visitElement(PsiElement element) { ProgressManager.checkCanceled(); PsiElement child = element.getFirstChild(); if (child == null) { // leaf element } else { // composite element while (child != null) { child.accept(this); result.add(child); child = child.getNextSibling(); } } } }; for (Language language : viewProvider.getLanguages()) { final PsiFile psiRoot = viewProvider.getPsi(language); if (psiRoot == null || !HighlightingLevelManager.getInstance(file.getProject()).shouldInspect(psiRoot)) { continue; } psiRoot.accept(visitor); result.add(psiRoot); } return new ArrayList<PsiElement>(result); } @NotNull private List<LocalInspectionToolWrapper> getHighlightingLocalInspectionTools( @NotNull InspectionProfileWrapper profile, PsiElement element) { List<LocalInspectionToolWrapper> enabled = new ArrayList<LocalInspectionToolWrapper>(); final InspectionToolWrapper[] toolWrappers = profile.getInspectionTools(element); InspectionProfileWrapper.checkInspectionsDuplicates(toolWrappers); Language language = myFile.getLanguage(); for (InspectionToolWrapper toolWrapper : toolWrappers) { if (!profile.isToolEnabled(HighlightDisplayKey.find(toolWrapper.getShortName()), element)) continue; LocalInspectionToolWrapper wrapper = null; if (toolWrapper instanceof LocalInspectionToolWrapper) { wrapper = (LocalInspectionToolWrapper) toolWrapper; } if (wrapper == null) continue; if (myIgnoreSuppressed) { if (wrapper.isApplicable(language) && SuppressionUtil.inspectionResultSuppressed(myFile, wrapper.getTool())) { continue; } } enabled.add(wrapper); } return enabled; } @NotNull List<LocalInspectionToolWrapper> getInspectionTools(@NotNull InspectionProfileWrapper profile) { return getHighlightingLocalInspectionTools(profile, myFile); } private void doInspectInjectedPsi(@NotNull PsiFile injectedPsi, final boolean isOnTheFly, @NotNull final ProgressIndicator indicator, @NotNull InspectionManagerEx iManager, final boolean inVisibleRange, @NotNull List<LocalInspectionToolWrapper> wrappers, boolean checkDumbAwareness) { final PsiElement host = InjectedLanguageManager.getInstance(injectedPsi.getProject()) .getInjectionHost(injectedPsi); final List<PsiElement> elements = getElementsFrom(injectedPsi); if (elements.isEmpty()) { return; } MultiMap<LocalInspectionToolWrapper, String> toolToLanguages = getToolsForElements(wrappers, checkDumbAwareness, elements, Collections.<PsiElement>emptyList()); for (final Map.Entry<LocalInspectionToolWrapper, Collection<String>> pair : toolToLanguages.entrySet()) { indicator.checkCanceled(); final LocalInspectionToolWrapper wrapper = pair.getKey(); final LocalInspectionTool tool = wrapper.getTool(); if (host != null && myIgnoreSuppressed && SuppressionUtil.inspectionResultSuppressed(host, tool)) { continue; } ProblemsHolder holder = new ProblemsHolder(iManager, injectedPsi, isOnTheFly) { @Override public void registerProblem(@NotNull ProblemDescriptor descriptor) { super.registerProblem(descriptor); if (isOnTheFly && inVisibleRange) { addDescriptorIncrementally(descriptor, wrapper, indicator); } } }; LocalInspectionToolSession injSession = new LocalInspectionToolSession(injectedPsi, 0, injectedPsi.getTextLength()); Collection<String> languages = pair.getValue(); InspectionEngine.createVisitorAndAcceptElements(tool, holder, isOnTheFly, injSession, elements, languages); tool.inspectionFinished(injSession, holder); List<ProblemDescriptor> problems = holder.getResults(); if (!problems.isEmpty()) { appendDescriptors(injectedPsi, problems, wrapper); } } } @Override @NotNull public List<HighlightInfo> getInfos() { return myInfos; } private static class InspectionResult { @NotNull public final LocalInspectionToolWrapper tool; @NotNull public final List<ProblemDescriptor> foundProblems; private InspectionResult(@NotNull LocalInspectionToolWrapper tool, @NotNull List<ProblemDescriptor> foundProblems) { this.tool = tool; this.foundProblems = foundProblems; } } private static class InspectionContext { private InspectionContext(@NotNull LocalInspectionToolWrapper tool, @NotNull ProblemsHolder holder, @NotNull PsiElementVisitor visitor, @Nullable Collection<String> languageIds) { this.tool = tool; this.holder = holder; this.visitor = visitor; this.languageIds = languageIds; } @NotNull final LocalInspectionToolWrapper tool; @NotNull final ProblemsHolder holder; @NotNull final PsiElementVisitor visitor; @Nullable final Collection<String> languageIds; } }