package com.eslint; import com.eslint.config.ESLintConfigFileListener; import com.eslint.fixes.BaseActionFix; import com.eslint.fixes.Fixes; import com.eslint.fixes.SuppressActionFix; import com.eslint.fixes.SuppressLineActionFix; import com.eslint.utils.ESLintRunner; import com.eslint.utils.Result; import com.eslint.utils.VerifyMessage; import com.intellij.codeInsight.daemon.HighlightDisplayKey; import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; import com.intellij.lang.annotation.Annotation; import com.intellij.lang.annotation.AnnotationHolder; import com.intellij.lang.annotation.ExternalAnnotator; import com.intellij.lang.annotation.HighlightSeverity; import com.intellij.lang.javascript.JavaScriptFileType; import com.intellij.lang.javascript.linter.JSLinterUtil; import com.intellij.lang.javascript.psi.JSFile; import com.intellij.notification.NotificationType; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.Document; import com.intellij.openapi.editor.Editor; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.TextRange; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.profile.codeInspection.InspectionProjectProfileManager; import com.intellij.psi.MultiplePsiFilesPerDocumentFileViewProvider; import com.intellij.psi.PsiDocumentManager; import com.intellij.psi.PsiElement; import com.intellij.psi.PsiFile; import com.intellij.psi.codeStyle.CommonCodeStyleSettings; import com.wix.ActualFile; import com.wix.ThreadLocalActualFile; import com.wix.annotator.ExternalLintAnnotationInput; import com.wix.annotator.ExternalLintAnnotationResult; import com.wix.utils.FileUtils; import com.wix.utils.PsiUtil; import org.apache.commons.lang.StringUtils; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; /** * @author idok */ public class ESLintExternalAnnotator extends ExternalAnnotator<ExternalLintAnnotationInput, ExternalLintAnnotationResult<Result>> { public static final ESLintExternalAnnotator INSTANCE = new ESLintExternalAnnotator(); private static final Logger LOG = Logger.getInstance(ESLintBundle.LOG_ID); private static final String MESSAGE_PREFIX = "ESLint: "; private static final Key<ThreadLocalActualFile> ESLINT_TEMP_FILE_KEY = Key.create("ESLINT_TEMP_FILE"); // private static final int TABS = 4; // private int tabSize; // private static int getTabSize(@NotNull Editor editor) { // // Get tab size // int tabSize = 0; // Project project = editor.getProject(); // PsiFile psifile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); // CommonCodeStyleSettings commonCodeStyleSettings = new CommonCodeStyleSettings(psifile.getLanguage()); // CommonCodeStyleSettings.IndentOptions indentOptions = commonCodeStyleSettings.getIndentOptions(); // // if (indentOptions != null) { // tabSize = commonCodeStyleSettings.getIndentOptions().TAB_SIZE; // } // if (tabSize == 0) { // tabSize = editor.getSettings().getTabSize(editor.getProject()); // } // return tabSize; // } @Nullable @Override public ExternalLintAnnotationInput collectInformation(@NotNull PsiFile file) { return collectInformation(file, null); } @Nullable @Override public ExternalLintAnnotationInput collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) { return collectInformation(file, editor); } @NotNull public static HighlightDisplayKey getHighlightDisplayKeyByClass() { String id = "ESLint"; HighlightDisplayKey key = HighlightDisplayKey.find(id); if (key == null) { key = new HighlightDisplayKey(id, id); } return key; } @Override public void apply(@NotNull PsiFile file, ExternalLintAnnotationResult<Result> annotationResult, @NotNull AnnotationHolder holder) { if (annotationResult == null) { return; } InspectionProjectProfileManager inspectionProjectProfileManager = InspectionProjectProfileManager.getInstance(file.getProject()); SeverityRegistrar severityRegistrar = inspectionProjectProfileManager.getSeverityRegistrar(); HighlightDisplayKey inspectionKey = getHighlightDisplayKeyByClass(); EditorColorsScheme colorsScheme = annotationResult.input.colorsScheme; Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); if (document == null) { return; } ESLintProjectComponent component = annotationResult.input.project.getComponent(ESLintProjectComponent.class); for (VerifyMessage warn : annotationResult.result.warns) { HighlightSeverity severity = getHighlightSeverity(warn, component.treatAsWarnings); TextAttributes forcedTextAttributes = JSLinterUtil.getTextAttributes(colorsScheme, severityRegistrar, severity); Annotation annotation = createAnnotation(holder, file, document, warn, severity, forcedTextAttributes, false); if (annotation != null) { int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column); PsiElement lit = PsiUtil.getElementAtOffset(file, offset); BaseActionFix actionFix = Fixes.getFixForRule(warn.ruleId, lit); if (actionFix != null) { annotation.registerFix(actionFix, null, inspectionKey); } annotation.registerFix(new SuppressActionFix(warn.ruleId, lit), null, inspectionKey); annotation.registerFix(new SuppressLineActionFix(warn.ruleId, lit), null, inspectionKey); } } } private static HighlightSeverity getHighlightSeverity(VerifyMessage warn, boolean treatAsWarnings) { if (treatAsWarnings) { return HighlightSeverity.WARNING; } return warn.severity == 2 ? HighlightSeverity.ERROR : HighlightSeverity.WARNING; } @Nullable private static Annotation createAnnotation(@NotNull AnnotationHolder holder, @NotNull PsiFile file, @NotNull Document document, @NotNull VerifyMessage warn, @NotNull HighlightSeverity severity, @Nullable TextAttributes forcedTextAttributes, boolean showErrorOnWholeLine) { int line = warn.line - 1; int column = warn.column - 1; if (line < 0 || line >= document.getLineCount()) { return null; } int lineEndOffset = document.getLineEndOffset(line); int lineStartOffset = document.getLineStartOffset(line); int errorLineStartOffset = StringUtil.lineColToOffset(document.getCharsSequence(), line, column); // int errorLineStartOffset = PsiUtil.calcErrorStartOffsetInDocument(document, lineStartOffset, lineEndOffset, column, tab); if (errorLineStartOffset == -1) { return null; } // PsiElement element = file.findElementAt(errorLineStartOffset); TextRange range; if (showErrorOnWholeLine) { range = new TextRange(lineStartOffset, lineEndOffset); } else { // int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column); PsiElement lit = PsiUtil.getElementAtOffset(file, errorLineStartOffset); range = lit.getTextRange(); // range = new TextRange(errorLineStartOffset, errorLineStartOffset + 1); } Annotation annotation = JSLinterUtil.createAnnotation(holder, severity, forcedTextAttributes, range, MESSAGE_PREFIX + warn.message.trim() + " (" + warn.ruleId + ')'); if (annotation != null) { annotation.setAfterEndOfLine(errorLineStartOffset == lineEndOffset); } return annotation; } @Nullable private static ExternalLintAnnotationInput collectInformation(@NotNull PsiFile psiFile, @Nullable Editor editor) { if (psiFile.getContext() != null) { return null; } VirtualFile virtualFile = psiFile.getVirtualFile(); if (virtualFile == null || !virtualFile.isInLocalFileSystem()) { return null; } if (psiFile.getViewProvider() instanceof MultiplePsiFilesPerDocumentFileViewProvider) { return null; } Project project = psiFile.getProject(); ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); if (!component.isSettingsValid() || !component.isEnabled() || !isJavaScriptFile(psiFile, component.ext)) { return null; } Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile); if (document == null) { return null; } String fileContent = document.getText(); if (StringUtil.isEmptyOrSpaces(fileContent)) { return null; } EditorColorsScheme colorsScheme = editor == null ? null : editor.getColorsScheme(); // tabSize = getTabSize(editor); // tabSize = 4; return new ExternalLintAnnotationInput(project, psiFile, fileContent, colorsScheme); } private static boolean isInList(String file, String ext) { if (StringUtils.isEmpty(ext)) { return false; } String[] exts = ext.split(","); for (String ex : exts) { if (file.endsWith(ex)) { return true; } } return false; } private static boolean isJavaScriptFile(PsiFile file, String ext) { return file instanceof JSFile && file.getFileType().equals(JavaScriptFileType.INSTANCE) || isInList(file.getName(), ext); } @Nullable @Override public ExternalLintAnnotationResult<Result> doAnnotate(ExternalLintAnnotationInput collectedInfo) { try { LOG.info("Running ESLint inspection"); PsiFile file = collectedInfo.psiFile; Project project = file.getProject(); ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); if (!component.isSettingsValid() || !component.isEnabled() || !isJavaScriptFile(file, component.ext)) { return null; } ESLintConfigFileListener.start(collectedInfo.project); String relativeFile; ActualFile actualCodeFile = ActualFile.getOrCreateActualFile(ESLINT_TEMP_FILE_KEY, file.getVirtualFile(), collectedInfo.fileContent); if (actualCodeFile == null || actualCodeFile.getFile() == null) { return null; } relativeFile = FileUtils.makeRelative(new File(project.getBasePath()), actualCodeFile.getActualFile()); Result result = ESLintRunner.lint(project.getBasePath(), relativeFile, component.nodeInterpreter, component.eslintExecutable, component.eslintRcFile, component.customRulesPath, component.settings.ext); actualCodeFile.deleteTemp(); if (StringUtils.isNotEmpty(result.errorOutput)) { component.showInfoNotification(result.errorOutput, NotificationType.WARNING); return null; } Document document = PsiDocumentManager.getInstance(project).getDocument(file); if (document == null) { component.showInfoNotification("Error running ESLint inspection: Could not get document for file " + file.getName(), NotificationType.WARNING); LOG.error("Could not get document for file " + file.getName()); return null; } return new ExternalLintAnnotationResult<Result>(collectedInfo, result); } catch (Exception e) { LOG.error("Error running ESLint inspection: ", e); ESLintProjectComponent.showNotification("Error running ESLint inspection: " + e.getMessage(), NotificationType.ERROR); } return null; } }