blob: d8f019a7327e262df1654fb4e3035e571c72b502 [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.codeInspection;
import com.intellij.analysis.AnalysisScope;
import com.intellij.codeInsight.daemon.impl.Divider;
import com.intellij.codeInspection.ex.GlobalInspectionToolWrapper;
import com.intellij.codeInspection.ex.InspectionToolWrapper;
import com.intellij.codeInspection.ex.LocalInspectionToolWrapper;
import com.intellij.codeInspection.reference.RefElement;
import com.intellij.codeInspection.reference.RefEntity;
import com.intellij.codeInspection.reference.RefManagerImpl;
import com.intellij.codeInspection.reference.RefVisitor;
import com.intellij.concurrency.JobLauncher;
import com.intellij.lang.Language;
import com.intellij.openapi.diagnostic.Logger;
import com.intellij.openapi.progress.EmptyProgressIndicator;
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.Condition;
import com.intellij.openapi.util.Conditions;
import com.intellij.openapi.util.ProperTextRange;
import com.intellij.openapi.util.TextRange;
import com.intellij.psi.*;
import com.intellij.util.Processor;
import com.intellij.util.containers.ContainerUtil;
import com.intellij.util.containers.MultiMap;
import com.intellij.util.containers.SmartHashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
public class InspectionEngine {
private static final Logger LOG = Logger.getInstance("#com.intellij.codeInspection.InspectionEngine");
@NotNull
public static PsiElementVisitor createVisitorAndAcceptElements(@NotNull LocalInspectionTool tool,
@NotNull ProblemsHolder holder,
boolean isOnTheFly,
@NotNull LocalInspectionToolSession session,
@NotNull List<PsiElement> elements,
@Nullable Collection<String> languages) {
PsiElementVisitor visitor = tool.buildVisitor(holder, isOnTheFly, session);
//noinspection ConstantConditions
if(visitor == null) {
LOG.error("Tool " + tool + " must not return null from the buildVisitor() method");
}
assert !(visitor instanceof PsiRecursiveElementVisitor || visitor instanceof PsiRecursiveElementWalkingVisitor)
: "The visitor returned from LocalInspectionTool.buildVisitor() must not be recursive. "+tool;
tool.inspectionStarted(session, isOnTheFly);
acceptElements(elements, visitor, languages);
return visitor;
}
public static void acceptElements(@NotNull List<PsiElement> elements,
@NotNull PsiElementVisitor elementVisitor,
@Nullable Collection<String> languages) {
//noinspection ForLoopReplaceableByForEach
for (int i = 0, elementsSize = elements.size(); i < elementsSize; i++) {
PsiElement element = elements.get(i);
if (languages == null || languages.contains(element.getLanguage().getID())) {
element.accept(elementVisitor);
}
ProgressManager.checkCanceled();
}
}
@NotNull
public static List<ProblemDescriptor> inspect(@NotNull final List<LocalInspectionToolWrapper> toolWrappers,
@NotNull final PsiFile file,
@NotNull final InspectionManager iManager,
final boolean isOnTheFly,
boolean failFastOnAcquireReadAction,
@NotNull final ProgressIndicator indicator) {
final Map<String, List<ProblemDescriptor>> problemDescriptors = inspectEx(toolWrappers, file, iManager, isOnTheFly, failFastOnAcquireReadAction, indicator);
final List<ProblemDescriptor> result = new ArrayList<ProblemDescriptor>();
for (List<ProblemDescriptor> group : problemDescriptors.values()) {
result.addAll(group);
}
return result;
}
// public accessibility for Upsource
@NotNull
public static Map<String, List<ProblemDescriptor>> inspectEx(@NotNull final List<LocalInspectionToolWrapper> toolWrappers,
@NotNull final PsiFile file,
@NotNull final InspectionManager iManager,
final boolean isOnTheFly,
boolean failFastOnAcquireReadAction,
@NotNull final ProgressIndicator indicator) {
if (toolWrappers.isEmpty()) return Collections.emptyMap();
final Map<String, List<ProblemDescriptor>> resultDescriptors = new ConcurrentHashMap<String, List<ProblemDescriptor>>();
final List<PsiElement> elements = new ArrayList<PsiElement>();
TextRange range = file.getTextRange();
final LocalInspectionToolSession session = new LocalInspectionToolSession(file, range.getStartOffset(), range.getEndOffset());
Divider.divideInsideAndOutside(file, range.getStartOffset(), range.getEndOffset(), range, elements, new ArrayList<ProperTextRange>(),
Collections.<PsiElement>emptyList(), Collections.<ProperTextRange>emptyList(), true, Conditions.<PsiFile>alwaysTrue());
MultiMap<LocalInspectionToolWrapper, String> toolToLanguages = getToolsForElements(toolWrappers, DumbService.isDumb(file.getProject()), elements, Collections.<PsiElement>emptyList());
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>> entry) {
ProblemsHolder holder = new ProblemsHolder(iManager, file, isOnTheFly);
final LocalInspectionTool tool = entry.getKey().getTool();
Collection<String> languages = entry.getValue();
createVisitorAndAcceptElements(tool, holder, isOnTheFly, session, elements, languages);
tool.inspectionFinished(session, holder);
if (holder.hasResults()) {
resultDescriptors.put(tool.getShortName(), ContainerUtil.filter(holder.getResults(), new Condition<ProblemDescriptor>() {
@Override
public boolean value(ProblemDescriptor descriptor) {
PsiElement element = descriptor.getPsiElement();
return element == null || !SuppressionUtil.inspectionResultSuppressed(element, tool);
}
}));
}
return true;
}
};
JobLauncher.getInstance().invokeConcurrentlyUnderProgress(entries, indicator, failFastOnAcquireReadAction, processor);
return resultDescriptors;
}
@NotNull
public static List<ProblemDescriptor> runInspectionOnFile(@NotNull final PsiFile file,
@NotNull InspectionToolWrapper toolWrapper,
@NotNull final GlobalInspectionContext inspectionContext) {
final InspectionManager inspectionManager = InspectionManager.getInstance(file.getProject());
toolWrapper.initialize(inspectionContext);
RefManagerImpl refManager = (RefManagerImpl)inspectionContext.getRefManager();
refManager.inspectionReadActionStarted();
try {
if (toolWrapper instanceof LocalInspectionToolWrapper) {
return inspect(Collections.singletonList((LocalInspectionToolWrapper)toolWrapper), file, inspectionManager, false, false, new EmptyProgressIndicator());
}
if (toolWrapper instanceof GlobalInspectionToolWrapper) {
final GlobalInspectionTool globalTool = ((GlobalInspectionToolWrapper)toolWrapper).getTool();
final List<ProblemDescriptor> descriptors = new ArrayList<ProblemDescriptor>();
if (globalTool instanceof GlobalSimpleInspectionTool) {
GlobalSimpleInspectionTool simpleTool = (GlobalSimpleInspectionTool)globalTool;
ProblemsHolder problemsHolder = new ProblemsHolder(inspectionManager, file, false);
ProblemDescriptionsProcessor collectProcessor = new ProblemDescriptionsProcessor() {
@Nullable
@Override
public CommonProblemDescriptor[] getDescriptions(@NotNull RefEntity refEntity) {
return descriptors.toArray(new CommonProblemDescriptor[descriptors.size()]);
}
@Override
public void ignoreElement(@NotNull RefEntity refEntity) {
throw new RuntimeException();
}
@Override
public void addProblemElement(@Nullable RefEntity refEntity, @NotNull CommonProblemDescriptor... commonProblemDescriptors) {
if (!(refEntity instanceof RefElement)) return;
PsiElement element = ((RefElement)refEntity).getElement();
convertToProblemDescriptors(element, commonProblemDescriptors, descriptors);
}
@Override
public RefEntity getElement(@NotNull CommonProblemDescriptor descriptor) {
throw new RuntimeException();
}
};
simpleTool.checkFile(file, inspectionManager, problemsHolder, inspectionContext, collectProcessor);
return descriptors;
}
RefElement fileRef = refManager.getReference(file);
final AnalysisScope scope = new AnalysisScope(file);
fileRef.accept(new RefVisitor(){
@Override
public void visitElement(@NotNull RefEntity elem) {
CommonProblemDescriptor[] elemDescriptors = globalTool.checkElement(elem, scope, inspectionManager, inspectionContext);
if (descriptors != null) {
convertToProblemDescriptors(file, elemDescriptors, descriptors);
}
for (RefEntity child : elem.getChildren()) {
child.accept(this);
}
}
});
return descriptors;
}
}
finally {
refManager.inspectionReadActionFinished();
toolWrapper.cleanup(file.getProject());
inspectionContext.cleanup();
}
return Collections.emptyList();
}
private static void convertToProblemDescriptors(PsiElement element,
CommonProblemDescriptor[] commonProblemDescriptors,
List<ProblemDescriptor> descriptors) {
for (CommonProblemDescriptor common : commonProblemDescriptors) {
if (common instanceof ProblemDescriptor) {
descriptors.add((ProblemDescriptor)common);
}
else {
ProblemDescriptorBase base =
new ProblemDescriptorBase(element, element, common.getDescriptionTemplate(), (LocalQuickFix[])common.getFixes(),
ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false, null, false, false);
descriptors.add(base);
}
}
}
@NotNull
public static <T extends InspectionToolWrapper> MultiMap<T, String> getToolsForElements(@NotNull List<T> 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>();
Set<String> dialects = new SmartHashSet<String>();
calculateDialects(inside, languages, langIds, dialects);
calculateDialects(outside, languages, langIds, dialects);
MultiMap<T, String> toolToLanguages = new MultiMap<T, String>() {
@NotNull
@Override
protected Collection<String> createCollection() {
return new SmartHashSet<String>();
}
@NotNull
@Override
protected Collection<String> createEmptyCollection() {
return Collections.emptySet();
}
};
for (T wrapper : toolWrappers) {
ProgressManager.checkCanceled();
String language = wrapper.getLanguage();
if (language == null) {
InspectionProfileEntry tool = wrapper.getTool();
if (!checkDumbAwareness || tool instanceof DumbAware) {
toolToLanguages.put(wrapper, null);
}
continue;
}
Language lang = langIds.get(language);
if (lang != null) {
InspectionProfileEntry tool = wrapper.getTool();
if (!checkDumbAwareness || tool instanceof DumbAware) {
toolToLanguages.putValue(wrapper, language);
if (wrapper.applyToDialects()) {
for (Language dialect : lang.getDialects()) {
toolToLanguages.putValue(wrapper, dialect.getID());
}
}
}
}
else if (wrapper.applyToDialects() && dialects.contains(language)) {
InspectionProfileEntry tool = wrapper.getTool();
if (!checkDumbAwareness || tool instanceof DumbAware) {
toolToLanguages.putValue(wrapper, language);
}
}
}
return toolToLanguages;
}
private static void calculateDialects(@NotNull List<PsiElement> inside,
@NotNull Set<Language> languages,
@NotNull Map<String, Language> langIds,
@NotNull Set<String> dialects) {
for (PsiElement element : inside) {
Language language = element.getLanguage();
if (languages.add(language)) {
langIds.put(language.getID(), language);
for (Language dialect : language.getDialects()) {
dialects.add(dialect.getID());
}
}
}
}
}