package com.intellij.lang.javascript.linter.tslint.highlight; import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; import com.intellij.codeInsight.intention.IntentionAction; import com.intellij.execution.process.ProcessOutput; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.javascript.DialectDetector; import com.intellij.lang.javascript.DialectOptionHolder; import com.intellij.lang.javascript.linter.*; import com.intellij.lang.javascript.linter.tslint.TsLintBundle; import com.intellij.lang.javascript.linter.tslint.config.TsLintConfiguration; import com.intellij.lang.javascript.linter.tslint.config.TsLintState; import com.intellij.lang.javascript.linter.tslint.execution.TsLintConfigFileSearcher; import com.intellij.lang.javascript.linter.tslint.execution.TsLinterError; import com.intellij.lang.javascript.linter.tslint.fix.TsLintErrorFixAction; import com.intellij.lang.javascript.linter.tslint.fix.TsLintFileFixAction; import com.intellij.lang.javascript.linter.tslint.service.TsLintLanguageService; import com.intellij.lang.javascript.linter.tslint.ui.TsLintConfigurable; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.lang.javascript.psi.util.JSUtils; import com.intellij.lang.javascript.service.JSLanguageServiceQueueImpl; import com.intellij.lang.javascript.service.JSLanguageServiceUtil; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.ReadAction; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.fileEditor.FileDocumentManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.io.FileUtil; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiFile; import com.intellij.util.containers.ContainerUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.Set; import java.util.concurrent.Future; import java.util.stream.Collectors; /** * @author Irina.Chernushina on 6/3/2015. */ public final class TsLintExternalAnnotator extends JSLinterWithInspectionExternalAnnotator<TsLintState, TsLinterInput> { private static final TsLintExternalAnnotator INSTANCE_FOR_BATCH_INSPECTION = new TsLintExternalAnnotator(false); @NotNull private final TsLintConfigFileSearcher myConfigFileSearcher; @NotNull public static TsLintExternalAnnotator getInstanceForBatchInspection() { return INSTANCE_FOR_BATCH_INSPECTION; } @SuppressWarnings("unused") public TsLintExternalAnnotator() { this(true); } public TsLintExternalAnnotator(boolean onTheFly) { super(onTheFly); myConfigFileSearcher = new TsLintConfigFileSearcher(); } @NotNull @Override protected JSLinterConfigurable<TsLintState> createSettingsConfigurable(@NotNull Project project) { return new TsLintConfigurable(project, true); } @Override protected Class<? extends JSLinterConfiguration<TsLintState>> getConfigurationClass() { return TsLintConfiguration.class; } @Override protected Class<? extends JSLinterInspection> getInspectionClass() { return TsLintInspection.class; } @Override protected boolean acceptPsiFile(@NotNull PsiFile file) { if (!(file instanceof JSFile)) return false; final TsLintConfiguration configuration = TsLintConfiguration.getInstance(file.getProject()); if (configuration.isAllowJs() && JSUtils.isJavaScriptFile(file)) return true; final DialectOptionHolder holder = DialectDetector.dialectOfFile(file); return holder != null && holder.isTypeScript; } @Nullable @Override protected TsLinterInput createInfo(Project project, @NotNull PsiFile psiFile, TsLintState state, Document document, String fileContent, EditorColorsScheme colorsScheme) { VirtualFile config = myConfigFileSearcher.getConfig(state, psiFile.getVirtualFile()); boolean skipProcessing = config != null && saveConfigFileAndReturnSkipProcessing(psiFile.getProject(), config); if (skipProcessing) { return null; } return new TsLinterInput(project, psiFile, fileContent, state, colorsScheme, config); } @Nullable @Override public JSLinterAnnotationResult<TsLintState> annotate(@NotNull TsLinterInput collectedInfo) { TsLintLanguageService service = TsLintLanguageService.getService(collectedInfo.getProject()); VirtualFile config = collectedInfo.getConfig(); Future<List<TsLinterError>> highlight = service.highlight(collectedInfo.getVirtualFile(), config, collectedInfo.getFileContent()); List<TsLinterError> annotationErrors = JSLanguageServiceUtil.awaitFuture(highlight); if (annotationErrors == null) { if (!service.isServiceCreated() || service.getServiceCreationError() != null) { String error = service.getServiceCreationError(); error = error == null ? JSLanguageServiceQueueImpl.CANNOT_START_LANGUAGE_SERVICE_PROCESS : error; return JSLinterAnnotationResult.create(collectedInfo, new JSLinterFileLevelAnnotation(error), config); } return null; } if (!annotationErrors.isEmpty()) { final Optional<TsLinterError> globalError = annotationErrors.stream().filter(error -> error.isGlobal()).findFirst(); if (globalError.isPresent()) { final JSLinterAnnotationResult<TsLintState> annotation = createGlobalErrorMessage(collectedInfo, config, globalError.get().getDescription()); if (annotation != null) return annotation; } } List<JSLinterError> result = filterResultByFile(collectedInfo, annotationErrors); return JSLinterAnnotationResult.createLinterResult(collectedInfo, result, config); } public List<JSLinterError> filterResultByFile(@NotNull TsLinterInput collectedInfo, List<TsLinterError> annotationErrors) { final String filePath = collectedInfo.getVirtualFile().getPath(); final String fileName = collectedInfo.getPsiFile().getName(); final Set<String> filteredPaths = annotationErrors.stream().map(TsLinterError::getAbsoluteFilePath) .distinct() .filter(path -> { if (path == null) return true; if (!path.endsWith(fileName)) return false; return FileUtil.pathsEqual(filePath, path); }).collect(Collectors.toSet()); return annotationErrors.stream().filter(el -> filteredPaths.contains(el.getAbsoluteFilePath())).collect(Collectors.toList()); } @Nullable private static JSLinterAnnotationResult<TsLintState> createGlobalErrorMessage(@NotNull TsLinterInput collectedInfo, @Nullable VirtualFile config, @Nullable String error) { if (!StringUtil.isEmptyOrSpaces(error)) { final ProcessOutput output = new ProcessOutput(); output.appendStderr(error); final IntentionAction detailsAction = JSLinterUtil.createDetailsAction(collectedInfo.getProject(), collectedInfo.getVirtualFile(), null, output, null); final JSLinterFileLevelAnnotation annotation = new JSLinterFileLevelAnnotation(error, detailsAction); return JSLinterAnnotationResult.create(collectedInfo, annotation, config); } return null; } protected void cleanNotification(@NotNull TsLinterInput collectedInfo) { JSLinterEditorNotificationPanel.clearNotification(collectedInfo.getProject(), getInspectionClass(), collectedInfo.getVirtualFile()); } public boolean saveConfigFileAndReturnSkipProcessing(@NotNull Project project, @NotNull VirtualFile config) { return ReadAction.compute(() -> { final FileDocumentManager manager = FileDocumentManager.getInstance(); Document document = manager.getCachedDocument(config); if (document != null) { boolean unsaved = manager.isDocumentUnsaved(document); if (unsaved) { ApplicationManager.getApplication().invokeLater(() -> { Document newDocument = manager.getCachedDocument(config); if (newDocument != null) { FileDocumentManager.getInstance().saveDocument(newDocument); } DaemonCodeAnalyzer.getInstance(project).restart(); }, project.getDisposed()); } return unsaved; } return false; }); } @Override public void apply(@NotNull PsiFile file, @Nullable JSLinterAnnotationResult<TsLintState> annotationResult, @NotNull AnnotationHolder holder) { if (annotationResult == null) return; TsLintConfigurable configurable = new TsLintConfigurable(file.getProject(), true); final Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); IntentionAction fixAllFileIntention = new TsLintFileFixAction().asIntentionAction(); JSLinterStandardFixes fixes = new JSLinterStandardFixes() { @Override public List<IntentionAction> createListForError(@Nullable VirtualFile configFile, @NotNull UntypedJSLinterConfigurable configurable, @NotNull JSLinterErrorBase errorBase) { List<IntentionAction> defaultIntentions = super.createListForError(configFile, configurable, errorBase); if (errorBase instanceof TsLinterError && ((TsLinterError)errorBase).hasFix()) { ArrayList<IntentionAction> result = ContainerUtil.newArrayList(); if (document != null && myOnTheFly) { result.add(new TsLintErrorFixAction((TsLinterError)errorBase, document)); } result.add(fixAllFileIntention); result.addAll(defaultIntentions); return result; } return defaultIntentions; } }; new JSLinterAnnotationsBuilder<>(file, annotationResult, holder, TsLintInspection.getHighlightDisplayKey(), configurable, TsLintBundle.message("tslint.framework.title") + ": ", getInspectionClass(), fixes) .setHighlightingGranularity(HighlightingGranularity.element).apply(document); } }