/* * Copyright 2000-2016 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.openapi.compiler.util; import com.intellij.codeHighlighting.HighlightDisplayLevel; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.impl.AnnotationHolderImpl; import com.intellij.codeInsight.daemon.impl.HighlightInfo; import com.intellij.codeInspection.*; import com.intellij.codeInspection.ex.LocalInspectionToolWrapper; import com.intellij.compiler.options.ValidationConfiguration; import com.intellij.lang.ExternalLanguageAnnotators; import com.intellij.lang.StdLanguages; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationSession; import com.intellij.lang.annotation.ExternalAnnotator; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.openapi.compiler.*; import com.intellij.openapi.compiler.options.ExcludesConfiguration; import com.intellij.openapi.editor.Document; import com.intellij.openapi.module.Module; import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Computable; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.PsiManager; import com.intellij.psi.xml.XmlFile; import com.intellij.util.Processor; import com.intellij.util.containers.ContainerUtil; import com.intellij.util.containers.hash.LinkedHashMap; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.DataInput; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; /** * @author peter */ public class InspectionValidatorWrapper implements Validator { private final InspectionValidator myValidator; private final PsiManager myPsiManager; private final CompilerManager myCompilerManager; private final InspectionManager myInspectionManager; private final InspectionProjectProfileManager myProfileManager; private final PsiDocumentManager myPsiDocumentManager; private static final ThreadLocal<Boolean> ourCompilationThreads = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return Boolean.FALSE; } }; public InspectionValidatorWrapper(final CompilerManager compilerManager, final InspectionManager inspectionManager, final InspectionProjectProfileManager profileManager, final PsiDocumentManager psiDocumentManager, final PsiManager psiManager, final InspectionValidator validator) { myCompilerManager = compilerManager; myInspectionManager = inspectionManager; myProfileManager = profileManager; myPsiDocumentManager = psiDocumentManager; myPsiManager = psiManager; myValidator = validator; } public static boolean isCompilationThread() { return ourCompilationThreads.get().booleanValue(); } private static List<ProblemDescriptor> runInspectionOnFile(@NotNull PsiFile file, @NotNull LocalInspectionTool inspectionTool) { InspectionManager inspectionManager = InspectionManager.getInstance(file.getProject()); GlobalInspectionContext context = inspectionManager.createNewGlobalContext(false); return InspectionEngine.runInspectionOnFile(file, new LocalInspectionToolWrapper(inspectionTool), context); } private class MyValidatorProcessingItem implements ProcessingItem { private final VirtualFile myVirtualFile; private final PsiManager myPsiManager; private PsiElementsValidityState myValidityState; public MyValidatorProcessingItem(@NotNull final PsiFile psiFile) { myVirtualFile = psiFile.getVirtualFile(); myPsiManager = psiFile.getManager(); } @Override @NotNull public VirtualFile getFile() { return myVirtualFile; } @Override @Nullable public ValidityState getValidityState() { if (myValidityState == null) { myValidityState = computeValidityState(); } return myValidityState; } private PsiElementsValidityState computeValidityState() { final PsiElementsValidityState state = new PsiElementsValidityState(); final PsiFile psiFile = getPsiFile(); if (psiFile != null) { for (PsiElement psiElement : myValidator.getDependencies(psiFile)) { state.addDependency(psiElement); } } return state; } @Nullable public PsiFile getPsiFile() { return myVirtualFile.isValid() ? myPsiManager.findFile(myVirtualFile) : null; } } @Override @NotNull public ProcessingItem[] getProcessingItems(final CompileContext context) { final Project project = context.getProject(); if (project.isDefault() || !ValidationConfiguration.shouldValidate(this, context)) { return ProcessingItem.EMPTY_ARRAY; } final ExcludesConfiguration excludesConfiguration = ValidationConfiguration.getExcludedEntriesConfiguration(project); final List<ProcessingItem> items = DumbService.getInstance(project).runReadActionInSmartMode((Computable<List<ProcessingItem>>)() -> { final CompileScope compileScope = context.getCompileScope(); if (!myValidator.isAvailableOnScope(compileScope)) return null; final ArrayList<ProcessingItem> items1 = new ArrayList<>(); final Processor<VirtualFile> processor = file -> { if (!file.isValid()) { return true; } if (myCompilerManager.isExcludedFromCompilation(file) || excludesConfiguration.isExcluded(file)) { return true; } final Module module = context.getModuleByFile(file); if (module != null) { final PsiFile psiFile = myPsiManager.findFile(file); if (psiFile != null) { items1.add(new MyValidatorProcessingItem(psiFile)); } } return true; }; ContainerUtil.process(myValidator.getFilesToProcess(myPsiManager.getProject(), context), processor); return items1; }); if (items == null) return ProcessingItem.EMPTY_ARRAY; return items.toArray(new ProcessingItem[items.size()]); } @Override public ProcessingItem[] process(final CompileContext context, final ProcessingItem[] items) { context.getProgressIndicator().setText(myValidator.getProgressIndicatorText()); final List<ProcessingItem> processedItems = new ArrayList<>(); final List<LocalInspectionTool> inspections = new ArrayList<>(); for (final Class aClass : myValidator.getInspectionToolClasses(context)) { try { inspections.add((LocalInspectionTool)aClass.newInstance()); } catch (RuntimeException e) { throw e; } catch (Exception e) { throw new Error(e); } } for (int i = 0; i < items.length; i++) { final MyValidatorProcessingItem item = (MyValidatorProcessingItem)items[i]; context.getProgressIndicator().checkCanceled(); context.getProgressIndicator().setFraction((double)i / items.length); try { ourCompilationThreads.set(Boolean.TRUE); if (checkFile(inspections, item, context)) { processedItems.add(item); } } finally { ourCompilationThreads.set(Boolean.FALSE); } } return processedItems.toArray(new ProcessingItem[processedItems.size()]); } private boolean checkFile(List<LocalInspectionTool> inspections, final MyValidatorProcessingItem item, final CompileContext context) { boolean hasErrors = false; if (!checkUnderReadAction(item, context, () -> myValidator.checkAdditionally(item.getPsiFile()))) { hasErrors = true; } if (!checkUnderReadAction(item, context, () -> { final PsiFile file = item.getPsiFile(); if (file instanceof XmlFile) { return runXmlFileSchemaValidation((XmlFile)file); } return Collections.emptyMap(); })) { hasErrors = true; } final InspectionProfile inspectionProfile = myProfileManager.getCurrentProfile(); for (final LocalInspectionTool inspectionTool : inspections) { if (!checkUnderReadAction(item, context, () -> { final PsiFile file = item.getPsiFile(); if (file != null && getHighlightDisplayLevel(inspectionTool, inspectionProfile, file) != HighlightDisplayLevel.DO_NOT_SHOW) { return runInspectionTool(file, inspectionTool, getHighlightDisplayLevel(inspectionTool, inspectionProfile, file) ); } return Collections.emptyMap(); })) { hasErrors = true; } } return !hasErrors; } private boolean checkUnderReadAction(final MyValidatorProcessingItem item, final CompileContext context, final Computable<Map<ProblemDescriptor, HighlightDisplayLevel>> runnable) { return DumbService.getInstance(context.getProject()).runReadActionInSmartMode(() -> { final PsiFile file = item.getPsiFile(); if (file == null) return false; final Document document = myPsiDocumentManager.getCachedDocument(file); if (document != null && myPsiDocumentManager.isUncommited(document)) { final String url = file.getViewProvider().getVirtualFile().getUrl(); context.addMessage(CompilerMessageCategory.WARNING, CompilerBundle.message("warning.text.file.has.been.changed"), url, -1, -1); return false; } if (reportProblems(context, runnable.compute())) return false; return true; }); } private boolean reportProblems(CompileContext context, Map<ProblemDescriptor, HighlightDisplayLevel> problemsMap) { if (problemsMap.isEmpty()) { return false; } boolean errorsReported = false; for (Map.Entry<ProblemDescriptor, HighlightDisplayLevel> entry : problemsMap.entrySet()) { ProblemDescriptor problemDescriptor = entry.getKey(); final PsiElement element = problemDescriptor.getPsiElement(); final PsiFile psiFile = element.getContainingFile(); if (psiFile == null) continue; final VirtualFile virtualFile = psiFile.getVirtualFile(); if (virtualFile == null) continue; final CompilerMessageCategory category = myValidator.getCategoryByHighlightDisplayLevel(entry.getValue(), virtualFile, context); final Document document = myPsiDocumentManager.getDocument(psiFile); final int offset = problemDescriptor.getStartElement().getTextOffset(); assert document != null; final int line = document.getLineNumber(offset); final int column = offset - document.getLineStartOffset(line); context.addMessage(category, problemDescriptor.getDescriptionTemplate(), virtualFile.getUrl(), line + 1, column + 1); if (CompilerMessageCategory.ERROR == category) { errorsReported = true; } } return errorsReported; } private static Map<ProblemDescriptor, HighlightDisplayLevel> runInspectionTool(final PsiFile file, final LocalInspectionTool inspectionTool, final HighlightDisplayLevel level) { Map<ProblemDescriptor, HighlightDisplayLevel> problemsMap = new LinkedHashMap<>(); for (ProblemDescriptor descriptor : runInspectionOnFile(file, inspectionTool)) { final ProblemHighlightType highlightType = descriptor.getHighlightType(); final HighlightDisplayLevel highlightDisplayLevel; if (highlightType == ProblemHighlightType.WEAK_WARNING) { highlightDisplayLevel = HighlightDisplayLevel.WEAK_WARNING; } else if (highlightType == ProblemHighlightType.INFORMATION) { highlightDisplayLevel = HighlightDisplayLevel.DO_NOT_SHOW; } else { highlightDisplayLevel = level; } problemsMap.put(descriptor, highlightDisplayLevel); } return problemsMap; } private static HighlightDisplayLevel getHighlightDisplayLevel(final LocalInspectionTool inspectionTool, final InspectionProfile inspectionProfile, PsiElement file) { final HighlightDisplayKey key = HighlightDisplayKey.find(inspectionTool.getShortName()); return inspectionProfile.isToolEnabled(key, file) ? inspectionProfile.getErrorLevel(key, file) : HighlightDisplayLevel.DO_NOT_SHOW; } private Map<ProblemDescriptor, HighlightDisplayLevel> runXmlFileSchemaValidation(@NotNull XmlFile xmlFile) { final AnnotationHolderImpl holder = new AnnotationHolderImpl(new AnnotationSession(xmlFile)); final List<ExternalAnnotator> annotators = ExternalLanguageAnnotators.allForFile(StdLanguages.XML, xmlFile); for (ExternalAnnotator<?, ?> annotator : annotators) { processAnnotator(xmlFile, holder, annotator); } if (!holder.hasAnnotations()) return Collections.emptyMap(); Map<ProblemDescriptor, HighlightDisplayLevel> problemsMap = new LinkedHashMap<>(); for (final Annotation annotation : holder) { final HighlightInfo info = HighlightInfo.fromAnnotation(annotation); if (info.getSeverity() == HighlightSeverity.INFORMATION) continue; final PsiElement startElement = xmlFile.findElementAt(info.startOffset); final PsiElement endElement = info.startOffset == info.endOffset ? startElement : xmlFile.findElementAt(info.endOffset - 1); if (startElement == null || endElement == null) continue; final ProblemDescriptor descriptor = myInspectionManager.createProblemDescriptor(startElement, endElement, info.getDescription(), ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false); final HighlightDisplayLevel level = info.getSeverity() == HighlightSeverity.ERROR? HighlightDisplayLevel.ERROR: HighlightDisplayLevel.WARNING; problemsMap.put(descriptor, level); } return problemsMap; } private static <X, Y> void processAnnotator(@NotNull XmlFile xmlFile, AnnotationHolderImpl holder, ExternalAnnotator<X, Y> annotator) { X initial = annotator.collectInformation(xmlFile); if (initial != null) { Y result = annotator.doAnnotate(initial); if (result != null) { annotator.apply(xmlFile, result, holder); } } } @Override @NotNull public String getDescription() { return myValidator.getDescription(); } @Override public boolean validateConfiguration(final CompileScope scope) { return true; } @Override public ValidityState createValidityState(final DataInput in) throws IOException { return PsiElementsValidityState.load(in); } }