blob: 0761379652ac6bc15b4e9e953dc2e2e0cfa07c8b [file] [log] [blame]
/*
* Copyright 2000-2014 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.*;
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.LinkedHashSet;
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);
assert file.isPhysical() : "can't inspect non-physical file: " + file + "; " + file.getVirtualFile();
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 = InspectionEngine.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 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;
Processor<PsiFile> processor = new Processor<PsiFile>() {
@Override
public boolean process(final PsiFile injectedPsi) {
doInspectInjectedPsi(injectedPsi, onTheFly, indicator, iManager, inVisibleRange, wrappers, checkDumbAwareness);
return true;
}
};
if (!JobLauncher.getInstance().invokeConcurrentlyUnderProgress(new ArrayList<PsiFile>(injected), indicator, myFailFastOnAcquireReadAction, processor)) {
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) {
ProgressManager.checkCanceled();
if (!profile.isToolEnabled(HighlightDisplayKey.find(toolWrapper.getShortName()), element)) continue;
LocalInspectionToolWrapper wrapper = null;
if (toolWrapper instanceof LocalInspectionToolWrapper) {
wrapper = (LocalInspectionToolWrapper)toolWrapper;
}
else if (toolWrapper instanceof GlobalInspectionToolWrapper) {
final GlobalInspectionToolWrapper globalInspectionToolWrapper = (GlobalInspectionToolWrapper)toolWrapper;
wrapper = globalInspectionToolWrapper.getSharedLocalInspectionToolWrapper();
}
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 =
InspectionEngine.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;
}
}