├── .gitignore ├── LICENSE ├── README.md ├── doc ├── config.png ├── inspect-inline.png └── inspect.png ├── resources ├── META-INF │ ├── plugin.xml │ └── pluginIcon.svg ├── com │ └── eslint │ │ └── config │ │ └── schema │ │ └── schema.json └── inspectionDescriptions │ └── ESLintInspection.html ├── src ├── com │ └── eslint │ │ ├── ESLintBundle.java │ │ ├── ESLintBundle.properties │ │ ├── ESLintExternalAnnotator.java │ │ ├── ESLintInspection.java │ │ ├── ESLintProjectComponent.java │ │ ├── actions │ │ └── ESLintFixAction.java │ │ ├── config │ │ ├── ESLintConfigFileListener.java │ │ ├── ESLintConfigFileType.java │ │ ├── ESLintConfigFileTypeFactory.java │ │ ├── ESLintConfigFileUtil.java │ │ └── schema │ │ │ ├── BaseType.java │ │ │ ├── ESLintSchema.java │ │ │ ├── RuleCache.java │ │ │ ├── RuntimeTypeAdapterFactory.java │ │ │ └── SchemaJsonObject.java │ │ ├── fixes │ │ ├── BaseActionFix.java │ │ ├── DotNotationActionFix.java │ │ ├── EqeqeqActionFix.java │ │ ├── Fixes.java │ │ ├── NoArrayConstructorActionFix.java │ │ ├── NoLonelyIfActionFix.java │ │ ├── NoNegatedInLhsActionFix.java │ │ ├── NoNewBaseActionFix.java │ │ ├── NoNewObjectActionFix.java │ │ ├── StrictActionFix.java │ │ ├── SuppressActionFix.java │ │ └── SuppressLineActionFix.java │ │ ├── inspection │ │ └── PropertySuppressableInspectionBase.java │ │ ├── settings │ │ ├── ESLintSettingsPage.form │ │ ├── ESLintSettingsPage.java │ │ └── Settings.java │ │ └── utils │ │ ├── CliBuilder.java │ │ ├── ESLintFinder.java │ │ ├── ESLintRunner.java │ │ ├── FileResult.java │ │ ├── JSBinaryExpressionUtil.java │ │ ├── Result.java │ │ └── VerifyMessage.java └── icons │ ├── ESLintIcons.java │ └── fileTypes │ ├── eslint.png │ └── eslint16x16.png ├── testData ├── .eslintrc └── inspections │ ├── eqeqeq.js │ ├── no-array-constructor.js │ ├── no-lonely-if.js │ ├── no-negated-in-lhs.js │ ├── no-new-object.js │ └── valid-typeof.js └── tests └── com └── eslint ├── ESLintRunnerTest.java ├── ESLintTest.java └── TestUtils.java /.gitignore: -------------------------------------------------------------------------------- 1 | ### OSX ### 2 | .DS_Store 3 | 4 | ### intellij ### 5 | .idea/ 6 | out/ 7 | 8 | *.iml 9 | 10 | ### plugin binary ### 11 | eslint-plugin.jar 12 | eslint-plugin.zip 13 | 14 | temp 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Idok 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of 6 | this software and associated documentation files (the "Software"), to deal in 7 | the Software without restriction, including without limitation the rights to 8 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software is furnished to do so, 10 | subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 17 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 18 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 19 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 20 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ESLint Plugin # 2 | 3 | [ESLint](http://eslint.org/) is The pluggable linting utility for JavaScript. see more [here](http://eslint.org/).
4 | ESLint plugin for WebStorm, PHPStorm and other Idea family IDE with Javascript plugin, provides integration with ESLint and shows errors and warnings inside the editor. 5 | * Support displaying eslint warnings as intellij inspections 6 | * Quick fixes for several rules 7 | * Support for custom eslint rules 8 | 9 | ## Bundled plugin ## 10 | As of Intellij 14, a plugin based on this one was bundled into the IDE release by Jetbrains. 11 | What's the diffrence? 12 | This plugin supports Intellij 13 and other versions, --fix option, quick fixes and other minor differences. 13 | Please make sure you are referring to this one before opening an issue. 14 | 15 | 16 | ## Getting started ## 17 | ### Prerequisites ### 18 | * [NodeJS](http://nodejs.org/) 19 | * IntelliJ 13.1.4 / Webstorm 8.0.4, or above. 20 | 21 | Install eslint npm package [eslint npm](https://www.npmjs.org/package/eslint):
22 | ```bash 23 | $ cd 24 | $ npm install eslint 25 | ``` 26 | Or, install eslint globally:
27 | ```bash 28 | $ npm install -g eslint 29 | ``` 30 | 31 | ### Settings ### 32 | To get started, you need to set the ESLint plugin settings:
33 | 34 | * Go to preferences, ESLint plugin page and check the Enable plugin. 35 | * Set the path to the nodejs interpreter bin file. 36 | * Select whether to let eslint search for ```.eslintrc``` file 37 | * Set the path to the eslint bin file. should point to ```node_modules/eslint/bin/eslint.js``` if you installed locally or ```/usr/local/bin/eslint``` if you installed globally. 38 | * For Windows: install eslint globally and point to the eslint cmd file like, e.g. ```C:\Users\\AppData\Roaming\npm\eslint.cmd``` 39 | * Set the ```.eslintrc``` file, or eslint will use the default settings. 40 | * You can also set a path to a custom rules directory. 41 | * By default, eslint plugin annotate the editor with warning or error based on the eslint configuration, you can check the 'Treat all eslint issues as warnings' checkbox to display all issues from eslint as warnings. 42 | 43 | Configuration:
44 | ![ESLint config](https://raw.githubusercontent.com/idok/eslint-plugin/master/doc/config.png) 45 | 46 | 47 | Inspection:
48 | ![ESLint inline](https://raw.githubusercontent.com/idok/eslint-plugin/master/doc/inspect-inline.png) 49 | 50 | 51 | Analyze Code:
52 | ![ESLint inline](https://raw.githubusercontent.com/idok/eslint-plugin/master/doc/inspect.png) 53 | 54 | ### A Note to contributors ### 55 | ESLint plugin uses the code from [here](https://github.com/idok/scss-lint-plugin/tree/master/intellij-common) as a module, to run the project you need to clone that project as well. 56 | 57 | -------------------------------------------------------------------------------- /doc/config.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idok/eslint-plugin/08cead4d3a9da827e77513ea8300e6af881bef1b/doc/config.png -------------------------------------------------------------------------------- /doc/inspect-inline.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idok/eslint-plugin/08cead4d3a9da827e77513ea8300e6af881bef1b/doc/inspect-inline.png -------------------------------------------------------------------------------- /doc/inspect.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idok/eslint-plugin/08cead4d3a9da827e77513ea8300e6af881bef1b/doc/inspect.png -------------------------------------------------------------------------------- /resources/META-INF/plugin.xml: -------------------------------------------------------------------------------- 1 | 2 | com.wix.eslint 3 | ESLint 4 | 1.0.36 5 | Ido 6 | HTML/JavaScript Development 7 | 9 |

Support displaying eslint warnings as intellij inspections

10 |

Quick fixes for several rules

11 |

Support for custom eslint rules

12 |

Support for eslint config annotation and completion

13 | ]]>
14 | 15 | 1.0.36 Bug fixes

17 |

1.0.35 Bug fixes

18 |

1.0.34 Bug fixes

19 |

1.0.33 Bug fixes

20 |

1.0.32 Bug fixes

21 |

1.0.31 Bug fixes

22 |

1.0.30 Fix NPE

23 |

1.0.29 Bug fixes

24 |

1.0.28 Fix default settings, support rc files with extensions, bug fixes

25 |

1.0.27 Bug fixes

26 |

1.0.26 Bug fixes

27 |

1.0.25 Add --ext. Bug fixes

28 |

1.0.24 Add --fix option as an action from code menu. Bug fixes

29 |

1.0.23 Class loading bug fixed

30 |

1.0.22 Intellij 14 / webstorm 9 compatible version

31 |

1.0.21 Bug fixes, doesn't take tab size into account

32 |

1.0.20 Add settings for 33 | eslint builtin rules directory to support completion and annotation on eslintrc files

34 |

1.0.19 Fix windows execution and solve version compatibility issue, thanks eric-isakson

35 |

1.0.18 fix plugin url

36 |

1.0.17 fix default eslint bin

37 |

1.0.16 fix issue finding eslint bin in windows

38 |

1.0.15 fix lag in settings dialog, fix windows issue with running lint

39 |

1.0.14 fix eslintrc configuration in settings

40 |

1.0.13 bug fix

41 |

1.0.12 bug fix related to relative path of eslint

42 |

1.0.11 bug fixes, add quick fix to DotNotation rule

43 |

1.0.10 Add annotation and completion to eslintrc, add version to settings dialog, fix eslintrc not loading bug

44 |

1.0.9 Fix performance issue

45 |

1.0.8 Performance improvements, bug fixes, refresh inspection status when eslintrc or config change

46 |

1.0.7 Fix ClassNotFound Error

47 |

1.0.6 Add configuration options for node interpreter and eslintrc search, bug fixes

48 |

1.0.1 First version.

49 | ]]>
50 | 51 | com.intellij.modules.lang 52 | 53 | JavaScript 54 | 55 | 56 | 57 | 58 | 59 | 61 | 64 | 65 | 66 | 67 | 68 | 69 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | com.eslint.ESLintProjectComponent 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 |
103 | -------------------------------------------------------------------------------- /resources/META-INF/pluginIcon.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | 7 | 10 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /resources/com/eslint/config/schema/schema.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "root", 3 | "type": "OBJECT", 4 | "description": "", 5 | "properties": [ 6 | { 7 | "title": "env", 8 | "type": "OBJECT", 9 | "description": "running environment", 10 | "properties": [ 11 | { 12 | "title": "amd", 13 | "type": "BOOLEAN", 14 | "description": "amd" 15 | }, 16 | { 17 | "title": "node", 18 | "type": "BOOLEAN", 19 | "description": "node" 20 | }, 21 | { 22 | "title": "browser", 23 | "type": "BOOLEAN", 24 | "description": "browser" 25 | } 26 | ] 27 | }, 28 | { 29 | "title": "globals", 30 | "type": "OBJECT", 31 | "description": "globals", 32 | "properties": [ 33 | { 34 | "title": "*", 35 | "type": "BOOLEAN", 36 | "description": "" 37 | } 38 | ] 39 | }, 40 | { 41 | "title": "rules", 42 | "type": "OBJECT", 43 | "description": "rules", 44 | "properties": [] 45 | } 46 | ] 47 | } 48 | -------------------------------------------------------------------------------- /resources/inspectionDescriptions/ESLintInspection.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Runs ESLint validator for specified JavaScript file. 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/com/eslint/ESLintBundle.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | import com.intellij.CommonBundle; 4 | import org.jetbrains.annotations.NonNls; 5 | import org.jetbrains.annotations.NotNull; 6 | import org.jetbrains.annotations.PropertyKey; 7 | 8 | import java.lang.ref.Reference; 9 | import java.lang.ref.SoftReference; 10 | import java.util.ResourceBundle; 11 | 12 | public final class ESLintBundle { 13 | 14 | public static String message(@NotNull @PropertyKey(resourceBundle = BUNDLE) String key, @NotNull Object... params) { 15 | return CommonBundle.message(getBundle(), key, params); 16 | } 17 | 18 | @NonNls 19 | public static final String BUNDLE = "com.eslint.ESLintBundle"; 20 | private static Reference ourBundle; 21 | 22 | @NonNls 23 | public static final String LOG_ID = "#com.eslint"; 24 | 25 | private ESLintBundle() { 26 | } 27 | 28 | private static ResourceBundle getBundle() { 29 | ResourceBundle bundle = com.intellij.reference.SoftReference.dereference(ourBundle); 30 | if (bundle == null) { 31 | bundle = ResourceBundle.getBundle(BUNDLE); 32 | ourBundle = new SoftReference(bundle); 33 | } 34 | return bundle; 35 | } 36 | } -------------------------------------------------------------------------------- /src/com/eslint/ESLintBundle.properties: -------------------------------------------------------------------------------- 1 | #Inspection suppression 2 | unused.property.suppress.for.statement=Suppress for statement 3 | unused.property.suppress.for.block=Suppress for block 4 | unused.property.suppress.for.file=Suppress for file 5 | 6 | #Inspections 7 | eslint.inspection.group.name=Code quality tools 8 | eslint.inspection.undefined.step.name=Undefined step 9 | eslint.inspection.undefined.step.msg.name=Undefined step reference: 10 | eslint.property.inspection.display.name=ESLint 11 | eslint.property.inspection.message=ESLint: {0} ({1}) 12 | 13 | eslint.rules.dir.does.not.exist=Rules directory not found. Path {0} not found in project 14 | eslint.rules.dir.is.not.a.dir=Rules path is not a directory. Path {0} not found in project 15 | 16 | eslint.directory.does.not.exist={0} directory not found. Path {1} not found in project 17 | eslint.directory.is.not.a.dir={0} path is not a directory. Path {1} not found in project 18 | eslint.file.does.not.exist={0} file not found. Path {1} not found in project 19 | eslint.file.is.not.a.file={0} path is not a file. Path {1} not found in project 20 | 21 | inspection.fix.strict=Add 'use strict' statement 22 | inspection.fix.no.new.object=Convert to {} 23 | inspection.fix.no-array-constructor=Convert to [] 24 | inspection.fix.eqeqeq=Convert to === 25 | inspection.fix.no-lonely-if=Convert to else if 26 | inspection.fix.no-negated-in-lhs=Wrap with parenthesis 27 | inspection.fix.dot-notation=Convert to dot notation 28 | 29 | 30 | properties.files.inspection.group.display.name=ESLint 31 | -------------------------------------------------------------------------------- /src/com/eslint/ESLintExternalAnnotator.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | import com.eslint.config.ESLintConfigFileListener; 4 | import com.eslint.fixes.BaseActionFix; 5 | import com.eslint.fixes.Fixes; 6 | import com.eslint.fixes.SuppressActionFix; 7 | import com.eslint.fixes.SuppressLineActionFix; 8 | import com.eslint.settings.ESLintSettingsPage; 9 | import com.eslint.utils.ESLintRunner; 10 | import com.eslint.utils.Result; 11 | import com.eslint.utils.VerifyMessage; 12 | import com.intellij.codeInsight.daemon.HighlightDisplayKey; 13 | import com.intellij.codeInsight.daemon.impl.SeverityRegistrar; 14 | import com.intellij.lang.annotation.Annotation; 15 | import com.intellij.lang.annotation.AnnotationHolder; 16 | import com.intellij.lang.annotation.ExternalAnnotator; 17 | import com.intellij.lang.annotation.HighlightSeverity; 18 | import com.intellij.lang.javascript.JavaScriptFileType; 19 | import com.intellij.lang.javascript.linter.JSLinterUtil; 20 | import com.intellij.lang.javascript.psi.JSFile; 21 | import com.intellij.notification.NotificationType; 22 | import com.intellij.openapi.application.ApplicationManager; 23 | import com.intellij.openapi.application.ModalityState; 24 | import com.intellij.openapi.application.WriteAction; 25 | import com.intellij.openapi.diagnostic.Logger; 26 | import com.intellij.openapi.editor.Document; 27 | import com.intellij.openapi.editor.Editor; 28 | import com.intellij.openapi.editor.colors.EditorColorsScheme; 29 | import com.intellij.openapi.editor.markup.TextAttributes; 30 | import com.intellij.openapi.project.Project; 31 | import com.intellij.openapi.util.Key; 32 | import com.intellij.openapi.util.TextRange; 33 | import com.intellij.openapi.util.text.StringUtil; 34 | import com.intellij.openapi.vfs.VirtualFile; 35 | import com.intellij.profile.codeInspection.InspectionProjectProfileManager; 36 | import com.intellij.psi.MultiplePsiFilesPerDocumentFileViewProvider; 37 | import com.intellij.psi.PsiDocumentManager; 38 | import com.intellij.psi.PsiElement; 39 | import com.intellij.psi.PsiFile; 40 | import com.intellij.psi.codeStyle.CommonCodeStyleSettings; 41 | import com.intellij.util.ThrowableRunnable; 42 | import com.intellij.util.ui.UIUtil; 43 | import com.wix.ActualFile; 44 | import com.wix.ThreadLocalActualFile; 45 | import com.wix.annotator.ExternalLintAnnotationInput; 46 | import com.wix.annotator.ExternalLintAnnotationResult; 47 | import com.wix.utils.FileUtils; 48 | import com.wix.utils.PsiUtil; 49 | import org.apache.commons.lang.StringUtils; 50 | import org.jetbrains.annotations.NotNull; 51 | import org.jetbrains.annotations.Nullable; 52 | 53 | import java.io.File; 54 | 55 | /** 56 | * @author idok 57 | */ 58 | public class ESLintExternalAnnotator extends ExternalAnnotator> { 59 | 60 | // public static final ESLintExternalAnnotator INSTANCE = new ESLintExternalAnnotator(); 61 | private static final Logger LOG = Logger.getInstance(ESLintBundle.LOG_ID); 62 | private static final String MESSAGE_PREFIX = "ESLint: "; 63 | private static final Key ESLINT_TEMP_FILE_KEY = Key.create("ESLINT_TEMP_FILE"); 64 | // private static final int TABS = 4; 65 | // private int tabSize; 66 | 67 | // private static int getTabSize(@NotNull Editor editor) { 68 | // // Get tab size 69 | // int tabSize = 0; 70 | // Project project = editor.getProject(); 71 | // PsiFile psifile = PsiDocumentManager.getInstance(project).getPsiFile(editor.getDocument()); 72 | // CommonCodeStyleSettings commonCodeStyleSettings = new CommonCodeStyleSettings(psifile.getLanguage()); 73 | // CommonCodeStyleSettings.IndentOptions indentOptions = commonCodeStyleSettings.getIndentOptions(); 74 | // 75 | // if (indentOptions != null) { 76 | // tabSize = commonCodeStyleSettings.getIndentOptions().TAB_SIZE; 77 | // } 78 | // if (tabSize == 0) { 79 | // tabSize = editor.getSettings().getTabSize(editor.getProject()); 80 | // } 81 | // return tabSize; 82 | // } 83 | 84 | @Nullable 85 | @Override 86 | public ExternalLintAnnotationInput collectInformation(@NotNull PsiFile file) { 87 | return collectInformation(file, null); 88 | } 89 | 90 | @Nullable 91 | @Override 92 | public ExternalLintAnnotationInput collectInformation(@NotNull PsiFile file, @NotNull Editor editor, boolean hasErrors) { 93 | return collectInformation(file, editor); 94 | } 95 | 96 | @NotNull 97 | public static HighlightDisplayKey getHighlightDisplayKeyByClass() { 98 | String id = "ESLint"; 99 | HighlightDisplayKey key = HighlightDisplayKey.find(id); 100 | if (key == null) { 101 | key = new HighlightDisplayKey(id, id); 102 | } 103 | return key; 104 | } 105 | 106 | @Override 107 | public void apply(@NotNull PsiFile file, ExternalLintAnnotationResult annotationResult, @NotNull AnnotationHolder holder) { 108 | if (annotationResult == null) { 109 | return; 110 | } 111 | InspectionProjectProfileManager inspectionProjectProfileManager = InspectionProjectProfileManager.getInstance(file.getProject()); 112 | SeverityRegistrar severityRegistrar = inspectionProjectProfileManager.getSeverityRegistrar(); 113 | HighlightDisplayKey inspectionKey = getHighlightDisplayKeyByClass(); 114 | EditorColorsScheme colorsScheme = annotationResult.input.colorsScheme; 115 | 116 | Document document = PsiDocumentManager.getInstance(file.getProject()).getDocument(file); 117 | if (document == null) { 118 | return; 119 | } 120 | ESLintProjectComponent component = annotationResult.input.project.getComponent(ESLintProjectComponent.class); 121 | for (VerifyMessage warn : annotationResult.result.warns) { 122 | HighlightSeverity severity = getHighlightSeverity(warn, component.treatAsWarnings); 123 | TextAttributes forcedTextAttributes = JSLinterUtil.getTextAttributes(colorsScheme, severityRegistrar, severity); 124 | Annotation annotation = createAnnotation(holder, file, document, warn, severity, forcedTextAttributes, false); 125 | if (annotation != null) { 126 | int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column); 127 | PsiElement lit = PsiUtil.getElementAtOffset(file, offset); 128 | BaseActionFix actionFix = Fixes.getFixForRule(warn.ruleId, lit); 129 | if (actionFix != null) { 130 | annotation.registerFix(actionFix, null, inspectionKey); 131 | } 132 | annotation.registerFix(new SuppressActionFix(warn.ruleId, lit), null, inspectionKey); 133 | annotation.registerFix(new SuppressLineActionFix(warn.ruleId, lit), null, inspectionKey); 134 | } 135 | } 136 | } 137 | 138 | private static HighlightSeverity getHighlightSeverity(VerifyMessage warn, boolean treatAsWarnings) { 139 | if (treatAsWarnings) { 140 | return HighlightSeverity.WARNING; 141 | } 142 | return warn.severity == 2 ? HighlightSeverity.ERROR : HighlightSeverity.WARNING; 143 | } 144 | 145 | @Nullable 146 | private static Annotation createAnnotation(@NotNull AnnotationHolder holder, @NotNull PsiFile file, @NotNull Document document, @NotNull VerifyMessage warn, 147 | @NotNull HighlightSeverity severity, @Nullable TextAttributes forcedTextAttributes, 148 | boolean showErrorOnWholeLine) { 149 | int line = warn.line - 1; 150 | int column = warn.column - 1; 151 | 152 | if (line < 0 || line >= document.getLineCount()) { 153 | return null; 154 | } 155 | int lineEndOffset = document.getLineEndOffset(line); 156 | int lineStartOffset = document.getLineStartOffset(line); 157 | 158 | int errorLineStartOffset = StringUtil.lineColToOffset(document.getCharsSequence(), line, column); 159 | // int errorLineStartOffset = PsiUtil.calcErrorStartOffsetInDocument(document, lineStartOffset, lineEndOffset, column, tab); 160 | 161 | if (errorLineStartOffset == -1) { 162 | return null; 163 | } 164 | // PsiElement element = file.findElementAt(errorLineStartOffset); 165 | TextRange range; 166 | if (showErrorOnWholeLine) { 167 | range = new TextRange(lineStartOffset, lineEndOffset); 168 | } else { 169 | // int offset = StringUtil.lineColToOffset(document.getText(), warn.line - 1, warn.column); 170 | PsiElement lit = PsiUtil.getElementAtOffset(file, errorLineStartOffset); 171 | range = lit.getTextRange(); 172 | // range = new TextRange(errorLineStartOffset, errorLineStartOffset + 1); 173 | } 174 | 175 | Annotation annotation = JSLinterUtil.createAnnotation(holder, severity, forcedTextAttributes, range, MESSAGE_PREFIX + warn.message.trim() + " (" + warn.ruleId + ')'); 176 | if (annotation != null) { 177 | annotation.setAfterEndOfLine(errorLineStartOffset == lineEndOffset); 178 | } 179 | return annotation; 180 | } 181 | 182 | @Nullable 183 | private static ExternalLintAnnotationInput collectInformation(@NotNull PsiFile psiFile, @Nullable Editor editor) { 184 | if (psiFile.getContext() != null) { 185 | return null; 186 | } 187 | VirtualFile virtualFile = psiFile.getVirtualFile(); 188 | if (virtualFile == null || !virtualFile.isInLocalFileSystem()) { 189 | return null; 190 | } 191 | if (psiFile.getViewProvider() instanceof MultiplePsiFilesPerDocumentFileViewProvider) { 192 | return null; 193 | } 194 | Project project = psiFile.getProject(); 195 | ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); 196 | if (!component.isSettingsValid() || !component.isEnabled() || !isJavaScriptFile(psiFile, component.ext)) { 197 | return null; 198 | } 199 | Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile); 200 | if (document == null) { 201 | return null; 202 | } 203 | String fileContent = document.getText(); 204 | if (StringUtil.isEmptyOrSpaces(fileContent)) { 205 | return null; 206 | } 207 | EditorColorsScheme colorsScheme = editor == null ? null : editor.getColorsScheme(); 208 | // tabSize = getTabSize(editor); 209 | // tabSize = 4; 210 | return new ExternalLintAnnotationInput(project, psiFile, fileContent, colorsScheme); 211 | } 212 | 213 | private static boolean isInList(String file, String ext) { 214 | if (StringUtils.isEmpty(ext)) { 215 | return false; 216 | } 217 | String[] exts = ext.split(","); 218 | for (String ex : exts) { 219 | if (file.endsWith(ex)) { 220 | return true; 221 | } 222 | } 223 | return false; 224 | } 225 | 226 | private static boolean isJavaScriptFile(PsiFile file, String ext) { 227 | return file instanceof JSFile && file.getFileType().equals(JavaScriptFileType.INSTANCE) || (!StringUtils.isEmpty(ext) && isInList(file.getName(), ext)); 228 | } 229 | 230 | @Nullable 231 | @Override 232 | public ExternalLintAnnotationResult doAnnotate(ExternalLintAnnotationInput collectedInfo) { 233 | try { 234 | LOG.info("Running ESLint inspection"); 235 | PsiFile file = collectedInfo.psiFile; 236 | Project project = file.getProject(); 237 | ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); 238 | if (!component.isSettingsValid() || !component.isEnabled() || !isJavaScriptFile(file, component.ext)) { 239 | return null; 240 | } 241 | ESLintConfigFileListener.start(collectedInfo.project); 242 | String relativeFile; 243 | ActualFile actualCodeFile = ActualFile.getOrCreateActualFile(ESLINT_TEMP_FILE_KEY, file.getVirtualFile(), collectedInfo.fileContent); 244 | if (actualCodeFile == null || actualCodeFile.getFile() == null) { 245 | return null; 246 | } 247 | relativeFile = FileUtils.makeRelative(new File(project.getBasePath()), actualCodeFile.getActualFile()); 248 | Result result = ESLintRunner.lint(project.getBasePath(), relativeFile, component); 249 | 250 | if (component.settings.autoFix) { 251 | // Document document = PsiDocumentManager.getInstance(project).getDocument(file); 252 | // document. 253 | // Document document = PsiDocumentManager.getInstance(project).getDocument(file); 254 | // read lock 255 | // ApplicationManager.getApplication().runWriteAction() 256 | // ApplicationManager.getApplication().runWriteAction() 257 | ApplicationManager.getApplication().invokeLater(new Runnable() { 258 | public void run() { 259 | file.getVirtualFile().refresh(false, false); 260 | } 261 | }, ModalityState.NON_MODAL); 262 | 263 | // WriteAction.run(() -> file.getVirtualFile().refresh(false, false)); 264 | // UIUtil.invokeLaterIfNeeded(new Runnable() { 265 | // public void run() { 266 | // file.getVirtualFile().refresh(false, false); 267 | // } 268 | // }); 269 | } 270 | 271 | actualCodeFile.deleteTemp(); 272 | if (StringUtils.isNotEmpty(result.errorOutput)) { 273 | component.showInfoNotification(result.errorOutput, NotificationType.WARNING); 274 | return null; 275 | } 276 | Document document = PsiDocumentManager.getInstance(project).getDocument(file); 277 | if (document == null) { 278 | component.showInfoNotification("Error running ESLint inspection: Could not get document for file " + file.getName(), NotificationType.WARNING); 279 | LOG.error("Could not get document for file " + file.getName()); 280 | return null; 281 | } 282 | return new ExternalLintAnnotationResult<>(collectedInfo, result); 283 | } catch (Exception e) { 284 | LOG.error("Error running ESLint inspection: ", e); 285 | ESLintProjectComponent.showNotification("Error running ESLint inspection: " + e.getMessage(), NotificationType.ERROR); 286 | } 287 | return null; 288 | } 289 | } -------------------------------------------------------------------------------- /src/com/eslint/ESLintInspection.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | import com.eslint.inspection.PropertySuppressableInspectionBase; 4 | import com.eslint.settings.ESLintSettingsPage; 5 | import com.google.common.base.Joiner; 6 | import com.intellij.codeInspection.*; 7 | import com.intellij.codeInspection.ex.UnfairLocalInspectionTool; 8 | import com.intellij.ide.DataManager; 9 | import com.intellij.ide.actions.ShowSettingsUtilImpl; 10 | import com.intellij.lang.javascript.JSBundle; 11 | import com.intellij.openapi.actionSystem.CommonDataKeys; 12 | import com.intellij.openapi.actionSystem.DataContext; 13 | import com.intellij.openapi.diagnostic.Logger; 14 | import com.intellij.openapi.options.Configurable; 15 | import com.intellij.openapi.options.ex.SingleConfigurableEditor; 16 | //import com.intellij.openapi.options.newEditor.OptionsEditor; 17 | import com.intellij.openapi.project.Project; 18 | import com.intellij.openapi.util.Key; 19 | import com.intellij.psi.PsiElementVisitor; 20 | import com.intellij.psi.PsiFile; 21 | import com.intellij.ui.HyperlinkAdapter; 22 | import com.intellij.ui.HyperlinkLabel; 23 | import com.intellij.ui.IdeBorderFactory; 24 | import com.intellij.util.containers.ContainerUtil; 25 | import org.jetbrains.annotations.NotNull; 26 | 27 | import javax.swing.*; 28 | import javax.swing.event.HyperlinkEvent; 29 | import java.awt.*; 30 | import java.util.List; 31 | 32 | public class ESLintInspection extends PropertySuppressableInspectionBase implements UnfairLocalInspectionTool { //extends PropertySuppressableInspectionBase { 33 | 34 | public static final String INSPECTION_SHORT_NAME = "ESLintInspection"; 35 | public static final Key KEY = Key.create(INSPECTION_SHORT_NAME); 36 | 37 | private static final Logger LOG = Logger.getInstance(ESLintBundle.LOG_ID); 38 | 39 | @NotNull 40 | public String getDisplayName() { 41 | return ESLintBundle.message("eslint.property.inspection.display.name"); 42 | } 43 | 44 | @NotNull 45 | public String getShortName() { 46 | return INSPECTION_SHORT_NAME; 47 | } 48 | 49 | public ProblemDescriptor[] checkFile(@NotNull PsiFile file, @NotNull final InspectionManager manager, final boolean isOnTheFly) { 50 | return ExternalAnnotatorInspectionVisitor.checkFileWithExternalAnnotator(file, manager, isOnTheFly, new ESLintExternalAnnotator()); 51 | } 52 | 53 | @NotNull 54 | @Override 55 | public PsiElementVisitor buildVisitor(@NotNull ProblemsHolder holder, boolean isOnTheFly, @NotNull LocalInspectionToolSession session) { 56 | return new ExternalAnnotatorInspectionVisitor(holder, new ESLintExternalAnnotator(), isOnTheFly); 57 | } 58 | 59 | public JComponent createOptionsPanel() { 60 | JPanel panel = new JPanel(new FlowLayout(FlowLayout.LEFT)); 61 | HyperlinkLabel settingsLink = createHyperLink(); 62 | panel.setBorder(IdeBorderFactory.createTitledBorder(getDisplayName() + " options")); 63 | panel.add(settingsLink); 64 | return panel; 65 | } 66 | 67 | @NotNull 68 | public String getId() { 69 | return "Settings.JavaScript.Linters.ESLint"; 70 | } 71 | 72 | @NotNull 73 | private HyperlinkLabel createHyperLink() { 74 | List path = ContainerUtil.newArrayList(JSBundle.message("settings.javascript.root.configurable.name"), JSBundle.message("settings.javascript.linters.configurable.name"), getDisplayName()); 75 | 76 | String title = Joiner.on(" / ").join(path); 77 | final HyperlinkLabel settingsLink = new HyperlinkLabel(title); 78 | settingsLink.addHyperlinkListener(new HyperlinkAdapter() { 79 | public void hyperlinkActivated(HyperlinkEvent e) { 80 | // DataContext dataContext = DataManager.getInstance().getDataContext(settingsLink); 81 | // OptionsEditor optionsEditor = OptionsEditor.KEY.getData(dataContext); 82 | // if (optionsEditor == null) { 83 | // Project project = CommonDataKeys.PROJECT.getData(dataContext); 84 | // if (project != null) { 85 | // showSettings(project); 86 | // } 87 | // return; 88 | // } 89 | // Configurable configurable = optionsEditor.findConfigurableById(ESLintInspection.this.getId()); 90 | // if (configurable != null) { 91 | // optionsEditor.clearSearchAndSelect(configurable); 92 | // } 93 | } 94 | }); 95 | return settingsLink; 96 | } 97 | 98 | public static void showSettings(Project project) { 99 | ESLintSettingsPage configurable = new ESLintSettingsPage(project); 100 | String dimensionKey = ShowSettingsUtilImpl.createDimensionKey(configurable); 101 | SingleConfigurableEditor singleConfigurableEditor = new SingleConfigurableEditor(project, configurable, dimensionKey, false); 102 | singleConfigurableEditor.show(); 103 | } 104 | 105 | // @Override 106 | // public boolean isSuppressedFor(@NotNull PsiElement element) { 107 | // return false; 108 | // } 109 | 110 | // @NotNull 111 | // @Override 112 | // public SuppressQuickFix[] getBatchSuppressActions(@Nullable PsiElement element) { 113 | // return super.getBatchSuppressActions(element); 114 | // return new SuppressQuickFix[0]; 115 | // } 116 | } -------------------------------------------------------------------------------- /src/com/eslint/ESLintProjectComponent.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | import com.eslint.config.schema.RuleCache; 4 | import com.eslint.settings.Settings; 5 | import com.intellij.notification.Notification; 6 | import com.intellij.notification.NotificationListener; 7 | import com.intellij.notification.NotificationType; 8 | import com.intellij.notification.Notifications; 9 | import com.intellij.openapi.components.ProjectComponent; 10 | import com.intellij.openapi.diagnostic.Logger; 11 | import com.intellij.openapi.project.Project; 12 | import com.wix.utils.FileUtils; 13 | import com.wix.utils.FileUtils.ValidationStatus; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import javax.swing.event.HyperlinkEvent; 17 | 18 | public class ESLintProjectComponent implements ProjectComponent { 19 | public static final String FIX_CONFIG_HREF = "\nFix Configuration"; 20 | protected Project project; 21 | protected Settings settings; 22 | protected boolean settingValidStatus; 23 | protected String settingValidVersion; 24 | protected String settingVersionLastShowNotification; 25 | 26 | private static final Logger LOG = Logger.getInstance(ESLintBundle.LOG_ID); 27 | 28 | public String eslintRcFile; 29 | public String customRulesPath; 30 | public String ext; 31 | public String rulesPath; 32 | public String eslintExecutable; 33 | public String nodeInterpreter; 34 | public boolean treatAsWarnings; 35 | public boolean pluginEnabled; 36 | public boolean autoFix; 37 | public boolean reportUnused; 38 | 39 | public static final String PLUGIN_NAME = "ESLint plugin"; 40 | 41 | public ESLintProjectComponent(Project project) { 42 | this.project = project; 43 | settings = Settings.getInstance(project); 44 | } 45 | 46 | @Override 47 | public void projectOpened() { 48 | if (isEnabled()) { 49 | isSettingsValid(); 50 | } 51 | } 52 | 53 | @Override 54 | public void projectClosed() { 55 | } 56 | 57 | @Override 58 | public void initComponent() { 59 | if (isEnabled()) { 60 | isSettingsValid(); 61 | } 62 | } 63 | 64 | @Override 65 | public void disposeComponent() { 66 | } 67 | 68 | @NotNull 69 | @Override 70 | public String getComponentName() { 71 | return "ESLintProjectComponent"; 72 | } 73 | 74 | public boolean isEnabled() { 75 | return Settings.getInstance(project).pluginEnabled; 76 | } 77 | 78 | public boolean isSettingsValid() { 79 | if (!settings.getVersion().equals(settingValidVersion)) { 80 | validateSettings(); 81 | settingValidVersion = settings.getVersion(); 82 | } 83 | return settingValidStatus; 84 | } 85 | 86 | public boolean validateSettings() { 87 | // do not validate if disabled 88 | if (!settings.pluginEnabled) { 89 | return true; 90 | } 91 | boolean status = validateField("Node Interpreter", settings.nodeInterpreter, true, false, true); 92 | if (!status) { 93 | return false; 94 | } 95 | status = validateField("Rules", settings.rulesPath, false, true, false); 96 | if (!status) { 97 | return false; 98 | } 99 | status = validateField("ESLint bin", settings.eslintExecutable, false, false, true); 100 | if (!status) { 101 | return false; 102 | } 103 | status = validateField("Builtin rules", settings.builtinRulesPath, false, true, false); 104 | if (!status) { 105 | return false; 106 | } 107 | 108 | // if (StringUtil.isNotEmpty(settings.eslintExecutable)) { 109 | // File file = new File(project.getBasePath(), settings.eslintExecutable); 110 | // if (!file.exists()) { 111 | // showErrorConfigNotification(ESLintBundle.message("eslint.rules.dir.does.not.exist", file.toString())); 112 | // LOG.debug("Rules directory not found"); 113 | // settingValidStatus = false; 114 | // return false; 115 | // } 116 | // } 117 | eslintExecutable = settings.eslintExecutable; 118 | eslintRcFile = settings.eslintRcFile; 119 | customRulesPath = settings.rulesPath; 120 | rulesPath = settings.builtinRulesPath; 121 | nodeInterpreter = settings.nodeInterpreter; 122 | treatAsWarnings = settings.treatAllEslintIssuesAsWarnings; 123 | pluginEnabled = settings.pluginEnabled; 124 | ext = settings.ext; 125 | autoFix = settings.autoFix; 126 | reportUnused = settings.reportUnused; 127 | 128 | RuleCache.initializeFromPath(project, this); 129 | 130 | settingValidStatus = true; 131 | return true; 132 | } 133 | 134 | private boolean validateField(String fieldName, String value, boolean shouldBeAbsolute, boolean allowEmpty, boolean isFile) { 135 | ValidationStatus r = FileUtils.validateProjectPath(shouldBeAbsolute ? null : project, value, allowEmpty, isFile); 136 | if (isFile) { 137 | if (r == ValidationStatus.NOT_A_FILE) { 138 | String msg = ESLintBundle.message("eslint.file.is.not.a.file", fieldName, value); 139 | validationFailed(msg); 140 | return false; 141 | } 142 | } else { 143 | if (r == ValidationStatus.NOT_A_DIRECTORY) { 144 | String msg = ESLintBundle.message("eslint.directory.is.not.a.dir", fieldName, value); 145 | validationFailed(msg); 146 | return false; 147 | } 148 | } 149 | if (r == ValidationStatus.DOES_NOT_EXIST) { 150 | String msg = ESLintBundle.message("eslint.file.does.not.exist", fieldName, value); 151 | validationFailed(msg); 152 | return false; 153 | } 154 | return true; 155 | } 156 | 157 | private void validationFailed(String msg) { 158 | NotificationListener notificationListener = new NotificationListener() { 159 | @Override 160 | public void hyperlinkUpdate(@NotNull Notification notification, @NotNull HyperlinkEvent event) { 161 | ESLintInspection.showSettings(project); 162 | } 163 | }; 164 | String errorMessage = msg + FIX_CONFIG_HREF; 165 | showInfoNotification(errorMessage, NotificationType.WARNING, notificationListener); 166 | LOG.debug(msg); 167 | settingValidStatus = false; 168 | } 169 | 170 | protected void showErrorConfigNotification(String content) { 171 | if (!settings.getVersion().equals(settingVersionLastShowNotification)) { 172 | settingVersionLastShowNotification = settings.getVersion(); 173 | showInfoNotification(content, NotificationType.WARNING); 174 | } 175 | } 176 | 177 | public void showInfoNotification(String content, NotificationType type) { 178 | Notification errorNotification = new Notification(PLUGIN_NAME, PLUGIN_NAME, content, type); 179 | Notifications.Bus.notify(errorNotification, this.project); 180 | } 181 | 182 | public void showInfoNotification(String content, NotificationType type, NotificationListener notificationListener) { 183 | Notification errorNotification = new Notification(PLUGIN_NAME, PLUGIN_NAME, content, type, notificationListener); 184 | Notifications.Bus.notify(errorNotification, this.project); 185 | } 186 | 187 | public static void showNotification(String content, NotificationType type) { 188 | Notification errorNotification = new Notification(PLUGIN_NAME, PLUGIN_NAME, content, type); 189 | Notifications.Bus.notify(errorNotification); 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /src/com/eslint/actions/ESLintFixAction.java: -------------------------------------------------------------------------------- 1 | package com.eslint.actions; 2 | 3 | import com.eslint.ESLintProjectComponent; 4 | import com.eslint.utils.ESLintRunner; 5 | import com.intellij.execution.ExecutionException; 6 | import com.intellij.openapi.actionSystem.AnAction; 7 | import com.intellij.openapi.actionSystem.AnActionEvent; 8 | import com.intellij.openapi.actionSystem.DataConstants; 9 | import com.intellij.openapi.project.DumbAware; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.openapi.vfs.VirtualFile; 12 | 13 | public class ESLintFixAction extends AnAction implements DumbAware { 14 | 15 | public void actionPerformed(AnActionEvent e) { 16 | final Project project = e.getProject(); 17 | if (project == null) return; 18 | final VirtualFile file = (VirtualFile) e.getDataContext().getData(DataConstants.VIRTUAL_FILE); 19 | 20 | // TODO handle multiple selection 21 | if (file == null) { 22 | // File[] rtFiles = RTFile.DATA_KEY.getData(e.getDataContext()); 23 | // if (rtFiles == null || rtFiles.length == 0) { 24 | // System.out.println("No file for rt compile"); 25 | // return; 26 | // } 27 | // // handle all files 28 | // for (RTFile rtFile : rtFiles) { 29 | // RTFileListener.compile(rtFile.getRtFile().getVirtualFile(), project); 30 | // } 31 | } else { 32 | ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); 33 | if (!component.isSettingsValid() || !component.isEnabled()) { 34 | return; 35 | } 36 | // Result result = ESLintRunner.lint(project.getBasePath(), relativeFile, component.nodeInterpreter, component.eslintExecutable, component.eslintRcFile, component.customRulesPath); 37 | 38 | if (project.getBasePath() != null) { 39 | ESLintRunner.ESLintSettings settings = ESLintRunner.buildSettings(project.getBasePath(), file.getPath(), component); 40 | try { 41 | ESLintRunner.fix(settings); 42 | file.refresh(false, false); 43 | } catch (ExecutionException e1) { 44 | e1.printStackTrace(); 45 | } 46 | } 47 | } 48 | } 49 | 50 | //TODO implement update, disable when not relevant? 51 | // add project view popup 52 | //fix menu location 53 | } 54 | -------------------------------------------------------------------------------- /src/com/eslint/config/ESLintConfigFileListener.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config; 2 | 3 | import com.eslint.ESLintProjectComponent; 4 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; 5 | import com.intellij.openapi.application.ApplicationManager; 6 | import com.intellij.openapi.components.ServiceManager; 7 | import com.intellij.openapi.editor.EditorFactory; 8 | import com.intellij.openapi.editor.event.DocumentAdapter; 9 | import com.intellij.openapi.editor.event.DocumentEvent; 10 | import com.intellij.openapi.editor.event.EditorEventMulticaster; 11 | import com.intellij.openapi.fileEditor.FileDocumentManager; 12 | import com.intellij.openapi.project.Project; 13 | import com.intellij.openapi.vfs.*; 14 | import org.jetbrains.annotations.NotNull; 15 | 16 | import java.util.concurrent.atomic.AtomicBoolean; 17 | 18 | public class ESLintConfigFileListener { 19 | private final Project project; 20 | private final AtomicBoolean LISTENING = new AtomicBoolean(false); 21 | 22 | public ESLintConfigFileListener(@NotNull Project project) { 23 | this.project = project; 24 | } 25 | 26 | private void startListener() { 27 | if (LISTENING.compareAndSet(false, true)) 28 | ApplicationManager.getApplication().invokeLater(new Runnable() { 29 | public void run() { 30 | ApplicationManager.getApplication().runWriteAction(new Runnable() { 31 | public void run() { 32 | VirtualFileManager.getInstance().addVirtualFileListener(new ESLintConfigFileVfsListener(), ESLintConfigFileListener.this.project); 33 | EditorEventMulticaster multicaster = EditorFactory.getInstance().getEventMulticaster(); 34 | multicaster.addDocumentListener(new ESLintConfigFileDocumentListener(), ESLintConfigFileListener.this.project); 35 | } 36 | }); 37 | } 38 | }); 39 | } 40 | 41 | public static void start(@NotNull Project project) { 42 | ESLintConfigFileListener listener = ServiceManager.getService(project, ESLintConfigFileListener.class); 43 | listener.startListener(); 44 | } 45 | 46 | private void fileChanged(@NotNull VirtualFile file) { 47 | if (ESLintConfigFileUtil.isESLintConfigFile(file) && !project.isDisposed()) { 48 | restartAnalyzer(); 49 | } 50 | } 51 | 52 | private void restartAnalyzer() { 53 | ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); 54 | if (component.isEnabled()) { 55 | DaemonCodeAnalyzer.getInstance(project).restart(); 56 | } 57 | } 58 | 59 | /** 60 | * VFS Listener 61 | */ 62 | private class ESLintConfigFileVfsListener extends VirtualFileAdapter { 63 | private ESLintConfigFileVfsListener() { 64 | } 65 | 66 | public void fileCreated(@NotNull VirtualFileEvent event) { 67 | ESLintConfigFileListener.this.fileChanged(event.getFile()); 68 | } 69 | 70 | public void fileDeleted(@NotNull VirtualFileEvent event) { 71 | ESLintConfigFileListener.this.fileChanged(event.getFile()); 72 | } 73 | 74 | public void fileMoved(@NotNull VirtualFileMoveEvent event) { 75 | ESLintConfigFileListener.this.fileChanged(event.getFile()); 76 | } 77 | 78 | public void fileCopied(@NotNull VirtualFileCopyEvent event) { 79 | ESLintConfigFileListener.this.fileChanged(event.getFile()); 80 | ESLintConfigFileListener.this.fileChanged(event.getOriginalFile()); 81 | } 82 | } 83 | 84 | /** 85 | * Document Listener 86 | */ 87 | private class ESLintConfigFileDocumentListener extends DocumentAdapter { 88 | private ESLintConfigFileDocumentListener() { 89 | } 90 | 91 | public void documentChanged(DocumentEvent event) { 92 | VirtualFile file = FileDocumentManager.getInstance().getFile(event.getDocument()); 93 | if (file != null) { 94 | ESLintConfigFileListener.this.fileChanged(file); 95 | } 96 | } 97 | } 98 | } 99 | 100 | -------------------------------------------------------------------------------- /src/com/eslint/config/ESLintConfigFileType.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config; 2 | 3 | import com.intellij.json.JsonLanguage; 4 | //import com.intellij.lang.javascript.json.JSONLanguageDialect; 5 | import com.intellij.openapi.fileTypes.LanguageFileType; 6 | 7 | import javax.swing.Icon; 8 | 9 | import icons.ESLintIcons; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | public class ESLintConfigFileType extends LanguageFileType { 13 | public static final ESLintConfigFileType INSTANCE = new ESLintConfigFileType(); 14 | public static final String ESLINTRC_EXT = "eslintrc"; 15 | public static final String ESLINTRC = '.' + ESLINTRC_EXT; 16 | public static final String[] ESLINTRC_FILES = {ESLINTRC, ESLINTRC + ".js", ESLINTRC + ".yml", ESLINTRC + ".yaml", ESLINTRC + ".json"}; 17 | 18 | private ESLintConfigFileType() { 19 | super(JsonLanguage.INSTANCE); //JSONLanguageDialect.JSON 20 | } 21 | 22 | @NotNull 23 | public String getName() { 24 | return "ESLint"; 25 | } 26 | 27 | @NotNull 28 | public String getDescription() { 29 | return "ESLint configuration file"; 30 | } 31 | 32 | @NotNull 33 | public String getDefaultExtension() { 34 | return ESLINTRC_EXT; 35 | } 36 | 37 | @NotNull 38 | public Icon getIcon() { 39 | return ESLintIcons.ESLint; 40 | } 41 | } -------------------------------------------------------------------------------- /src/com/eslint/config/ESLintConfigFileTypeFactory.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config; 2 | 3 | import com.intellij.openapi.fileTypes.ExactFileNameMatcher; 4 | import com.intellij.openapi.fileTypes.ExtensionFileNameMatcher; 5 | import com.intellij.openapi.fileTypes.FileTypeConsumer; 6 | import com.intellij.openapi.fileTypes.FileTypeFactory; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | public class ESLintConfigFileTypeFactory extends FileTypeFactory { 10 | public void createFileTypes(@NotNull FileTypeConsumer consumer) { 11 | consumer.consume(ESLintConfigFileType.INSTANCE, new ExactFileNameMatcher(ESLintConfigFileType.ESLINTRC)); 12 | // new ExtensionFileNameMatcher(ESLintConfigFileType.ESLINTRC), new ExactFileNameMatcher("eslint.json")); 13 | } 14 | } -------------------------------------------------------------------------------- /src/com/eslint/config/ESLintConfigFileUtil.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.intellij.lang.javascript.JSTokenTypes; 5 | import com.intellij.lang.javascript.psi.JSFile; 6 | import com.intellij.lang.javascript.psi.JSObjectLiteralExpression; 7 | import com.intellij.lang.javascript.psi.JSProperty; 8 | import com.intellij.openapi.vfs.VirtualFile; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.util.PsiTreeUtil; 11 | import com.intellij.util.ObjectUtils; 12 | import com.intellij.util.containers.HashSet; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | import java.util.Collections; 17 | import java.util.Set; 18 | 19 | /** 20 | * @author idok 21 | */ 22 | public final class ESLintConfigFileUtil { 23 | private ESLintConfigFileUtil() { 24 | } 25 | 26 | private static final Set FILES = createSet(); 27 | 28 | public static boolean isESLintConfigFile(JSFile file) { 29 | return file != null && (isESLintConfigFile(file.getVirtualFile()) || file.getFileType().equals(ESLintConfigFileType.INSTANCE)); 30 | } 31 | 32 | private static Set createSet() { 33 | Set s = new HashSet(); 34 | Collections.addAll(s, ESLintConfigFileType.ESLINTRC_FILES); 35 | return s; 36 | } 37 | 38 | public static boolean isRC(String fileName) { 39 | return FILES.contains(fileName); 40 | } 41 | 42 | public static boolean isESLintConfigFile(PsiElement position) { 43 | return isESLintConfigFile(position.getContainingFile().getOriginalFile().getVirtualFile()); 44 | } 45 | 46 | public static boolean isESLintConfigFile(VirtualFile file) { 47 | // return file != null && file.getName().equals(ESLintConfigFileType.ESLINTRC); 48 | return file != null && isRC(file.getName()); 49 | } 50 | 51 | @Nullable 52 | public static JSProperty getProperty(@NotNull PsiElement position) { 53 | JSProperty property = PsiTreeUtil.getParentOfType(position, JSProperty.class, false); 54 | if (property != null) { 55 | JSObjectLiteralExpression objectLiteralExpression = ObjectUtils.tryCast(property.getParent(), JSObjectLiteralExpression.class); 56 | if (objectLiteralExpression != null) { 57 | return property; 58 | } 59 | } 60 | return null; 61 | } 62 | 63 | @Nullable 64 | public static PsiElement getStringLiteral(@NotNull JSProperty property) { 65 | PsiElement firstElement = property.getFirstChild(); 66 | if (firstElement != null && isStringLiteral(firstElement)) { 67 | return firstElement; 68 | } 69 | return null; 70 | } 71 | 72 | public static boolean isStringLiteral(@NotNull PsiElement element) { 73 | if (element instanceof ASTNode) { 74 | ASTNode node = (ASTNode) element; 75 | return node.getElementType().equals(JSTokenTypes.STRING_LITERAL); 76 | } 77 | return false; 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/com/eslint/config/schema/BaseType.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config.schema; 2 | 3 | public class BaseType { 4 | public String title; 5 | public ESLintSchema.PropertyType type; 6 | public String description; 7 | 8 | public BaseType() { 9 | } 10 | 11 | public static final String ANY_NAME = "*"; 12 | 13 | public BaseType(String title, ESLintSchema.PropertyType type, String description) { 14 | this.title = title; 15 | this.type = type; 16 | this.description = description; 17 | } 18 | 19 | public boolean isValidValue(String value) { 20 | return true; 21 | } 22 | 23 | public static boolean isBoolean(String valueStr) { 24 | return Boolean.TRUE.toString().equals(valueStr) || Boolean.FALSE.toString().equals(valueStr); 25 | } 26 | 27 | public static class SchemaBoolean extends BaseType { 28 | public SchemaBoolean() { 29 | type = ESLintSchema.PropertyType.BOOLEAN; 30 | } 31 | 32 | @Override 33 | public boolean isValidValue(String value) { 34 | return isBoolean(value); 35 | } 36 | } 37 | 38 | public static class SchemaAny extends BaseType { 39 | public SchemaAny(String title, String description) { 40 | super(title, ESLintSchema.PropertyType.ANY, description); 41 | } 42 | 43 | public SchemaAny() { 44 | type = ESLintSchema.PropertyType.ANY; 45 | } 46 | } 47 | 48 | public static class SchemaString extends BaseType { 49 | public SchemaString() { 50 | type = ESLintSchema.PropertyType.STRING; 51 | } 52 | } 53 | } -------------------------------------------------------------------------------- /src/com/eslint/config/schema/ESLintSchema.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config.schema; 2 | 3 | import com.google.gson.*; 4 | import com.intellij.util.Function; 5 | import com.intellij.util.containers.ContainerUtil; 6 | 7 | import java.io.IOException; 8 | import java.io.InputStream; 9 | import java.io.InputStreamReader; 10 | import java.util.List; 11 | 12 | public final class ESLintSchema { 13 | public static final String RULES = "rules"; 14 | 15 | // public RootProp properties; 16 | 17 | // public static final SchemaJsonObject instance = load(); 18 | 19 | // public static SchemaJsonObject ROOT = new SchemaJsonObject("root", PropertyType.OBJECT, "", new BaseType[]{ 20 | // new SchemaJsonObject("env", PropertyType.OBJECT, "env", 21 | // new BaseType[]{ 22 | // new BaseType("amd", PropertyType.BOOLEAN, "amd"), 23 | // new BaseType("node", PropertyType.BOOLEAN, "node"), 24 | // new BaseType("browser", PropertyType.BOOLEAN, "browser") 25 | // } 26 | // ), 27 | // new SchemaJsonObject("globals", PropertyType.OBJECT, "globals", new BaseType[]{ 28 | // new BaseType(BaseType.ANY_NAME, PropertyType.BOOLEAN, "") 29 | // }), 30 | // new SchemaJsonObject("rules", PropertyType.OBJECT, "rules", new BaseType[]{}) 31 | // } 32 | // ); 33 | public static SchemaJsonObject ROOT; 34 | 35 | private ESLintSchema() { 36 | } 37 | 38 | public static void buildSchema() { 39 | BaseType rules = ROOT.find(RULES); 40 | if (rules != null) { 41 | List rulesMap = ContainerUtil.map(RuleCache.instance.rulesMap, new Function() { 42 | public BaseType fun(String rule) { 43 | return new BaseType(rule, PropertyType.ANY, rule); 44 | } 45 | }); 46 | if (rules instanceof SchemaJsonObject) { 47 | SchemaJsonObject obj = (SchemaJsonObject) rules; 48 | obj.properties = rulesMap.toArray(new BaseType[rulesMap.size()]); 49 | } 50 | } 51 | } 52 | 53 | // public static Gson getGson() { 54 | // // Gson gson = new GsonBuilder().setPrettyPrinting().create(); 55 | // GsonBuilder builder = new GsonBuilder(); 56 | // builder.registerTypeAdapter(BaseType.class, new BaseTypeAdapter()); 57 | // return builder.setPrettyPrinting().create(); 58 | // } 59 | 60 | public static Gson getGson() { 61 | RuntimeTypeAdapterFactory adapter = RuntimeTypeAdapterFactory.of(BaseType.class, "type") 62 | .registerSubtype(SchemaJsonObject.class, PropertyType.OBJECT.name()) 63 | .registerSubtype(BaseType.SchemaString.class, PropertyType.STRING.name()) 64 | .registerSubtype(BaseType.SchemaAny.class, PropertyType.ANY.name()) 65 | .registerSubtype(BaseType.SchemaBoolean.class, PropertyType.BOOLEAN.name()); 66 | GsonBuilder builder = new GsonBuilder(); 67 | builder.registerTypeAdapterFactory(adapter); 68 | return builder.setPrettyPrinting().create(); 69 | } 70 | 71 | public static SchemaJsonObject load() { 72 | // FileReader reader = null; 73 | InputStreamReader reader = null; 74 | try { 75 | // reader = new FileReader(schema); 76 | InputStream stream = ESLintSchema.class.getResourceAsStream("/com/eslint/config/schema/schema.json"); 77 | // Reader reader = new BufferedReader(new InputStreamReader(stream, "UTF-8")); 78 | reader = new InputStreamReader(stream, "UTF-8"); 79 | Gson gson = getGson(); 80 | SchemaJsonObject ret = gson.fromJson(reader, SchemaJsonObject.class); 81 | ROOT = ret; 82 | // System.out.println(ret.description); 83 | return ret; 84 | } catch (IOException e) { 85 | e.printStackTrace(); 86 | } finally { 87 | if (reader != null) { 88 | try { 89 | reader.close(); 90 | } catch (IOException e) { 91 | e.printStackTrace(); 92 | } 93 | } 94 | } 95 | return null; 96 | } 97 | 98 | // public static class BaseTypeAdapter implements JsonSerializer, JsonDeserializer { 99 | // private static final String CLASSNAME = "CLASSNAME"; 100 | // private static final String INSTANCE = "INSTANCE"; 101 | // private static final String TYPE = "class-type"; 102 | // 103 | // @Override 104 | // public JsonElement serialize(BaseType src, Type typeOfSrc, JsonSerializationContext context) { 105 | // JsonObject retValue = new JsonObject(); 106 | // String className = src.getClass().getCanonicalName(); 107 | // retValue.addProperty(CLASSNAME, className); 108 | // JsonElement elem = context.serialize(src); 109 | // elem.getAsJsonObject().addProperty(TYPE, className); 110 | // retValue.add(INSTANCE, elem); 111 | // return retValue; 112 | // } 113 | // 114 | // @Override 115 | // public BaseType deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 116 | // JsonObject jsonObject = json.getAsJsonObject(); 117 | // JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); 118 | // String className = prim.getAsString(); 119 | // 120 | // Class klass; 121 | // try { 122 | // klass = Class.forName(className); 123 | // } catch (ClassNotFoundException e) { 124 | // e.printStackTrace(); 125 | // throw new JsonParseException(e.getMessage()); 126 | // } 127 | // return context.deserialize(jsonObject.get(INSTANCE), klass); 128 | // } 129 | // } 130 | 131 | // public static class BookTypeAdapter extends TypeAdapter { 132 | // @Override 133 | // public BaseType read(final JsonReader in) throws IOException { 134 | // final BaseType book = new BaseType(); 135 | // in.beginObject(); 136 | // while (in.hasNext()) { 137 | // switch (in.nextName()) { 138 | // case "isbn": 139 | // super.read(in); 140 | // book.setIsbn(in.nextString()); 141 | // break; 142 | // case "title": 143 | // book.setTitle(in.nextString()); 144 | // break; 145 | // case "authors": 146 | // book.setAuthors(in.nextString().split(";")); 147 | // break; 148 | // } 149 | // } 150 | // in.endObject(); 151 | // return book; 152 | // } 153 | // 154 | // @Override 155 | // public void write(final JsonWriter out, final BaseType book) throws IOException { 156 | // out.beginObject(); 157 | // out.name("isbn").value(book.getIsbn()); 158 | // out.name("title").value(book.getTitle()); 159 | // out.name("authors").value(StringUtils.join(book.getAuthors(), ";")); 160 | // out.endObject(); 161 | // } 162 | // } 163 | 164 | // public static class PropertyTypeAdapter implements JsonSerializer, JsonDeserializer { 165 | // private static final String CLASSNAME = "CLASSNAME"; 166 | // private static final String INSTANCE = "INSTANCE"; 167 | // private static final String TYPE = "type"; 168 | // 169 | // @Override 170 | // public JsonElement serialize(PropertyType src, Type typeOfSrc, JsonSerializationContext context) { 171 | //// JsonObject retValue = new JsonObject(); 172 | // String className = src.getClass().getCanonicalName(); 173 | //// retValue.addProperty(CLASSNAME, className); 174 | // JsonElement elem = context.serialize(src); 175 | // elem.getAsJsonObject().addProperty(TYPE, className); 176 | //// retValue.add(INSTANCE, elem); 177 | // return elem; 178 | // } 179 | // 180 | // @Override 181 | // public PropertyType deserialize(PropertyType json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException { 182 | // JsonObject jsonObject = json.getAsJsonObject(); 183 | // JsonPrimitive prim = (JsonPrimitive) jsonObject.get(CLASSNAME); 184 | // String className = prim.getAsString(); 185 | // 186 | // Class klass; 187 | // try { 188 | // klass = Class.forName(className); 189 | // } catch (ClassNotFoundException e) { 190 | // e.printStackTrace(); 191 | // throw new JsonParseException(e.getMessage()); 192 | // } 193 | // return context.deserialize(jsonObject.get(INSTANCE), klass); 194 | // } 195 | // } 196 | 197 | public enum PropertyType {ANY, BOOLEAN, STRING, OBJECT, INT} 198 | } 199 | -------------------------------------------------------------------------------- /src/com/eslint/config/schema/RuleCache.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config.schema; 2 | 3 | import com.eslint.ESLintProjectComponent; 4 | import com.google.common.io.Files; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.openapi.util.text.StringUtil; 7 | import com.intellij.util.Function; 8 | import com.intellij.util.containers.ContainerUtil; 9 | import com.wix.utils.FileUtils; 10 | 11 | import java.io.File; 12 | import java.io.FilenameFilter; 13 | import java.util.ArrayList; 14 | import java.util.Arrays; 15 | import java.util.List; 16 | import java.util.Set; 17 | 18 | // TODO refresh when config change 19 | public final class RuleCache { 20 | 21 | public List rules = new ArrayList(); 22 | 23 | public Set rulesMap = ContainerUtil.newLinkedHashSet(); 24 | 25 | public static RuleCache instance; 26 | 27 | public void read(String path) { 28 | FilenameFilter filter = new FilenameFilter() { 29 | @Override 30 | public boolean accept(File file, String name) { 31 | return name.endsWith(".js"); 32 | } 33 | }; 34 | String[] rules1 = new File(path).list(filter); 35 | rules.addAll(Arrays.asList(rules1)); 36 | 37 | List names = ContainerUtil.map(rules1, new Function() { 38 | public String fun(String file) { 39 | return Files.getNameWithoutExtension(file); 40 | } 41 | }); 42 | rulesMap.addAll(names); 43 | } 44 | 45 | public void readRules() { 46 | SchemaJsonObject schemaRules = ESLintSchema.ROOT.findOfType(ESLintSchema.RULES); 47 | if (schemaRules != null) { 48 | List tempRules = ContainerUtil.map(rulesMap, new Function() { 49 | @Override 50 | public BaseType.SchemaAny fun(String ruleName) { 51 | return new BaseType.SchemaAny(ruleName, ruleName); 52 | } 53 | }); 54 | schemaRules.properties = tempRules.toArray(new BaseType[tempRules.size()]); 55 | } 56 | } 57 | 58 | // private static void initialize(Project project, String builtinRulesPath) { 59 | // instance = new RuleCache(); 60 | // ESLintSchema.load(); 61 | // SchemaJsonObject rules = ESLintSchema.ROOT.findOfType(ESLintSchema.RULES); 62 | // if (rules != null) { 63 | // for (BaseType b : rules.properties) { 64 | // RuleCache.instance.rulesMap.add(b.title); 65 | // } 66 | // } 67 | // String absRulesPath = FileUtils.resolvePath(project, builtinRulesPath); 68 | // if (StringUtil.isNotEmpty(absRulesPath)) { 69 | // instance.read(absRulesPath); 70 | // } 71 | //// instance.read(RuleCache.defaultPath); 72 | // } 73 | 74 | // public static void initializeFromPath(Project project, String builtinRulesPath) { 75 | // instance = new RuleCache(); 76 | // String absRulesPath = FileUtils.resolvePath(project, builtinRulesPath); 77 | // if (StringUtil.isNotEmpty(absRulesPath)) { 78 | // instance.read(absRulesPath); 79 | // } 80 | // instance.read(RuleCache.defaultPath); 81 | // } 82 | 83 | // public static void initializeFromPath(Project project) { 84 | // ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); 85 | // instance = new RuleCache(); 86 | // ESLintSchema.load(); 87 | // String absRulesPath = FileUtils.resolvePath(project, component.rulesPath); 88 | // if (StringUtil.isNotEmpty(absRulesPath)) { 89 | // instance.read(absRulesPath); 90 | // } 91 | // instance.read(component.builtinRulesPath); 92 | // } 93 | 94 | public static void initializeFromPath(Project project, ESLintProjectComponent component) { 95 | // ESLintProjectComponent component = project.getComponent(ESLintProjectComponent.class); 96 | String absRulesPath = FileUtils.resolvePath(project, component.customRulesPath); 97 | initializeFromPaths(component.rulesPath, absRulesPath); 98 | } 99 | 100 | private static void initializeFromPaths(String... paths) { 101 | instance = new RuleCache(); 102 | ESLintSchema.load(); 103 | for (String path : paths) { 104 | if (StringUtil.isNotEmpty(path)) { 105 | instance.read(path); 106 | } 107 | } 108 | instance.readRules(); 109 | } 110 | } 111 | -------------------------------------------------------------------------------- /src/com/eslint/config/schema/RuntimeTypeAdapterFactory.java: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright (C) 2011 Google Inc. 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package com.eslint.config.schema; 18 | 19 | import java.io.IOException; 20 | import java.util.LinkedHashMap; 21 | import java.util.Map; 22 | 23 | import com.google.gson.Gson; 24 | import com.google.gson.JsonElement; 25 | import com.google.gson.JsonObject; 26 | import com.google.gson.JsonParseException; 27 | import com.google.gson.JsonPrimitive; 28 | import com.google.gson.TypeAdapter; 29 | import com.google.gson.TypeAdapterFactory; 30 | import com.google.gson.internal.Streams; 31 | import com.google.gson.reflect.TypeToken; 32 | import com.google.gson.stream.JsonReader; 33 | import com.google.gson.stream.JsonWriter; 34 | 35 | /** 36 | * Adapts values whose runtime type may differ from their declaration type. This 37 | * is necessary when a field's type is not the same type that GSON should create 38 | * when deserializing that field. For example, consider these types: 39 | *
   {@code
 40 |  *   abstract class Shape {
 41 |  *     int x;
 42 |  *     int y;
 43 |  *   }
 44 |  *   class Circle extends Shape {
 45 |  *     int radius;
 46 |  *   }
 47 |  *   class Rectangle extends Shape {
 48 |  *     int width;
 49 |  *     int height;
 50 |  *   }
 51 |  *   class Diamond extends Shape {
 52 |  *     int width;
 53 |  *     int height;
 54 |  *   }
 55 |  *   class Drawing {
 56 |  *     Shape bottomShape;
 57 |  *     Shape topShape;
 58 |  *   }
 59 |  * }
60 | *

Without additional type information, the serialized JSON is ambiguous. Is 61 | * the bottom shape in this drawing a rectangle or a diamond?

   {@code
 62 |  *   {
 63 |  *     "bottomShape": {
 64 |  *       "width": 10,
 65 |  *       "height": 5,
 66 |  *       "x": 0,
 67 |  *       "y": 0
 68 |  *     },
 69 |  *     "topShape": {
 70 |  *       "radius": 2,
 71 |  *       "x": 4,
 72 |  *       "y": 1
 73 |  *     }
 74 |  *   }}
75 | * This class addresses this problem by adding type information to the 76 | * serialized JSON and honoring that type information when the JSON is 77 | * deserialized:
   {@code
 78 |  *   {
 79 |  *     "bottomShape": {
 80 |  *       "type": "Diamond",
 81 |  *       "width": 10,
 82 |  *       "height": 5,
 83 |  *       "x": 0,
 84 |  *       "y": 0
 85 |  *     },
 86 |  *     "topShape": {
 87 |  *       "type": "Circle",
 88 |  *       "radius": 2,
 89 |  *       "x": 4,
 90 |  *       "y": 1
 91 |  *     }
 92 |  *   }}
93 | * Both the type field name ({@code "type"}) and the type labels ({@code 94 | * "Rectangle"}) are configurable. 95 | * 96 | *

Registering Types

97 | * Create a {@code RuntimeTypeAdapter} by passing the base type and type field 98 | * name to the {@link #of} factory method. If you don't supply an explicit type 99 | * field name, {@code "type"} will be used.
   {@code
100 |  *   RuntimeTypeAdapter shapeAdapter
101 |  *       = RuntimeTypeAdapter.of(Shape.class, "type");
102 |  * }
103 | * Next register all of your subtypes. Every subtype must be explicitly 104 | * registered. This protects your application from injection attacks. If you 105 | * don't supply an explicit type label, the type's simple name will be used. 106 | *
   {@code
107 |  *   shapeAdapter.registerSubtype(Rectangle.class, "Rectangle");
108 |  *   shapeAdapter.registerSubtype(Circle.class, "Circle");
109 |  *   shapeAdapter.registerSubtype(Diamond.class, "Diamond");
110 |  * }
111 | * Finally, register the type adapter in your application's GSON builder: 112 | *
   {@code
113 |  *   Gson gson = new GsonBuilder()
114 |  *       .registerTypeAdapter(Shape.class, shapeAdapter)
115 |  *       .create();
116 |  * }
117 | * Like {@code GsonBuilder}, this API supports chaining:
   {@code
118 |  *   RuntimeTypeAdapter shapeAdapter = RuntimeTypeAdapterFactory.of(Shape.class)
119 |  *       .registerSubtype(Rectangle.class)
120 |  *       .registerSubtype(Circle.class)
121 |  *       .registerSubtype(Diamond.class);
122 |  * }
123 | */ 124 | public final class RuntimeTypeAdapterFactory implements TypeAdapterFactory { 125 | private final Class baseType; 126 | private final String typeFieldName; 127 | private final Map> labelToSubtype = new LinkedHashMap>(); 128 | private final Map, String> subtypeToLabel = new LinkedHashMap, String>(); 129 | 130 | private RuntimeTypeAdapterFactory(Class baseType, String typeFieldName) { 131 | if (typeFieldName == null || baseType == null) { 132 | throw new NullPointerException(); 133 | } 134 | this.baseType = baseType; 135 | this.typeFieldName = typeFieldName; 136 | } 137 | 138 | /** 139 | * Creates a new runtime type adapter using for {@code baseType} using {@code 140 | * typeFieldName} as the type field name. Type field names are case sensitive. 141 | */ 142 | public static RuntimeTypeAdapterFactory of(Class baseType, String typeFieldName) { 143 | return new RuntimeTypeAdapterFactory(baseType, typeFieldName); 144 | } 145 | 146 | /** 147 | * Creates a new runtime type adapter for {@code baseType} using {@code "type"} as 148 | * the type field name. 149 | */ 150 | public static RuntimeTypeAdapterFactory of(Class baseType) { 151 | return new RuntimeTypeAdapterFactory(baseType, "type"); 152 | } 153 | 154 | /** 155 | * Registers {@code type} identified by {@code label}. Labels are case 156 | * sensitive. 157 | * 158 | * @throws IllegalArgumentException if either {@code type} or {@code label} 159 | * have already been registered on this type adapter. 160 | */ 161 | public RuntimeTypeAdapterFactory registerSubtype(Class type, String label) { 162 | if (type == null || label == null) { 163 | throw new NullPointerException(); 164 | } 165 | if (subtypeToLabel.containsKey(type) || labelToSubtype.containsKey(label)) { 166 | throw new IllegalArgumentException("types and labels must be unique"); 167 | } 168 | labelToSubtype.put(label, type); 169 | subtypeToLabel.put(type, label); 170 | return this; 171 | } 172 | 173 | /** 174 | * Registers {@code type} identified by its {@link Class#getSimpleName simple 175 | * name}. Labels are case sensitive. 176 | * 177 | * @throws IllegalArgumentException if either {@code type} or its simple name 178 | * have already been registered on this type adapter. 179 | */ 180 | public RuntimeTypeAdapterFactory registerSubtype(Class type) { 181 | return registerSubtype(type, type.getSimpleName()); 182 | } 183 | 184 | public TypeAdapter create(Gson gson, TypeToken type) { 185 | if (type.getRawType() != baseType) { 186 | return null; 187 | } 188 | 189 | final Map> labelToDelegate 190 | = new LinkedHashMap>(); 191 | final Map, TypeAdapter> subtypeToDelegate 192 | = new LinkedHashMap, TypeAdapter>(); 193 | for (Map.Entry> entry : labelToSubtype.entrySet()) { 194 | TypeAdapter delegate = gson.getDelegateAdapter(this, TypeToken.get(entry.getValue())); 195 | labelToDelegate.put(entry.getKey(), delegate); 196 | subtypeToDelegate.put(entry.getValue(), delegate); 197 | } 198 | 199 | return new TypeAdapter() { 200 | @Override public R read(JsonReader in) throws IOException { 201 | JsonElement jsonElement = Streams.parse(in); 202 | JsonElement labelJsonElement = jsonElement.getAsJsonObject().remove(typeFieldName); 203 | if (labelJsonElement == null) { 204 | throw new JsonParseException("cannot deserialize " + baseType 205 | + " because it does not define a field named " + typeFieldName); 206 | } 207 | String label = labelJsonElement.getAsString(); 208 | @SuppressWarnings("unchecked") // registration requires that subtype extends T 209 | TypeAdapter delegate = (TypeAdapter) labelToDelegate.get(label); 210 | if (delegate == null) { 211 | throw new JsonParseException("cannot deserialize " + baseType + " subtype named " 212 | + label + "; did you forget to register a subtype?"); 213 | } 214 | return delegate.fromJsonTree(jsonElement); 215 | } 216 | 217 | @Override public void write(JsonWriter out, R value) throws IOException { 218 | Class srcType = value.getClass(); 219 | String label = subtypeToLabel.get(srcType); 220 | @SuppressWarnings("unchecked") // registration requires that subtype extends T 221 | TypeAdapter delegate = (TypeAdapter) subtypeToDelegate.get(srcType); 222 | if (delegate == null) { 223 | throw new JsonParseException("cannot serialize " + srcType.getName() 224 | + "; did you forget to register a subtype?"); 225 | } 226 | JsonObject jsonObject = delegate.toJsonTree(value).getAsJsonObject(); 227 | if (jsonObject.has(typeFieldName)) { 228 | throw new JsonParseException("cannot serialize " + srcType.getName() 229 | + " because it already defines a field named " + typeFieldName); 230 | } 231 | JsonObject clone = new JsonObject(); 232 | clone.add(typeFieldName, new JsonPrimitive(label)); 233 | for (Map.Entry e : jsonObject.entrySet()) { 234 | clone.add(e.getKey(), e.getValue()); 235 | } 236 | Streams.write(clone, out); 237 | } 238 | }; 239 | } 240 | } 241 | -------------------------------------------------------------------------------- /src/com/eslint/config/schema/SchemaJsonObject.java: -------------------------------------------------------------------------------- 1 | package com.eslint.config.schema; 2 | 3 | public class SchemaJsonObject extends BaseType { 4 | public BaseType[] properties; 5 | 6 | public SchemaJsonObject() { 7 | type = ESLintSchema.PropertyType.OBJECT; 8 | } 9 | 10 | public SchemaJsonObject(String title, ESLintSchema.PropertyType type, String description, BaseType[] properties) { 11 | super(title, type, description); 12 | this.properties = properties; 13 | } 14 | 15 | public BaseType find(String name) { 16 | for (BaseType b : properties) { 17 | if (b.title.equals(name)) { 18 | return b; 19 | } 20 | } 21 | return null; 22 | } 23 | 24 | public T findOfType(String name) { 25 | for (BaseType b : properties) { 26 | if (b.title.equals(name)) { 27 | // if (b instanceof T) { 28 | return (T) b; 29 | // } 30 | } 31 | } 32 | return null; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/BaseActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; 4 | import com.intellij.codeInsight.intention.HighPriorityAction; 5 | import com.intellij.codeInsight.intention.IntentionAction; 6 | import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; 7 | import com.intellij.openapi.editor.Editor; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiFile; 11 | import com.intellij.util.IncorrectOperationException; 12 | import org.jetbrains.annotations.NotNull; 13 | import org.jetbrains.annotations.Nullable; 14 | 15 | /** 16 | * @author idok 17 | */ 18 | public abstract class BaseActionFix extends LocalQuickFixAndIntentionActionOnPsiElement implements IntentionAction, HighPriorityAction { 19 | public BaseActionFix(PsiElement element) { 20 | super(element); 21 | } 22 | 23 | @NotNull 24 | @Override 25 | public String getFamilyName() { 26 | return getText(); 27 | } 28 | 29 | // @Override 30 | // public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { 31 | // return true; 32 | // } 33 | 34 | // protected abstract void fix(@NotNull Project project, Editor editor, PsiFile file, PsiElement start); 35 | 36 | // @Override 37 | // public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { 38 | // fix(project, editor, file); 39 | // DaemonCodeAnalyzer.getInstance(project).restart(file); 40 | // } 41 | 42 | // public void invoke(@NotNull Project project, @NotNull PsiFile file, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement start, @NotNull PsiElement end) { 43 | // invoke(project, editor, file, start); 44 | // } 45 | 46 | @Override 47 | public boolean startInWriteAction() { 48 | return true; 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/DotNotationActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.codeInspection.LocalQuickFixAndIntentionActionOnPsiElement; 5 | import com.intellij.lang.ASTNode; 6 | import com.intellij.lang.javascript.psi.*; 7 | import com.intellij.lang.javascript.psi.impl.JSChangeUtil; 8 | import com.intellij.openapi.editor.Editor; 9 | import com.intellij.openapi.project.Project; 10 | import com.intellij.openapi.util.text.StringUtil; 11 | import com.intellij.psi.PsiElement; 12 | import com.intellij.psi.PsiFile; 13 | import com.intellij.psi.util.PsiTreeUtil; 14 | import com.intellij.util.IncorrectOperationException; 15 | import org.jetbrains.annotations.Nls; 16 | import org.jetbrains.annotations.NotNull; 17 | import org.jetbrains.annotations.Nullable; 18 | 19 | /** 20 | * @author idok 21 | */ 22 | public class DotNotationActionFix extends BaseActionFix { 23 | public DotNotationActionFix(PsiElement element) { 24 | super(element); 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public String getText() { 30 | return ESLintBundle.message("inspection.fix.dot-notation"); 31 | } 32 | 33 | @Override 34 | public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement element, @NotNull PsiElement end) throws IncorrectOperationException { 35 | JSIndexedPropertyAccessExpression indexed = PsiTreeUtil.getParentOfType(element, JSIndexedPropertyAccessExpression.class); 36 | JSReferenceExpression ref = PsiTreeUtil.findChildOfType(indexed, JSReferenceExpression.class); 37 | JSLiteralExpression literalExpression = (JSLiteralExpression) indexed.getIndexExpression(); 38 | String path = StringUtil.stripQuotesAroundValue(literalExpression.getText()); 39 | ASTNode dotExp = JSChangeUtil.createStatementFromText(project, ref.getText() + '.' + path); 40 | indexed.replace(dotExp.getPsi()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/EqeqeqActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.eslint.utils.JSBinaryExpressionUtil; 5 | import com.intellij.lang.ASTNode; 6 | import com.intellij.openapi.editor.Document; 7 | import com.intellij.openapi.editor.Editor; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.psi.PsiDocumentManager; 10 | import com.intellij.psi.PsiElement; 11 | import com.intellij.psi.PsiFile; 12 | import com.intellij.util.IncorrectOperationException; 13 | import org.jetbrains.annotations.NotNull; 14 | import org.jetbrains.annotations.Nullable; 15 | 16 | /** 17 | * @author idok 18 | */ 19 | public class EqeqeqActionFix extends BaseActionFix { 20 | 21 | public EqeqeqActionFix(PsiElement element) { 22 | super(element); 23 | } 24 | 25 | @NotNull 26 | @Override 27 | public String getText() { 28 | return ESLintBundle.message("inspection.fix.eqeqeq"); 29 | } 30 | 31 | @Override 32 | public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement element, @NotNull PsiElement end) throws IncorrectOperationException { 33 | ASTNode op = JSBinaryExpressionUtil.getOperator(element); 34 | Document document = PsiDocumentManager.getInstance(project).getDocument(element.getContainingFile()); 35 | 36 | String replace = ""; 37 | if (op.getText().equals("==")) { 38 | replace = "==="; 39 | } else if (op.getText().equals("!=")) { 40 | replace = "!=="; 41 | } 42 | document.replaceString(op.getStartOffset(), op.getStartOffset() + op.getTextLength(), replace); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/Fixes.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.google.common.base.Strings; 4 | import com.intellij.psi.PsiElement; 5 | 6 | public final class Fixes { 7 | private Fixes() { 8 | } 9 | 10 | public static BaseActionFix getFixForRule(String rule, PsiElement element) { 11 | // Map map = new HashMap(); 12 | // map.put("strict", ) 13 | if (Strings.isNullOrEmpty(rule)) { 14 | return null; 15 | } 16 | if (rule.equals("strict")) { 17 | return new StrictActionFix(element); 18 | } 19 | if (rule.equals("no-new-object")) { 20 | return new NoNewObjectActionFix(element); 21 | } 22 | if (rule.equals("no-array-constructor")) { 23 | return new NoArrayConstructorActionFix(element); 24 | } 25 | if (rule.equals("eqeqeq")) { 26 | return new EqeqeqActionFix(element); 27 | } 28 | if (rule.equals("no-negated-in-lhs")) { 29 | return new NoNegatedInLhsActionFix(element); 30 | } 31 | if (rule.equals("no-lonely-if")) { 32 | return new NoLonelyIfActionFix(element); 33 | } 34 | if (rule.equals("dot-notation")) { 35 | return new DotNotationActionFix(element); 36 | } 37 | return null; 38 | } 39 | } -------------------------------------------------------------------------------- /src/com/eslint/fixes/NoArrayConstructorActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.lang.javascript.psi.JSElementFactory; 5 | import com.intellij.lang.javascript.psi.JSExpressionCodeFragment; 6 | import com.intellij.lang.javascript.psi.JSNewExpression; 7 | import com.intellij.openapi.editor.Editor; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiFile; 11 | import com.intellij.util.IncorrectOperationException; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | /** 15 | * @author idok 16 | */ 17 | public class NoArrayConstructorActionFix extends NoNewBaseActionFix { 18 | public NoArrayConstructorActionFix(PsiElement element) { 19 | super(element); 20 | } 21 | 22 | @Override 23 | protected String getNewExp() { 24 | return "[]"; 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public String getText() { 30 | return ESLintBundle.message("inspection.fix.no-array-constructor"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/NoLonelyIfActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.lang.javascript.psi.JSIfStatement; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.PsiFile; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | import com.intellij.util.IncorrectOperationException; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | /** 15 | * @author idok 16 | */ 17 | public class NoLonelyIfActionFix extends BaseActionFix { 18 | public NoLonelyIfActionFix(PsiElement element) { 19 | super(element); 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public String getText() { 25 | return ESLintBundle.message("inspection.fix.no-lonely-if"); 26 | } 27 | 28 | @Override 29 | public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement element, @NotNull PsiElement end) throws IncorrectOperationException { 30 | JSIfStatement ifStatement = PsiTreeUtil.getParentOfType(element, JSIfStatement.class); 31 | JSIfStatement parentIf = PsiTreeUtil.getParentOfType(ifStatement, JSIfStatement.class); 32 | parentIf.getElse().replace(ifStatement); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/NoNegatedInLhsActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.lang.ASTNode; 5 | import com.intellij.lang.javascript.psi.JSBinaryExpression; 6 | import com.intellij.lang.javascript.psi.JSParenthesizedExpression; 7 | import com.intellij.lang.javascript.psi.impl.JSChangeUtil; 8 | import com.intellij.openapi.editor.Editor; 9 | import com.intellij.openapi.project.Project; 10 | import com.intellij.psi.PsiElement; 11 | import com.intellij.psi.PsiFile; 12 | import com.intellij.psi.util.PsiTreeUtil; 13 | import com.intellij.util.IncorrectOperationException; 14 | import org.jetbrains.annotations.NotNull; 15 | import org.jetbrains.annotations.Nullable; 16 | 17 | /** 18 | * @author idok 19 | */ 20 | public class NoNegatedInLhsActionFix extends BaseActionFix { 21 | public NoNegatedInLhsActionFix(PsiElement element) { 22 | super(element); 23 | } 24 | 25 | @NotNull 26 | @Override 27 | public String getText() { 28 | return ESLintBundle.message("inspection.fix.no-negated-in-lhs"); 29 | } 30 | 31 | @Override 32 | public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement element, @NotNull PsiElement end) throws IncorrectOperationException { 33 | // PsiElement element = descriptor.getPsiElement(); 34 | JSBinaryExpression binary = PsiTreeUtil.getParentOfType(element, JSBinaryExpression.class); 35 | JSBinaryExpression binaryClone = (JSBinaryExpression) binary.copy(); 36 | binaryClone.getLOperand().replace(binary.getLOperand().getLastChild()); 37 | ASTNode negate = JSChangeUtil.createStatementFromText(project, "!(true)"); 38 | JSParenthesizedExpression paren = PsiTreeUtil.getChildOfType(negate.getPsi().getFirstChild(), JSParenthesizedExpression.class); 39 | paren.getInnerExpression().replace(binaryClone); 40 | binary.replace(negate.getPsi()); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/NoNewBaseActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.intellij.lang.javascript.psi.JSElementFactory; 4 | import com.intellij.lang.javascript.psi.JSExpressionCodeFragment; 5 | import com.intellij.lang.javascript.psi.JSNewExpression; 6 | import com.intellij.openapi.editor.Editor; 7 | import com.intellij.openapi.project.Project; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.util.IncorrectOperationException; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | /** 15 | * @author idok 16 | */ 17 | public abstract class NoNewBaseActionFix extends BaseActionFix { 18 | protected NoNewBaseActionFix(PsiElement element) { 19 | super(element); 20 | } 21 | 22 | protected abstract String getNewExp(); 23 | 24 | @Override 25 | public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement element, @NotNull PsiElement end) throws IncorrectOperationException { 26 | PsiElement parent = element.getParent(); 27 | if (!(parent instanceof JSNewExpression)) return; 28 | final JSExpressionCodeFragment useStrict = JSElementFactory.createExpressionCodeFragment(project, getNewExp(), parent); 29 | PsiElement child = useStrict.getFirstChild(); 30 | parent.replace(child); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/NoNewObjectActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.lang.javascript.psi.JSElementFactory; 5 | import com.intellij.lang.javascript.psi.JSExpressionCodeFragment; 6 | import com.intellij.lang.javascript.psi.JSNewExpression; 7 | import com.intellij.openapi.editor.Editor; 8 | import com.intellij.openapi.project.Project; 9 | import com.intellij.psi.PsiElement; 10 | import com.intellij.psi.PsiFile; 11 | import com.intellij.util.IncorrectOperationException; 12 | import org.jetbrains.annotations.NotNull; 13 | 14 | /** 15 | * @author idok 16 | */ 17 | public class NoNewObjectActionFix extends NoNewBaseActionFix { 18 | public NoNewObjectActionFix(PsiElement element) { 19 | super(element); 20 | } 21 | 22 | @Override 23 | protected String getNewExp() { 24 | return "{}"; 25 | } 26 | 27 | @NotNull 28 | @Override 29 | public String getText() { 30 | return ESLintBundle.message("inspection.fix.no.new.object"); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/StrictActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.lang.javascript.psi.*; 5 | import com.intellij.openapi.editor.Editor; 6 | import com.intellij.openapi.project.Project; 7 | import com.intellij.openapi.util.TextRange; 8 | import com.intellij.psi.PsiElement; 9 | import com.intellij.psi.PsiFile; 10 | import com.intellij.util.IncorrectOperationException; 11 | import org.jetbrains.annotations.NotNull; 12 | import org.jetbrains.annotations.Nullable; 13 | 14 | /** 15 | * @author idok 16 | */ 17 | public class StrictActionFix extends BaseActionFix { 18 | public StrictActionFix(PsiElement element) { 19 | super(element); 20 | } 21 | 22 | @NotNull 23 | @Override 24 | public String getText() { 25 | return ESLintBundle.message("inspection.fix.strict"); 26 | } 27 | 28 | @Override 29 | public void invoke(@NotNull Project project, @NotNull PsiFile psiFile, @Nullable("is null when called from inspection") Editor editor, @NotNull PsiElement element, @NotNull PsiElement end) throws IncorrectOperationException { 30 | final PsiElement parent = element.getParent(); 31 | if (!(parent instanceof JSFunctionExpression || parent instanceof JSFunction)) return; 32 | 33 | // if (parent.getChildren().length < 2) { 34 | // return; 35 | // } 36 | 37 | JSBlockStatement block = null; 38 | for (PsiElement elem : parent.getChildren()) { 39 | if (elem instanceof JSBlockStatement) { 40 | block = (JSBlockStatement) elem; 41 | break; 42 | } 43 | } 44 | 45 | if (block != null) { 46 | TextRange textRange = block.getTextRange(); 47 | 48 | // PsiTreeUtil. JSPsiImplUtils 49 | final JSExpressionCodeFragment useStrict = JSElementFactory.createExpressionCodeFragment(project, "'use strict';\n", block); 50 | PsiElement child = useStrict.getFirstChild(); 51 | if (block.getStatements().length == 0) { 52 | block.add(child); 53 | } else { 54 | block.addBefore(child, block.getStatements()[0]); 55 | } 56 | // final JSBlockStatement finalBlock = block; 57 | // ApplicationManager.getApplication().runWriteAction(new Runnable() { 58 | // @Override 59 | // public void run() { 60 | // finalBlock.add(useStrict); 61 | // } 62 | // }); 63 | 64 | // if (textRange != null) { 65 | // Document document = PsiDocumentManager.getInstance(project).getDocument(element.getContainingFile()); 66 | //// TextRange docRange = textRange.shiftRight(element.getTextRange().getStartOffset()); 67 | // document.insertString(textRange.getStartOffset() + 1, "\n'use strict';"); 68 | // } 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/SuppressActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.intellij.codeInsight.FileModificationService; 4 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; 5 | import com.intellij.codeInsight.intention.IntentionAction; 6 | import com.intellij.lang.javascript.psi.JSElement; 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.openapi.editor.Document; 9 | import com.intellij.openapi.editor.Editor; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.psi.PsiDocumentManager; 12 | import com.intellij.psi.PsiElement; 13 | import com.intellij.psi.PsiFile; 14 | import com.intellij.psi.util.PsiTreeUtil; 15 | import com.intellij.util.IncorrectOperationException; 16 | import org.jetbrains.annotations.NonNls; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | /** 20 | * @author idok 21 | */ 22 | public class SuppressActionFix implements IntentionAction { 23 | private static final Logger LOG = Logger.getInstance(SuppressActionFix.class); 24 | private final PsiElement element; 25 | private final String rule; 26 | 27 | 28 | public SuppressActionFix(String rule, PsiElement element) { 29 | this.element = element; 30 | this.rule = rule; 31 | } 32 | 33 | @NotNull 34 | @Override 35 | public String getText() { 36 | return "Suppress ESLint rule"; 37 | } 38 | 39 | @NotNull 40 | @Override 41 | public String getFamilyName() { 42 | return "ESLint"; 43 | } 44 | 45 | @Override 46 | public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { 47 | return true; 48 | } 49 | 50 | @Override 51 | public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { 52 | // final PsiFile file = element.getContainingFile(); 53 | if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; 54 | 55 | // InspectionManager inspectionManager = InspectionManager.getInstance(project); 56 | // ProblemDescriptor descriptor = inspectionManager.createProblemDescriptor(element, element, "", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false); 57 | 58 | final JSElement property = PsiTreeUtil.getParentOfType(element, JSElement.class); 59 | LOG.assertTrue(property != null); 60 | final int start = property.getTextRange().getStartOffset(); 61 | 62 | @NonNls final Document doc = PsiDocumentManager.getInstance(project).getDocument(file); 63 | LOG.assertTrue(doc != null); 64 | final int line = doc.getLineNumber(start); 65 | final int lineStart = doc.getLineStartOffset(line); 66 | 67 | doc.insertString(lineStart, "/*eslint " + rule + ":0*/\n"); 68 | DaemonCodeAnalyzer.getInstance(project).restart(file); 69 | } 70 | 71 | @Override 72 | public boolean startInWriteAction() { 73 | return true; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/com/eslint/fixes/SuppressLineActionFix.java: -------------------------------------------------------------------------------- 1 | package com.eslint.fixes; 2 | 3 | import com.intellij.codeInsight.FileModificationService; 4 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; 5 | import com.intellij.codeInsight.intention.IntentionAction; 6 | import com.intellij.lang.javascript.psi.JSElement; 7 | import com.intellij.openapi.diagnostic.Logger; 8 | import com.intellij.openapi.editor.Document; 9 | import com.intellij.openapi.editor.Editor; 10 | import com.intellij.openapi.project.Project; 11 | import com.intellij.psi.PsiDocumentManager; 12 | import com.intellij.psi.PsiElement; 13 | import com.intellij.psi.PsiFile; 14 | import com.intellij.psi.util.PsiTreeUtil; 15 | import com.intellij.util.IncorrectOperationException; 16 | import org.jetbrains.annotations.NonNls; 17 | import org.jetbrains.annotations.NotNull; 18 | 19 | /** 20 | * @author idok 21 | */ 22 | public class SuppressLineActionFix implements IntentionAction { 23 | private static final Logger LOG = Logger.getInstance(SuppressLineActionFix.class); 24 | private final PsiElement element; 25 | private final String rule; 26 | 27 | 28 | public SuppressLineActionFix(String rule, PsiElement element) { 29 | this.element = element; 30 | this.rule = rule; 31 | } 32 | 33 | @NotNull 34 | @Override 35 | public String getText() { 36 | //noinspection DialogTitleCapitalization 37 | return "Suppress ESLint rule line"; 38 | } 39 | 40 | @NotNull 41 | @Override 42 | public String getFamilyName() { 43 | return "ESLint"; 44 | } 45 | 46 | @Override 47 | public boolean isAvailable(@NotNull Project project, Editor editor, PsiFile file) { 48 | return true; 49 | } 50 | 51 | @Override 52 | public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws IncorrectOperationException { 53 | // final PsiFile file = element.getContainingFile(); 54 | if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; 55 | 56 | // InspectionManager inspectionManager = InspectionManager.getInstance(project); 57 | // ProblemDescriptor descriptor = inspectionManager.createProblemDescriptor(element, element, "", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false); 58 | 59 | final JSElement property = PsiTreeUtil.getParentOfType(element, JSElement.class); 60 | LOG.assertTrue(property != null); 61 | final int start = property.getTextRange().getStartOffset(); 62 | 63 | @NonNls final Document doc = PsiDocumentManager.getInstance(project).getDocument(file); 64 | LOG.assertTrue(doc != null); 65 | final int line = doc.getLineNumber(start); 66 | final int lineEnd = doc.getLineEndOffset(line); 67 | doc.insertString(lineEnd, " //eslint-disable-line " + rule); 68 | DaemonCodeAnalyzer.getInstance(project).restart(file); 69 | } 70 | 71 | @Override 72 | public boolean startInWriteAction() { 73 | return true; 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/com/eslint/inspection/PropertySuppressableInspectionBase.java: -------------------------------------------------------------------------------- 1 | package com.eslint.inspection; 2 | 3 | import com.eslint.ESLintBundle; 4 | import com.intellij.codeInsight.FileModificationService; 5 | import com.intellij.codeInsight.daemon.HighlightDisplayKey; 6 | import com.intellij.codeInsight.daemon.impl.actions.SuppressByCommentFix; 7 | import com.intellij.codeInspection.CustomSuppressableInspectionTool; 8 | import com.intellij.codeInspection.LocalInspectionTool; 9 | import com.intellij.codeInspection.SuppressIntentionAction; 10 | import com.intellij.codeInspection.SuppressQuickFix; 11 | import com.intellij.lang.javascript.inspections.JSInspectionSuppressor; 12 | import com.intellij.lang.javascript.linter.jshint.JSHintInspection; 13 | import com.intellij.lang.javascript.psi.JSElement; 14 | import com.intellij.lang.javascript.psi.JSFile; 15 | import com.intellij.lang.javascript.psi.impl.JSFileImpl; 16 | import com.intellij.openapi.command.CommandProcessor; 17 | import com.intellij.openapi.diagnostic.Logger; 18 | import com.intellij.openapi.editor.Document; 19 | import com.intellij.openapi.editor.Editor; 20 | import com.intellij.openapi.project.Project; 21 | import com.intellij.psi.PsiDocumentManager; 22 | import com.intellij.psi.PsiElement; 23 | import com.intellij.psi.PsiFile; 24 | import com.intellij.psi.PsiNamedElement; 25 | import com.intellij.psi.util.PsiTreeUtil; 26 | import com.intellij.util.IncorrectOperationException; 27 | import org.jetbrains.annotations.NonNls; 28 | import org.jetbrains.annotations.NotNull; 29 | import org.jetbrains.annotations.Nullable; 30 | 31 | public abstract class PropertySuppressableInspectionBase extends LocalInspectionTool { //implements CustomSuppressableInspectionTool { 32 | private static final Logger LOG = Logger.getInstance("#com.intellij.lang.properties.PropertySuppressableInspectionBase"); 33 | 34 | @NotNull 35 | public String getGroupDisplayName() { 36 | return ESLintBundle.message("eslint.inspection.group.name"); 37 | } 38 | 39 | public SuppressIntentionAction[] getSuppressActions(final PsiElement element) { 40 | PsiNamedElement pe = getProblemElement(element); 41 | return new SuppressIntentionAction[]{new SuppressForStatement(getShortName()), new SuppressForFile(getShortName())}; 42 | } 43 | 44 | @NotNull 45 | public SuppressQuickFix[] getBatchSuppressActions(@Nullable PsiElement element) { 46 | return new SuppressQuickFix[]{new ESLintSuppressByCommentFix(HighlightDisplayKey.find(this.getShortName()), JSInspectionSuppressor.getHolderClass(element))}; 47 | } 48 | 49 | public static class ESLintSuppressByCommentFix extends SuppressByCommentFix { 50 | public ESLintSuppressByCommentFix(HighlightDisplayKey key, Class suppressionHolderClass) { 51 | super(key, suppressionHolderClass); 52 | } 53 | 54 | @NotNull 55 | public String getText() { 56 | return "Suppress for line"; 57 | } 58 | 59 | protected void createSuppression(@NotNull Project project, @NotNull PsiElement element, @NotNull PsiElement container) throws IncorrectOperationException { 60 | if (element.isValid()) { 61 | PsiFile psiFile = element.getContainingFile(); 62 | if (psiFile != null) { 63 | psiFile = psiFile.getOriginalFile(); 64 | } 65 | 66 | if (psiFile != null && psiFile.isValid()) { 67 | final Document document = PsiDocumentManager.getInstance(project).getDocument(psiFile); 68 | if (document != null) { 69 | int lineNo = document.getLineNumber(element.getTextOffset()); 70 | final int lineEndOffset = document.getLineEndOffset(lineNo); 71 | CommandProcessor.getInstance().executeCommand(project, new Runnable() { 72 | public void run() { 73 | document.insertString(lineEndOffset, " //eslint-disable-line"); 74 | } 75 | }, null, null); 76 | } 77 | } 78 | } 79 | } 80 | } 81 | 82 | 83 | public boolean isSuppressedFor(@NotNull PsiElement element) { 84 | // Property property = PsiTreeUtil.getParentOfType(element, Property.class, false); 85 | // JSFileImpl file; 86 | // if (property == null) { 87 | // PsiFile containingFile = element.getContainingFile(); 88 | // if (containingFile instanceof JSFileImpl) { 89 | // file = (JSFileImpl) containingFile; 90 | // } else { 91 | // return false; 92 | // } 93 | // } else { 94 | // PsiElement prev = property.getPrevSibling(); 95 | // while (prev instanceof PsiWhiteSpace || prev instanceof PsiComment) { 96 | // if (prev instanceof PsiComment) { 97 | // @NonNls String text = prev.getText(); 98 | // if (text.contains("suppress") && text.contains('"' + getShortName() + '"')) return true; 99 | // } 100 | // prev = prev.getPrevSibling(); 101 | // } 102 | // file = property.getPropertiesFile(); 103 | // } 104 | // PsiElement leaf = file.getContainingFile().findElementAt(0); 105 | // while (leaf instanceof PsiWhiteSpace) leaf = leaf.getNextSibling(); 106 | // 107 | // while (leaf instanceof PsiComment) { 108 | // @NonNls String text = leaf.getText(); 109 | // if (text.contains("suppress") && text.contains('"' + getShortName() + '"') && text.contains("file")) { 110 | // return true; 111 | // } 112 | // leaf = leaf.getNextSibling(); 113 | // if (leaf instanceof PsiWhiteSpace) leaf = leaf.getNextSibling(); 114 | // // comment before first property get bound to the file, not property 115 | // if (leaf instanceof PropertiesList && leaf.getFirstChild() == property && text.contains("suppress") && text.contains("\"" + getShortName() + "\"")) { 116 | // return true; 117 | // } 118 | // } 119 | 120 | return false; 121 | } 122 | 123 | private static class SuppressForStatement extends SuppressIntentionAction { 124 | private final String rule; 125 | 126 | public SuppressForStatement(String rule) { 127 | this.rule = rule; 128 | } 129 | 130 | @NotNull 131 | public String getText() { 132 | return ESLintBundle.message("unused.property.suppress.for.statement"); 133 | } 134 | 135 | @NotNull 136 | public String getFamilyName() { 137 | return ESLintBundle.message("unused.property.suppress.for.statement"); 138 | } 139 | 140 | public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) { 141 | final JSElement property = PsiTreeUtil.getParentOfType(element, JSElement.class); 142 | return property != null && property.isValid(); 143 | } 144 | 145 | public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) throws IncorrectOperationException { 146 | final PsiFile file = element.getContainingFile(); 147 | if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; 148 | 149 | // InspectionManager inspectionManager = InspectionManager.getInstance(project); 150 | // ProblemDescriptor descriptor = inspectionManager.createProblemDescriptor(element, element, "", ProblemHighlightType.GENERIC_ERROR_OR_WARNING, false); 151 | 152 | final JSElement property = PsiTreeUtil.getParentOfType(element, JSElement.class); 153 | LOG.assertTrue(property != null); 154 | final int start = property.getTextRange().getStartOffset(); 155 | 156 | @NonNls final Document doc = PsiDocumentManager.getInstance(project).getDocument(file); 157 | LOG.assertTrue(doc != null); 158 | final int line = doc.getLineNumber(start); 159 | final int lineEnd = doc.getLineEndOffset(line); 160 | doc.insertString(lineEnd, " //eslint-disable-line " + rule); 161 | } 162 | } 163 | 164 | private static class SuppressForFile extends SuppressIntentionAction { 165 | private final String rule; 166 | 167 | public SuppressForFile(String rule) { 168 | this.rule = rule; 169 | } 170 | 171 | @NotNull 172 | public String getText() { 173 | return ESLintBundle.message("unused.property.suppress.for.file"); 174 | } 175 | 176 | @NotNull 177 | public String getFamilyName() { 178 | return ESLintBundle.message("unused.property.suppress.for.file"); 179 | } 180 | 181 | public boolean isAvailable(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) { 182 | return element.isValid() && element.getContainingFile() instanceof JSFile; 183 | } 184 | 185 | public void invoke(@NotNull final Project project, final Editor editor, @NotNull final PsiElement element) throws IncorrectOperationException { 186 | final PsiFile file = element.getContainingFile(); 187 | if (!FileModificationService.getInstance().prepareFileForWrite(file)) return; 188 | 189 | @NonNls final Document doc = PsiDocumentManager.getInstance(project).getDocument(file); 190 | LOG.assertTrue(doc != null, file); 191 | 192 | // doc.insertString(0, "// eslint suppress inspection \"" + rule + "\" for whole file\n"); 193 | doc.insertString(0, "/* eslint-disable */\n"); 194 | } 195 | } 196 | 197 | //doc.insertString(lineStart, "/*eslint " + rule + ":0*/\n"); 198 | } -------------------------------------------------------------------------------- /src/com/eslint/settings/ESLintSettingsPage.form: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 |
226 | -------------------------------------------------------------------------------- /src/com/eslint/settings/ESLintSettingsPage.java: -------------------------------------------------------------------------------- 1 | package com.eslint.settings; 2 | 3 | import com.eslint.ESLintProjectComponent; 4 | import com.eslint.utils.ESLintFinder; 5 | import com.eslint.utils.ESLintRunner; 6 | import com.intellij.codeInsight.daemon.DaemonCodeAnalyzer; 7 | import com.intellij.execution.ExecutionException; 8 | import com.intellij.openapi.application.ApplicationManager; 9 | import com.intellij.openapi.fileChooser.FileChooserDescriptorFactory; 10 | import com.intellij.openapi.options.Configurable; 11 | import com.intellij.openapi.project.Project; 12 | import com.intellij.openapi.vfs.VirtualFile; 13 | import com.intellij.psi.PsiManager; 14 | import com.intellij.ui.DocumentAdapter; 15 | import com.intellij.ui.HyperlinkLabel; 16 | import com.intellij.ui.TextFieldWithHistory; 17 | import com.intellij.ui.TextFieldWithHistoryWithBrowseButton; 18 | import com.intellij.util.NotNullProducer; 19 | import com.intellij.util.ui.UIUtil; 20 | import com.intellij.webcore.ui.SwingHelper; 21 | import com.wix.nodejs.NodeDetectionUtil; 22 | import com.wix.settings.ValidationUtils; 23 | import com.wix.settings.Validator; 24 | import com.wix.ui.PackagesNotificationPanel; 25 | import com.wix.utils.FileUtils; 26 | import org.apache.commons.lang.StringUtils; 27 | import org.jetbrains.annotations.Nls; 28 | import org.jetbrains.annotations.NotNull; 29 | import org.jetbrains.annotations.Nullable; 30 | 31 | import javax.swing.*; 32 | import javax.swing.event.DocumentEvent; 33 | import javax.swing.text.JTextComponent; 34 | import java.awt.*; 35 | import java.awt.event.ItemEvent; 36 | import java.awt.event.ItemListener; 37 | import java.io.File; 38 | import java.util.List; 39 | import java.util.Objects; 40 | 41 | //import com.intellij.javascript.nodejs.NodeDetectionUtil; 42 | 43 | //public class ESLintSettingsPage extends SearchableConfigurable.Parent.Abstract implements Configurable.NoScroll { 44 | public class ESLintSettingsPage implements Configurable { 45 | private static final String FIX_IT = "Fix it"; 46 | private static final String HOW_TO_USE_ESLINT = "How to Use ESLint"; 47 | private static final String HOW_TO_USE_LINK = "https://github.com/idok/eslint-plugin"; 48 | protected Project project; 49 | 50 | private JCheckBox pluginEnabledCheckbox; 51 | private JTextField customRulesPathField; 52 | private JPanel panel; 53 | private JPanel errorPanel; 54 | private TextFieldWithHistoryWithBrowseButton eslintBinField2; 55 | private TextFieldWithHistoryWithBrowseButton nodeInterpreterField; 56 | private TextFieldWithHistoryWithBrowseButton eslintrcFile; 57 | private JRadioButton searchForEslintrcInRadioButton; 58 | private JRadioButton useProjectEslintrcRadioButton; 59 | private HyperlinkLabel usageLink; 60 | private JLabel ESLintConfigFilePathLabel; 61 | private JLabel rulesDirectoryLabel; 62 | private JLabel pathToEslintBinLabel; 63 | private JLabel nodeInterpreterLabel; 64 | private JCheckBox treatAllEslintIssuesCheckBox; 65 | private JLabel versionLabel; 66 | private TextFieldWithHistoryWithBrowseButton rulesPathField; 67 | private JLabel rulesDirectoryLabel1; 68 | private JTextField textFieldExt; 69 | private JLabel extensionsLabel; 70 | private JCheckBox autoFixCheckbox; 71 | private JCheckBox reportUnusedCheckbox; 72 | private final PackagesNotificationPanel packagesNotificationPanel; 73 | 74 | public ESLintSettingsPage(@NotNull final Project project) { 75 | this.project = project; 76 | configESLintBinField(); 77 | configESLintRcField(); 78 | configESLintRulesField(); 79 | configNodeField(); 80 | // searchForEslintrcInRadioButton.addItemListener(new ItemListener() { 81 | // public void itemStateChanged(ItemEvent e) { 82 | // eslintrcFile.setEnabled(e.getStateChange() == ItemEvent.DESELECTED); 83 | // System.out.println("searchForEslintrcInRadioButton: " + (e.getStateChange() == ItemEvent.SELECTED ? "checked" : "unchecked")); 84 | // } 85 | // }); 86 | useProjectEslintrcRadioButton.addItemListener(new ItemListener() { 87 | public void itemStateChanged(ItemEvent e) { 88 | eslintrcFile.setEnabled(e.getStateChange() == ItemEvent.SELECTED); 89 | // System.out.println("useProjectEslintrcRadioButton: " + (e.getStateChange() == ItemEvent.SELECTED ? "checked" : "unchecked")); 90 | } 91 | }); 92 | pluginEnabledCheckbox.addItemListener(new ItemListener() { 93 | public void itemStateChanged(ItemEvent e) { 94 | boolean enabled = e.getStateChange() == ItemEvent.SELECTED; 95 | setEnabledState(enabled); 96 | } 97 | }); 98 | 99 | this.packagesNotificationPanel = new PackagesNotificationPanel(project); 100 | // GridConstraints gridConstraints = new GridConstraints(5, 1, 1, 1, GridConstraints.ANCHOR_CENTER, GridConstraints.FILL_BOTH, 101 | // GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, GridConstraints.SIZEPOLICY_CAN_SHRINK | GridConstraints.SIZEPOLICY_WANT_GROW, 102 | // null, new Dimension(250, 150), null); 103 | errorPanel.add(this.packagesNotificationPanel.getComponent(), BorderLayout.CENTER); 104 | 105 | DocumentAdapter docAdp = new DocumentAdapter() { 106 | protected void textChanged(@NotNull DocumentEvent e) { 107 | updateLaterInEDT(); 108 | } 109 | }; 110 | eslintBinField2.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); 111 | eslintrcFile.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); 112 | nodeInterpreterField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); 113 | rulesPathField.getChildComponent().getTextEditor().getDocument().addDocumentListener(docAdp); 114 | customRulesPathField.getDocument().addDocumentListener(docAdp); 115 | textFieldExt.getDocument().addDocumentListener(docAdp); 116 | } 117 | 118 | private File getProjectPath() { 119 | if (project.isDefault()) { 120 | return null; 121 | } 122 | return new File(Objects.requireNonNull(project.getBasePath())); 123 | } 124 | 125 | private void updateLaterInEDT() { 126 | UIUtil.invokeLaterIfNeeded(new Runnable() { 127 | public void run() { 128 | ESLintSettingsPage.this.update(); 129 | } 130 | }); 131 | } 132 | 133 | private void update() { 134 | ApplicationManager.getApplication().assertIsDispatchThread(); 135 | validate(); 136 | } 137 | 138 | private void setEnabledState(boolean enabled) { 139 | eslintrcFile.setEnabled(enabled); 140 | customRulesPathField.setEnabled(enabled); 141 | rulesPathField.setEnabled(enabled); 142 | searchForEslintrcInRadioButton.setEnabled(enabled); 143 | useProjectEslintrcRadioButton.setEnabled(enabled); 144 | eslintBinField2.setEnabled(enabled); 145 | reportUnusedCheckbox.setEnabled(enabled); 146 | autoFixCheckbox.setEnabled(enabled); 147 | nodeInterpreterField.setEnabled(enabled); 148 | ESLintConfigFilePathLabel.setEnabled(enabled); 149 | rulesDirectoryLabel.setEnabled(enabled); 150 | rulesDirectoryLabel1.setEnabled(enabled); 151 | pathToEslintBinLabel.setEnabled(enabled); 152 | nodeInterpreterLabel.setEnabled(enabled); 153 | treatAllEslintIssuesCheckBox.setEnabled(enabled); 154 | textFieldExt.setEnabled(enabled); 155 | extensionsLabel.setEnabled(enabled); 156 | } 157 | 158 | private void validateField(Validator validator, TextFieldWithHistoryWithBrowseButton field, boolean allowEmpty, String message) { 159 | if (!validatePath(field.getChildComponent().getText(), allowEmpty)) { 160 | validator.add(field.getChildComponent().getTextEditor(), message, FIX_IT); 161 | // addError(validator, field.getChildComponent().getTextEditor(), message, FIX_IT); 162 | } 163 | } 164 | 165 | private void validate() { 166 | if (!pluginEnabledCheckbox.isSelected()) { 167 | return; 168 | } 169 | Validator validator = new Validator(); 170 | validateField(validator, eslintBinField2, false, "Path to eslint is invalid {{LINK}}"); 171 | validateField(validator, eslintrcFile, true, "Path to eslintrc is invalid {{LINK}}"); //Please correct path to 172 | validateField(validator, nodeInterpreterField, false, "Path to node interpreter is invalid {{LINK}}"); 173 | if (!validateDirectory(customRulesPathField.getText(), true)) { 174 | addError(validator, customRulesPathField, "Path to custom rules is invalid {{LINK}}", FIX_IT); 175 | } 176 | if (!validateDirectory(rulesPathField.getChildComponent().getText(), true)) { 177 | addError(validator, rulesPathField.getChildComponent().getTextEditor(), "Path to rules is invalid {{LINK}}", FIX_IT); 178 | } 179 | if (!validateExt(textFieldExt.getText())) { 180 | addError(validator, textFieldExt, "Extensions format is invalid, should be e.g. .js,.jsx without white space {{LINK}}", FIX_IT); 181 | } 182 | if (!validator.hasErrors() && !project.isDefault()) { 183 | getVersion(); 184 | } 185 | packagesNotificationPanel.processErrors(validator); 186 | } 187 | 188 | private static void addError(Validator validator, @Nullable JTextComponent textComponent, @NotNull String errorHtmlDescriptionTemplate, @NotNull String linkText) { 189 | validator.add((JTextField) textComponent, errorHtmlDescriptionTemplate, linkText); 190 | // ValidationInfo error = new ValidationInfo(textComponent, errorHtmlDescriptionTemplate, linkText); 191 | // errors.add(error); 192 | } 193 | 194 | private static boolean validateExt(String ext) { 195 | return StringUtils.isEmpty(ext) || ext.matches("^(\\.\\w+,?)+$"); 196 | } 197 | 198 | private ESLintRunner.ESLintSettings settings; 199 | 200 | private void getVersion() { 201 | if (settings != null && 202 | areEqual(nodeInterpreterField, settings.node) && 203 | areEqual(eslintBinField2, settings.eslintExecutablePath) && 204 | settings.cwd.equals(project.getBasePath()) 205 | ) { 206 | return; 207 | } 208 | settings = new ESLintRunner.ESLintSettings(); 209 | settings.node = nodeInterpreterField.getChildComponent().getText(); 210 | settings.eslintExecutablePath = eslintBinField2.getChildComponent().getText(); 211 | settings.cwd = project.getBasePath(); 212 | try { 213 | String version = ESLintRunner.runVersion(settings); 214 | versionLabel.setText(version.trim()); 215 | } catch (ExecutionException e) { 216 | e.printStackTrace(); 217 | } 218 | } 219 | 220 | private boolean validatePath(String path, boolean allowEmpty) { 221 | if (StringUtils.isEmpty(path)) { 222 | return allowEmpty; 223 | } 224 | File filePath = new File(path); 225 | if (filePath.isAbsolute()) { 226 | if (!filePath.exists() || !filePath.isFile()) { 227 | return false; 228 | } 229 | } else { 230 | if (project.isDefault()) { 231 | return false; 232 | } 233 | VirtualFile child = project.getBaseDir().findFileByRelativePath(path); 234 | if (child == null || !child.exists() || child.isDirectory()) { 235 | return false; 236 | } 237 | } 238 | return true; 239 | } 240 | 241 | private boolean validateDirectory(String path, boolean allowEmpty) { 242 | return ValidationUtils.validateDirectory(project, path, allowEmpty); 243 | } 244 | 245 | private static TextFieldWithHistory configWithDefaults(TextFieldWithHistoryWithBrowseButton field) { 246 | TextFieldWithHistory textFieldWithHistory = field.getChildComponent(); 247 | textFieldWithHistory.setHistorySize(-1); 248 | textFieldWithHistory.setMinimumAndPreferredWidth(0); 249 | return textFieldWithHistory; 250 | } 251 | 252 | private void configESLintBinField() { 253 | configWithDefaults(eslintBinField2); 254 | SwingHelper.addHistoryOnExpansion(eslintBinField2.getChildComponent(), new NotNullProducer>() { 255 | @NotNull 256 | public List produce() { 257 | List newFiles = ESLintFinder.searchForESLintBin(getProjectPath()); 258 | return FileUtils.toAbsolutePath(newFiles); 259 | } 260 | }); 261 | SwingHelper.installFileCompletionAndBrowseDialog(project, eslintBinField2, "Select ESLint.js Cli", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); 262 | } 263 | 264 | private void configESLintRulesField() { 265 | TextFieldWithHistory textFieldWithHistory = rulesPathField.getChildComponent(); 266 | SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer>() { 267 | @NotNull 268 | public List produce() { 269 | return ESLintFinder.tryFindRulesAsString(getProjectPath()); 270 | } 271 | }); 272 | SwingHelper.installFileCompletionAndBrowseDialog(project, rulesPathField, "Select Built in Rules", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); 273 | } 274 | 275 | private void configESLintRcField() { 276 | TextFieldWithHistory textFieldWithHistory = configWithDefaults(eslintrcFile); 277 | SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer>() { 278 | @NotNull 279 | public List produce() { 280 | return ESLintFinder.searchForESLintRCFiles(getProjectPath()); 281 | } 282 | }); 283 | SwingHelper.installFileCompletionAndBrowseDialog(project, eslintrcFile, "Select ESLint Config", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); 284 | } 285 | 286 | private void configNodeField() { 287 | TextFieldWithHistory textFieldWithHistory = configWithDefaults(nodeInterpreterField); 288 | SwingHelper.addHistoryOnExpansion(textFieldWithHistory, new NotNullProducer>() { 289 | @NotNull 290 | public List produce() { 291 | List newFiles = NodeDetectionUtil.listAllPossibleNodeInterpreters(); 292 | return FileUtils.toAbsolutePath(newFiles); 293 | } 294 | }); 295 | SwingHelper.installFileCompletionAndBrowseDialog(project, nodeInterpreterField, "Select Node Interpreter", FileChooserDescriptorFactory.createSingleFileNoJarsDescriptor()); 296 | } 297 | 298 | @Nls 299 | @Override 300 | public String getDisplayName() { 301 | return "ESLint"; 302 | } 303 | 304 | @Nullable 305 | @Override 306 | public String getHelpTopic() { 307 | return null; 308 | } 309 | 310 | @Nullable 311 | @Override 312 | public JComponent createComponent() { 313 | loadSettings(); 314 | return panel; 315 | } 316 | 317 | private static boolean areEqual(TextFieldWithHistoryWithBrowseButton field, String value) { 318 | return field.getChildComponent().getText().equals(value); 319 | } 320 | 321 | @Override 322 | public boolean isModified() { 323 | Settings s = getSettings(); 324 | return pluginEnabledCheckbox.isSelected() != s.pluginEnabled || 325 | autoFixCheckbox.isSelected() != s.autoFix || 326 | reportUnusedCheckbox.isSelected() != s.reportUnused || 327 | !areEqual(eslintBinField2, s.eslintExecutable) || 328 | !areEqual(nodeInterpreterField, s.nodeInterpreter) || 329 | treatAllEslintIssuesCheckBox.isSelected() != s.treatAllEslintIssuesAsWarnings || 330 | !customRulesPathField.getText().equals(s.rulesPath) || 331 | !textFieldExt.getText().equals(s.ext) || 332 | !areEqual(rulesPathField, s.builtinRulesPath) || 333 | !getESLintRCFile().equals(s.eslintRcFile); 334 | } 335 | 336 | private String getESLintRCFile() { 337 | return useProjectEslintrcRadioButton.isSelected() ? eslintrcFile.getChildComponent().getText() : ""; 338 | } 339 | 340 | @Override 341 | public void apply() { 342 | saveSettings(); 343 | PsiManager.getInstance(project).dropResolveCaches(); 344 | } 345 | 346 | protected void saveSettings() { 347 | Settings settings = getSettings(); 348 | settings.pluginEnabled = pluginEnabledCheckbox.isSelected(); 349 | settings.autoFix = autoFixCheckbox.isSelected(); 350 | settings.reportUnused = reportUnusedCheckbox.isSelected(); 351 | settings.eslintExecutable = eslintBinField2.getChildComponent().getText(); 352 | settings.nodeInterpreter = nodeInterpreterField.getChildComponent().getText(); 353 | settings.eslintRcFile = getESLintRCFile(); 354 | settings.rulesPath = customRulesPathField.getText(); 355 | settings.builtinRulesPath = rulesPathField.getChildComponent().getText(); 356 | settings.treatAllEslintIssuesAsWarnings = treatAllEslintIssuesCheckBox.isSelected(); 357 | settings.ext = textFieldExt.getText(); 358 | if (!project.isDefault()) { 359 | project.getComponent(ESLintProjectComponent.class).validateSettings(); 360 | DaemonCodeAnalyzer.getInstance(project).restart(); 361 | } 362 | validate(); 363 | } 364 | 365 | protected void loadSettings() { 366 | Settings settings = getSettings(); 367 | setEnabledState(settings.pluginEnabled); 368 | pluginEnabledCheckbox.setSelected(settings.pluginEnabled); 369 | autoFixCheckbox.setSelected(settings.autoFix); 370 | reportUnusedCheckbox.setSelected(settings.reportUnused); 371 | eslintBinField2.getChildComponent().setText(settings.eslintExecutable); 372 | eslintrcFile.getChildComponent().setText(settings.eslintRcFile); 373 | nodeInterpreterField.getChildComponent().setText(settings.nodeInterpreter); 374 | customRulesPathField.setText(settings.rulesPath); 375 | textFieldExt.setText(settings.ext); 376 | rulesPathField.getChildComponent().setText(settings.builtinRulesPath); 377 | useProjectEslintrcRadioButton.setSelected(StringUtils.isNotEmpty(settings.eslintRcFile)); 378 | searchForEslintrcInRadioButton.setSelected(StringUtils.isEmpty(settings.eslintRcFile)); 379 | eslintrcFile.setEnabled(useProjectEslintrcRadioButton.isSelected()); 380 | treatAllEslintIssuesCheckBox.setSelected(settings.treatAllEslintIssuesAsWarnings); 381 | } 382 | 383 | @Override 384 | public void reset() { 385 | loadSettings(); 386 | } 387 | 388 | @Override 389 | public void disposeUIResources() { 390 | } 391 | 392 | // @Override 393 | // protected Configurable[] buildConfigurables() { 394 | // return new Configurable[0]; 395 | // } 396 | 397 | protected Settings getSettings() { 398 | return Settings.getInstance(project); 399 | } 400 | 401 | private void createUIComponents() { 402 | // TODO: place custom component creation code here 403 | usageLink = SwingHelper.createWebHyperlink(HOW_TO_USE_ESLINT, HOW_TO_USE_LINK); 404 | } 405 | 406 | // @NotNull 407 | // @Override 408 | // public String getId() { 409 | // return "com.eslint.elintconfig"; 410 | // } 411 | } 412 | -------------------------------------------------------------------------------- /src/com/eslint/settings/Settings.java: -------------------------------------------------------------------------------- 1 | package com.eslint.settings; 2 | 3 | import com.eslint.utils.ESLintFinder; 4 | import com.intellij.openapi.components.*; 5 | import com.intellij.openapi.project.Project; 6 | import com.intellij.util.xmlb.XmlSerializerUtil; 7 | import org.jetbrains.annotations.NotNull; 8 | import org.jetbrains.annotations.Nullable; 9 | 10 | @State( 11 | name = "ESLintProjectComponent", 12 | storages = {@Storage("eslintPlugin.xml")} 13 | ) 14 | public class Settings implements PersistentStateComponent { 15 | public String eslintRcFile = ESLintFinder.ESLINTRC; 16 | public String rulesPath = ""; 17 | public String builtinRulesPath = ""; 18 | public String eslintExecutable = ""; 19 | public String nodeInterpreter; 20 | public boolean treatAllEslintIssuesAsWarnings; 21 | public boolean pluginEnabled; 22 | public boolean autoFix; 23 | public boolean reportUnused; 24 | public String ext = ""; 25 | 26 | protected Project project; 27 | 28 | public static Settings getInstance(Project project) { 29 | Settings settings = ServiceManager.getService(project, Settings.class); 30 | settings.project = project; 31 | return settings; 32 | } 33 | 34 | @Nullable 35 | @Override 36 | public Settings getState() { 37 | return this; 38 | } 39 | 40 | @Override 41 | public void loadState(@NotNull Settings state) { 42 | XmlSerializerUtil.copyBean(state, this); 43 | } 44 | 45 | public String getVersion() { 46 | return nodeInterpreter + eslintExecutable + eslintRcFile + rulesPath + builtinRulesPath + ext + autoFix + reportUnused; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/com/eslint/utils/CliBuilder.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | import com.intellij.execution.configurations.GeneralCommandLine; 4 | import com.intellij.openapi.util.text.StringUtil; 5 | import com.wix.nodejs.CLI; 6 | import com.wix.nodejs.NodeRunner; 7 | import org.jetbrains.annotations.NotNull; 8 | 9 | final class CliBuilder { 10 | 11 | public static final String V = "-v"; 12 | public static final String RULESDIR = "--rulesdir"; 13 | public static final String EXT = "--ext"; 14 | public static final String C = "-c"; 15 | public static final String FIX = "--fix"; 16 | public static final String FORMAT = "--format"; 17 | public static final String REPORT_UNUSED = "--report-unused-disable-directives"; 18 | public static final String JSON = "json"; 19 | 20 | private CliBuilder() { 21 | } 22 | 23 | @NotNull 24 | static GeneralCommandLine create(@NotNull ESLintRunner.ESLintSettings settings) { 25 | return NodeRunner.createCommandLine(settings.cwd, settings.node, settings.eslintExecutablePath); 26 | } 27 | 28 | @NotNull 29 | static GeneralCommandLine createLint(@NotNull ESLintRunner.ESLintSettings settings) { 30 | GeneralCommandLine commandLine = create(settings); 31 | // TODO validate arguments (file exist etc) 32 | commandLine.addParameter(settings.targetFile); 33 | CLI.addParamIfNotEmpty(commandLine, C, settings.config); 34 | if (StringUtil.isNotEmpty(settings.rules)) { 35 | CLI.addParam(commandLine, RULESDIR, "['" + settings.rules + "']"); 36 | } 37 | if (StringUtil.isNotEmpty(settings.ext)) { 38 | CLI.addParam(commandLine, EXT, settings.ext); 39 | } 40 | if (settings.fix) { 41 | commandLine.addParameter(FIX); 42 | } 43 | if (settings.reportUnused) { 44 | commandLine.addParameter(REPORT_UNUSED); 45 | } 46 | CLI.addParam(commandLine, FORMAT, JSON); 47 | return commandLine; 48 | } 49 | 50 | @NotNull 51 | static GeneralCommandLine createFix(@NotNull ESLintRunner.ESLintSettings settings) { 52 | GeneralCommandLine commandLine = createLint(settings); 53 | commandLine.addParameter(FIX); 54 | return commandLine; 55 | } 56 | 57 | @NotNull 58 | static GeneralCommandLine createVersion(@NotNull ESLintRunner.ESLintSettings settings) { 59 | GeneralCommandLine commandLine = create(settings); 60 | commandLine.addParameter(V); 61 | return commandLine; 62 | } 63 | } -------------------------------------------------------------------------------- /src/com/eslint/utils/ESLintFinder.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | import com.intellij.openapi.util.Condition; 4 | import com.intellij.openapi.util.SystemInfo; 5 | import com.intellij.openapi.util.io.FileUtil; 6 | import com.intellij.util.Function; 7 | import com.intellij.util.containers.ContainerUtil; 8 | import com.wix.nodejs.NodeFinder; 9 | import com.wix.utils.FileUtils; 10 | import org.jetbrains.annotations.NotNull; 11 | 12 | import java.io.File; 13 | import java.io.FilenameFilter; 14 | import java.util.ArrayList; 15 | import java.util.List; 16 | 17 | public final class ESLintFinder { 18 | public static final String ESLINTRC = ".eslintrc"; 19 | public static final String ESLINT_BASE_NAME = NodeFinder.getBinName("eslint"); 20 | 21 | // TODO figure out a way to automatically get this path or add it to config 22 | // should read from /usr/local/lib/node_modules/eslint/lib/rules 23 | // public static String defaultPath = "/usr/local/lib/node_modules/eslint/lib/rules"; 24 | // c:/users/user/appdata/roaming/npm/node_modules 25 | 26 | private ESLintFinder() { 27 | } 28 | 29 | public static List tryFindRules(File projectRoot) { 30 | List options = new ArrayList(); 31 | String relativeRules = NodeFinder.buildPath(NodeFinder.NODE_MODULES, "eslint", "lib", "rules"); 32 | File local = new File(projectRoot, relativeRules); 33 | options.add(local); 34 | if (SystemInfo.isWindows) { 35 | File file = new File("C:\\Program Files (x86)\\nodejs", relativeRules); 36 | options.add(file); 37 | file = new File("C:\\Program Files\\nodejs", relativeRules); 38 | options.add(file); 39 | } else { 40 | File file = new File("/usr/local/lib", relativeRules); 41 | options.add(file); 42 | } 43 | List valid = ContainerUtil.filter(options, new Condition() { 44 | @Override 45 | public boolean value(File file) { 46 | return file.exists() && file.isDirectory(); 47 | } 48 | }); 49 | return valid; 50 | } 51 | 52 | public static List tryFindRulesAsString(final File projectRoot) { 53 | List files = tryFindRules(projectRoot); 54 | return ContainerUtil.map(files, new Function() { 55 | public String fun(File file) { 56 | // return FileUtils.makeRelative(projectRoot, file); 57 | if (projectRoot != null && FileUtil.isAncestor(projectRoot, file, true)) { 58 | return FileUtils.makeRelative(projectRoot, file); 59 | } 60 | return file.toString(); 61 | } 62 | }); 63 | } 64 | 65 | @NotNull 66 | public static List listPossibleESLintExe() { 67 | return NodeFinder.searchNodeModulesBin(ESLINT_BASE_NAME); 68 | } 69 | 70 | @NotNull 71 | public static List searchForESLintBin(File projectRoot) { 72 | return NodeFinder.searchAllScopesForBin(projectRoot, ESLINT_BASE_NAME); 73 | } 74 | 75 | /** 76 | * find possible eslint rc files 77 | * 78 | * @param projectRoot project root 79 | * @return list 80 | */ 81 | public static List searchForESLintRCFiles(final File projectRoot) { 82 | FilenameFilter filter = new FilenameFilter() { 83 | @Override 84 | public boolean accept(File file, String name) { 85 | return name.equals(ESLINTRC) || name.startsWith(ESLINTRC + '.'); 86 | } 87 | }; 88 | // return Arrays.asList(files); 89 | List files = FileUtils.recursiveVisitor(projectRoot, filter); 90 | return ContainerUtil.map(files, new Function() { 91 | public String fun(String curFile) { 92 | return FileUtils.makeRelative(projectRoot, new File(curFile)); 93 | } 94 | }); 95 | } 96 | } -------------------------------------------------------------------------------- /src/com/eslint/utils/ESLintRunner.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | import com.eslint.ESLintProjectComponent; 4 | import com.intellij.execution.ExecutionException; 5 | import com.intellij.execution.configurations.GeneralCommandLine; 6 | import com.intellij.execution.process.ProcessOutput; 7 | import com.intellij.notification.NotificationType; 8 | import com.intellij.openapi.diagnostic.Logger; 9 | import com.wix.nodejs.NodeRunner; 10 | import org.jetbrains.annotations.NotNull; 11 | import org.jetbrains.annotations.Nullable; 12 | 13 | import java.io.File; 14 | import java.util.concurrent.TimeUnit; 15 | 16 | public final class ESLintRunner { 17 | private ESLintRunner() { 18 | } 19 | 20 | private static final Logger LOG = Logger.getInstance(ESLintRunner.class); 21 | 22 | private static final int TIME_OUT = (int) TimeUnit.SECONDS.toMillis(120L); 23 | 24 | public static class ESLintSettings { 25 | public String node; 26 | public String eslintExecutablePath; 27 | public String rules; 28 | public String config; 29 | public String cwd; 30 | public String targetFile; 31 | public String ext; 32 | public boolean fix; 33 | public boolean reportUnused; 34 | } 35 | 36 | public static ESLintSettings buildSettings(@NotNull String cwd, @NotNull String path, @NotNull ESLintProjectComponent component) { 37 | return buildSettings(cwd, path, component.nodeInterpreter, component.eslintExecutable, component.eslintRcFile, component.customRulesPath, component.ext, component.autoFix, component.reportUnused); 38 | } 39 | 40 | private static ESLintSettings buildSettings(@NotNull String cwd, @NotNull String path, @NotNull String nodeInterpreter, @NotNull String eslintBin, @Nullable String eslintrc, 41 | @Nullable String rulesdir, @Nullable String ext, boolean autoFix, boolean reportUnused) { 42 | ESLintRunner.ESLintSettings settings = new ESLintRunner.ESLintSettings(); 43 | settings.cwd = cwd; 44 | settings.eslintExecutablePath = eslintBin; 45 | settings.node = nodeInterpreter; 46 | settings.rules = rulesdir; 47 | settings.config = eslintrc; 48 | settings.targetFile = path; 49 | settings.ext = ext; 50 | settings.fix = autoFix; 51 | settings.reportUnused = reportUnused; 52 | return settings; 53 | } 54 | 55 | @NotNull 56 | public static ProcessOutput lint(@NotNull ESLintSettings settings) throws ExecutionException { 57 | GeneralCommandLine commandLine = CliBuilder.createLint(settings); 58 | return NodeRunner.execute(commandLine, TIME_OUT); 59 | } 60 | 61 | @NotNull 62 | public static Result lint(@NotNull String cwd, @NotNull String path, @NotNull ESLintProjectComponent component) { 63 | ESLintRunner.ESLintSettings settings = ESLintRunner.buildSettings(cwd, path, component); 64 | try { 65 | ProcessOutput output = ESLintRunner.lint(settings); 66 | return Result.processResults(output); 67 | } catch (ExecutionException e) { 68 | LOG.warn("Could not lint file", e); 69 | ESLintProjectComponent.showNotification("Error running ESLint inspection: " + e.getMessage() + "\ncwd: " + cwd + "\ncommand: " + component.eslintExecutable, NotificationType.WARNING); 70 | e.printStackTrace(); 71 | return Result.createError(e.getMessage()); 72 | } 73 | } 74 | 75 | // @NotNull 76 | // public static Result lint(@NotNull String cwd, @NotNull String path, @NotNull String nodeInterpreter, @NotNull String eslintBin, @Nullable String eslintrc, @Nullable String rulesdir, @Nullable String ext, boolean autoFix) { 77 | // ESLintRunner.ESLintSettings settings = ESLintRunner.buildSettings(cwd, path, nodeInterpreter, eslintBin, eslintrc, rulesdir, ext, autoFix); 78 | // try { 79 | // ProcessOutput output = ESLintRunner.lint(settings); 80 | // return Result.processResults(output); 81 | // } catch (ExecutionException e) { 82 | // LOG.warn("Could not lint file", e); 83 | // ESLintProjectComponent.showNotification("Error running ESLint inspection: " + e.getMessage() + "\ncwd: " + cwd + "\ncommand: " + eslintBin, NotificationType.WARNING); 84 | // e.printStackTrace(); 85 | // return Result.createError(e.getMessage()); 86 | // } 87 | // } 88 | 89 | @NotNull 90 | public static ProcessOutput fix(@NotNull ESLintSettings settings) throws ExecutionException { 91 | GeneralCommandLine commandLine = CliBuilder.createFix(settings); 92 | return NodeRunner.execute(commandLine, TIME_OUT); 93 | } 94 | 95 | @NotNull 96 | private static ProcessOutput version(@NotNull ESLintSettings settings) throws ExecutionException { 97 | GeneralCommandLine commandLine = CliBuilder.createVersion(settings); 98 | return NodeRunner.execute(commandLine, TIME_OUT); 99 | } 100 | 101 | @NotNull 102 | public static String runVersion(@NotNull ESLintSettings settings) throws ExecutionException { 103 | if (!new File(settings.eslintExecutablePath).exists()) { 104 | LOG.warn("Calling version with invalid eslint exe " + settings.eslintExecutablePath); 105 | return ""; 106 | } 107 | ProcessOutput out = version(settings); 108 | if (out.getExitCode() == 0) { 109 | return out.getStdout().trim(); 110 | } 111 | return ""; 112 | } 113 | } -------------------------------------------------------------------------------- /src/com/eslint/utils/FileResult.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | import java.util.List; 4 | 5 | public class FileResult { 6 | public String filePath; 7 | public List messages; 8 | public int errorCount; 9 | public int warningCount; 10 | } 11 | -------------------------------------------------------------------------------- /src/com/eslint/utils/JSBinaryExpressionUtil.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | import com.intellij.lang.ASTNode; 4 | import com.intellij.lang.javascript.JSTokenTypes; 5 | import com.intellij.lang.javascript.psi.JSBinaryExpression; 6 | import com.intellij.openapi.util.Condition; 7 | import com.intellij.psi.PsiElement; 8 | import com.intellij.psi.tree.TokenSet; 9 | import com.intellij.psi.util.PsiTreeUtil; 10 | 11 | public final class JSBinaryExpressionUtil { 12 | private static final TokenSet BINARY_OPERATIONS = TokenSet.orSet(JSTokenTypes.OPERATIONS, JSTokenTypes.RELATIONAL_OPERATIONS); 13 | 14 | private JSBinaryExpressionUtil() {} 15 | 16 | public static ASTNode getOperator(PsiElement element) { 17 | PsiElement binary = PsiTreeUtil.findFirstParent(element, new Condition() { 18 | @Override 19 | public boolean value(PsiElement psiElement) { 20 | return psiElement instanceof JSBinaryExpression; 21 | } 22 | }); 23 | return binary == null ? null : binary.getNode().getChildren(BINARY_OPERATIONS)[0]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/com/eslint/utils/Result.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | import com.google.gson.Gson; 4 | import com.google.gson.GsonBuilder; 5 | import com.google.gson.reflect.TypeToken; 6 | import com.intellij.execution.process.ProcessOutput; 7 | 8 | import java.lang.reflect.Type; 9 | import java.util.ArrayList; 10 | import java.util.List; 11 | 12 | /** 13 | * ESLint result 14 | * Created by idok on 8/25/14. 15 | */ 16 | public class Result { 17 | public List warns = new ArrayList(); 18 | public String errorOutput; 19 | 20 | private static List parseInternal(String json) { 21 | GsonBuilder builder = new GsonBuilder(); 22 | // builder.registerTypeAdapterFactory(adapter); 23 | Gson g = builder.setPrettyPrinting().create(); 24 | Type listType = new TypeToken>() {}.getType(); 25 | return g.fromJson(json, listType); 26 | } 27 | 28 | public static Result processResults(ProcessOutput output) { 29 | Result result = new Result(); 30 | result.errorOutput = output.getStderr(); 31 | try { 32 | List fileResults = parseInternal(output.getStdout()); 33 | if (fileResults != null && !fileResults.isEmpty()) { 34 | result.warns = fileResults.get(0).messages; 35 | } 36 | } catch (Exception e) { 37 | result.errorOutput = output.getStdout(); 38 | // result.errorOutput = e.toString(); 39 | } 40 | return result; 41 | } 42 | 43 | public static Result createError(String error) { 44 | Result result = new Result(); 45 | result.errorOutput = error; 46 | return result; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/com/eslint/utils/VerifyMessage.java: -------------------------------------------------------------------------------- 1 | package com.eslint.utils; 2 | 3 | public class VerifyMessage { 4 | public int severity; 5 | public String message; 6 | public String ruleId; 7 | public int line; 8 | public int column; 9 | } -------------------------------------------------------------------------------- /src/icons/ESLintIcons.java: -------------------------------------------------------------------------------- 1 | package icons; 2 | 3 | import com.intellij.openapi.util.IconLoader; 4 | 5 | import javax.swing.Icon; 6 | 7 | public final class ESLintIcons { 8 | private ESLintIcons() { 9 | } 10 | 11 | public static final Icon ESLint = IconLoader.getIcon("/icons/fileTypes/eslint16x16.png", ESLintIcons.class); 12 | } 13 | -------------------------------------------------------------------------------- /src/icons/fileTypes/eslint.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idok/eslint-plugin/08cead4d3a9da827e77513ea8300e6af881bef1b/src/icons/fileTypes/eslint.png -------------------------------------------------------------------------------- /src/icons/fileTypes/eslint16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/idok/eslint-plugin/08cead4d3a9da827e77513ea8300e6af881bef1b/src/icons/fileTypes/eslint16x16.png -------------------------------------------------------------------------------- /testData/.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "no-array-constructor": 2, 4 | "no-catch-shadow": 2, 5 | "no-comma-dangle": 2, 6 | "no-cond-assign": 2, 7 | "no-constant-condition": 2, 8 | "no-control-regex": 2, 9 | "no-div-regex": 0, 10 | "no-else-return": 1, 11 | "no-empty-class": 2, 12 | "no-empty-label": 2, 13 | "no-eq-null": 1, 14 | "no-extend-native": 2, 15 | "no-extra-boolean-cast": 2, 16 | "no-extra-strict": 2, 17 | "no-global-strict": 2, 18 | "no-inner-declarations": [2, "functions"], 19 | "no-iterator": 2, 20 | "no-labels": 2, 21 | "no-lone-blocks": 2, 22 | "no-lonely-if": 1, 23 | "no-loop-func": 2, 24 | "no-mixed-requires": [0, false], 25 | "no-negated-in-lhs": 2, 26 | "no-nested-ternary": 0, 27 | "no-new-require": 0, 28 | "no-octal-escape": 2, 29 | "no-path-concat": 0, 30 | "no-process-exit": 2, 31 | "no-proto": 2, 32 | "no-redeclare": 2, 33 | "no-regex-spaces": 2, 34 | "no-restricted-modules": 0, 35 | "no-script-url": 2, 36 | "no-sequences": 2, 37 | "no-shadow": 2, 38 | "no-shadow-restricted-names": 2, 39 | "no-spaced-func": 2, 40 | "no-space-before-semi": 2, 41 | "no-sparse-arrays": 2, 42 | "no-sync": 0, 43 | "no-undef": 2, 44 | "no-unused-expressions": 2, 45 | "no-unused-vars": [0, {"vars": "local", "args": "after-used"}], 46 | "no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }], 47 | "no-wrap-func": 2, 48 | "yoda": 2, 49 | 50 | "block-scoped-var": 0, 51 | "brace-style": [0, "1tbs"], 52 | "consistent-return": 2, 53 | "consistent-this": [0, "that"], 54 | "curly": [2, "all"], 55 | "default-case": 0, 56 | "func-names": 0, 57 | "func-style": [0, "declaration"], 58 | "max-depth": [0, 4], 59 | "max-len": [0, 80, 4], 60 | "max-nested-callbacks": [0, 2], 61 | "handle-callback-err": 0, 62 | "one-var": 0, 63 | "sort-vars": 0, 64 | "space-after-keywords": [0, "always"], 65 | "space-in-brackets": [0, "never"], 66 | "space-infix-ops": 2, 67 | "space-return-throw-case": 2, 68 | "space-unary-word-ops": 0, 69 | "strict": 2, 70 | "valid-typeof": 2, 71 | "wrap-regex": 0, 72 | 73 | "no-alert": 0, 74 | "no-caller": 2, 75 | "no-bitwise": 0, 76 | "no-console": 0, 77 | "no-underscore-dangle": 0, 78 | "no-debugger": 0, 79 | "no-dupe-keys": 1, 80 | "no-empty": 2, 81 | "no-eval": 1, 82 | "no-ex-assign": 2, 83 | "no-extra-parens": 0, 84 | "no-extra-semi": 1, 85 | "no-floating-decimal": 0, 86 | "no-func-assign": 1, 87 | "no-invalid-regexp": 1, 88 | "no-implied-eval": 2, 89 | "no-with": 2, 90 | "no-fallthrough": 2, 91 | "no-unreachable": 2, 92 | "no-undef-init": 2, 93 | "no-octal": 2, 94 | "no-obj-calls": 2, 95 | "no-new-wrappers": 2, 96 | "no-new": 2, 97 | "no-new-func": 2, 98 | "no-native-reassign": 2, 99 | "no-plusplus": 0, 100 | "no-delete-var": 2, 101 | "no-return-assign": 2, 102 | "no-new-object": 2, 103 | "no-label-var": 2, 104 | "no-ternary": 0, 105 | "no-self-compare": 1, 106 | "no-use-before-define": 0, 107 | "valid-jsdoc": 0, 108 | "eol-last": 0, 109 | 110 | "camelcase": 0, 111 | "dot-notation": 1, 112 | "eqeqeq": 1, 113 | "new-parens": 2, 114 | "guard-for-in": 0, 115 | "radix": 0, 116 | "new-cap": 2, 117 | "quote-props": 0, 118 | "semi": 2, 119 | "use-isnan": 2, 120 | "quotes": [0, "single"], 121 | "max-params": [0, 3], 122 | "max-statements": [0, 10], 123 | "complexity": [0, 11], 124 | "wrap-iife": 1, 125 | "no-multi-str": 2, 126 | 127 | "enforce-package-access": 0, 128 | "module-definition": 0 129 | }, 130 | "env": { 131 | "browser": true, 132 | "node": true, 133 | "amd": true 134 | }, 135 | "globals": { 136 | "_": false, 137 | "$": false, 138 | "requirejs": true, 139 | "xdescribe": false, 140 | "describe": false, 141 | "ddescribe": false, 142 | "iit": false, 143 | "it": false, 144 | "xit": false, 145 | "beforeEach": false, 146 | "afterEach": false, 147 | "jasmine": false, 148 | "expect": false, 149 | "waitsFor": false, 150 | "waits": false, 151 | "runs": false, 152 | "any": false, 153 | "spyOn": false, 154 | "createSpy": false, 155 | "DocumentTouch": false 156 | } 157 | } -------------------------------------------------------------------------------- /testData/inspections/eqeqeq.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | 'use strict'; 3 | var e; 4 | if (e == 3) { 5 | return; 6 | } 7 | if (e != 3) { 8 | return; 9 | } 10 | } -------------------------------------------------------------------------------- /testData/inspections/no-array-constructor.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | 'use strict'; 3 | var e = new Array(); 4 | } -------------------------------------------------------------------------------- /testData/inspections/no-lonely-if.js: -------------------------------------------------------------------------------- 1 | var a; 2 | if (a) { 3 | 4 | } else { 5 | if (a) { 6 | 7 | } 8 | } -------------------------------------------------------------------------------- /testData/inspections/no-negated-in-lhs.js: -------------------------------------------------------------------------------- 1 | var a; 2 | var b; 3 | 4 | if (!a in b) { 5 | a = 3; 6 | } 7 | 8 | var x = !a in b; -------------------------------------------------------------------------------- /testData/inspections/no-new-object.js: -------------------------------------------------------------------------------- 1 | function f() { 2 | 'use strict'; 3 | var e = new Object(); 4 | } -------------------------------------------------------------------------------- /testData/inspections/valid-typeof.js: -------------------------------------------------------------------------------- 1 | var a; 2 | 3 | if (typeof a === 'banana') { 4 | a = 3; 5 | } -------------------------------------------------------------------------------- /tests/com/eslint/ESLintRunnerTest.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | public class ESLintRunnerTest { 4 | public static final String ESLINT_BIN = ""; 5 | public static final String NODE_INTERPRETER = ""; 6 | } 7 | -------------------------------------------------------------------------------- /tests/com/eslint/ESLintTest.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | import com.eslint.settings.Settings; 4 | import com.intellij.openapi.project.Project; 5 | import com.intellij.testFramework.fixtures.*; 6 | 7 | public class ESLintTest extends LightPlatformCodeInsightFixtureTestCase { 8 | @Override 9 | protected String getTestDataPath() { 10 | return TestUtils.getTestDataPath(); 11 | } 12 | 13 | @Override 14 | protected void setUp() throws Exception { 15 | super.setUp(); 16 | } 17 | 18 | @Override 19 | protected boolean isWriteActionRequired() { 20 | return false; 21 | } 22 | 23 | protected void doTest(final String file) { 24 | Project project = myFixture.getProject(); 25 | Settings settings = Settings.getInstance(project); 26 | settings.eslintExecutable = ESLintRunnerTest.ESLINT_BIN; 27 | settings.eslintRcFile = getTestDataPath() + "/.eslintrc"; 28 | settings.nodeInterpreter = ESLintRunnerTest.NODE_INTERPRETER; 29 | settings.rulesPath = ""; 30 | settings.pluginEnabled = true; 31 | myFixture.configureByFile(file); 32 | myFixture.enableInspections(new ESLintInspection()); 33 | myFixture.checkHighlighting(true, false, true); 34 | } 35 | 36 | protected void doTest() { 37 | String name = getTestName(true).replaceAll("_", "-"); 38 | doTest("/inspections/" + name + ".js"); 39 | } 40 | 41 | public void testEqeqeq() { 42 | doTest(); 43 | } 44 | 45 | public void testNo_negated_in_lhs() { 46 | doTest(); 47 | } 48 | 49 | public void testValid_typeof() { 50 | doTest(); 51 | } 52 | 53 | public void testNo_lonely_if() { 54 | doTest(); 55 | } 56 | 57 | public void testNo_new_object() { 58 | doTest(); 59 | } 60 | 61 | public void testNo_array_constructor() { 62 | doTest(); 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /tests/com/eslint/TestUtils.java: -------------------------------------------------------------------------------- 1 | package com.eslint; 2 | 3 | import com.intellij.openapi.diagnostic.Logger; 4 | 5 | import java.io.File; 6 | import java.net.URISyntaxException; 7 | import java.net.URL; 8 | 9 | /** 10 | * @author idok 11 | */ 12 | final class TestUtils { 13 | 14 | private TestUtils() { 15 | } 16 | 17 | private static final Logger LOG = Logger.getInstance(TestUtils.class); 18 | 19 | private static String TEST_DATA_PATH; 20 | 21 | static String getTestDataPath() { 22 | if (TEST_DATA_PATH == null) { 23 | ClassLoader loader = TestUtils.class.getClassLoader(); 24 | URL resource = loader.getResource("testData"); 25 | try { 26 | TEST_DATA_PATH = new File("testData").getAbsolutePath(); 27 | if (resource != null) { 28 | TEST_DATA_PATH = new File(resource.toURI()).getPath().replace(File.separatorChar, '/'); 29 | } 30 | } catch (URISyntaxException e) { 31 | LOG.error(e); 32 | return null; 33 | } 34 | } 35 | return TEST_DATA_PATH; 36 | } 37 | } 38 | --------------------------------------------------------------------------------