Skip to content

Commit b2037d4

Browse files
author
Ives van Hoorne
committed
Add Vue Eslint
1 parent abc7f3b commit b2037d4

File tree

9 files changed

+246
-7
lines changed

9 files changed

+246
-7
lines changed

packages/app/config/webpack.common.js

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
const webpack = require('webpack');
22
const path = require('path');
3+
const fs = require('fs');
34
const paths = require('./paths');
45
const HtmlWebpackPlugin = require('html-webpack-plugin');
56
const CaseSensitivePathsPlugin = require('case-sensitive-paths-webpack-plugin');
@@ -17,6 +18,24 @@ const __PROD__ = NODE_ENV === 'production'; // eslint-disable-line no-underscore
1718
const __TEST__ = NODE_ENV === 'test'; // eslint-disable-line no-underscore-dangle
1819
const babelConfig = __DEV__ ? babelDev : babelProd;
1920

21+
// Shim for `eslint-plugin-vue/lib/index.js`
22+
const ESLINT_PLUGIN_VUE_INDEX = `module.exports = {
23+
rules: {${fs
24+
.readdirSync('../../node_modules/eslint-plugin-vue/lib/rules')
25+
.filter(filename => path.extname(filename) === '.js')
26+
.map(filename => {
27+
const ruleId = path.basename(filename, '.js');
28+
return ` "${ruleId}": require("eslint-plugin-vue/lib/rules/${
29+
filename
30+
}"),`;
31+
})
32+
.join('\n')}
33+
},
34+
processors: {
35+
".vue": require("eslint-plugin-vue/lib/processor")
36+
}
37+
}`;
38+
2039
module.exports = {
2140
entry: __TEST__
2241
? {
@@ -67,6 +86,67 @@ module.exports = {
6786
],
6887
loader: 'happypack/loader',
6988
},
89+
90+
// `eslint-plugin-vue/lib/index.js` depends on `fs` module we cannot use in browsers, so needs shimming.
91+
{
92+
test: new RegExp(
93+
`eslint-plugin-vue\\${path.sep}lib\\${path.sep}index\\.js$`
94+
),
95+
loader: 'string-replace-loader',
96+
options: {
97+
search: '[\\s\\S]+', // whole file.
98+
replace: ESLINT_PLUGIN_VUE_INDEX,
99+
flags: 'g',
100+
},
101+
},
102+
// `eslint` has some dynamic `require(...)`.
103+
// Delete those.
104+
{
105+
test: new RegExp(
106+
`eslint\\${path.sep}lib\\${path.sep}(?:linter|rules)\\.js$`
107+
),
108+
loader: 'string-replace-loader',
109+
options: {
110+
search: '(?:\\|\\||(\\())\\s*require\\(.+?\\)',
111+
replace: '$1',
112+
flags: 'g',
113+
},
114+
},
115+
// `vue-eslint-parser` has `require(parserOptions.parser || "espree")`.
116+
// Modify it by a static importing.
117+
{
118+
test: /vue-eslint-parser/,
119+
loader: 'string-replace-loader',
120+
options: {
121+
search: 'require(parserOptions.parser || "espree")',
122+
replace:
123+
'(parserOptions.parser === "babel-eslint" ? require("babel-eslint") : require("espree"))',
124+
},
125+
},
126+
// Patch for `babel-eslint`
127+
{
128+
test: new RegExp(
129+
`babel-eslint\\${path.sep}lib\\${path.sep}index\\.js$`
130+
),
131+
loader: 'string-replace-loader',
132+
options: {
133+
search: '[\\s\\S]+', // whole file.
134+
replace:
135+
'module.exports.parseForESLint = require("./parse-with-scope")',
136+
flags: 'g',
137+
},
138+
},
139+
{
140+
test: new RegExp(
141+
`babel-eslint\\${path.sep}lib\\${path.sep}patch-eslint-scope\\.js$`
142+
),
143+
loader: 'string-replace-loader',
144+
options: {
145+
search: '[\\s\\S]+', // whole file.
146+
replace: 'module.exports = () => {}',
147+
flags: 'g',
148+
},
149+
},
70150
// JSON is not enabled by default in Webpack but both Node and Browserify
71151
// allow it implicitly so we also enable it.
72152
{

packages/app/package.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -123,6 +123,7 @@
123123
"debug": "^2.6.8",
124124
"downshift": "^1.0.0-rc.14",
125125
"eslint-config-react-app": "^1.0.5",
126+
"eslint-plugin-vue": "^4.2.2",
126127
"file-saver": "^1.3.3",
127128
"geniejs": "^0.5.0",
128129
"glamor": "^2.20.25",
@@ -184,10 +185,12 @@
184185
"script-ext-html-webpack-plugin": "^1.8.8",
185186
"shelljs": "^0.7.8",
186187
"store": "^2.0.12",
188+
"string-replace-loader": "^1.3.0",
187189
"styled-components": "^3.1.4",
188190
"svg-react-loader": "^0.4.4",
189191
"tern": "^0.21.0",
190192
"vue": "^2.5.2",
193+
"vue-eslint-parser": "^2.0.3",
191194
"vue-hot-reload-api": "^2.2.4",
192195
"vue-template-compiler": "^2.5.2",
193196
"vue-template-es2015-compiler": "^1.6.0"

packages/app/src/app/components/CodeEditor/Monaco/index.js

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
563563
const currentModule = this.currentModule;
564564

565565
const mode = await this.getMode(currentModule.title);
566-
if (mode === 'javascript') {
566+
if (mode === 'javascript' || mode === 'vue') {
567567
this.monaco.editor.setModelMarkers(
568568
this.editor.getModel(),
569569
'eslint',
@@ -680,12 +680,13 @@ class MonacoEditor extends React.Component<Props, State> implements Editor {
680680

681681
lint = async (code: string, title: string, version: number) => {
682682
const mode = await this.getMode(title);
683-
if (mode === 'javascript') {
683+
if (mode === 'javascript' || mode === 'vue') {
684684
if (this.lintWorker) {
685685
this.lintWorker.postMessage({
686686
code,
687687
title,
688688
version,
689+
template: this.sandbox.template,
689690
});
690691
}
691692
}

packages/app/src/app/components/CodeEditor/Monaco/workers/linter.js renamed to packages/app/src/app/components/CodeEditor/Monaco/workers/linter/index.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
import Linter from 'eslint/lib/linter';
22

3-
import monkeypatch from './utils/monkeypatch-babel-eslint';
3+
import monkeypatch from './monkeypatch-babel-eslint';
4+
import {
5+
getConfig as getVueConfig,
6+
getVerifyOptions as getVueVerifyOptions,
7+
} from './vue';
48

59
/* eslint-disable global-require */
610
const allRules = {
@@ -104,7 +108,6 @@ const restrictedGlobals = [
104108
];
105109

106110
const defaultConfig = {
107-
extends: ['prettier', 'prettier/react', 'prettier/flowtype', 'react-app'],
108111
parserOptions: {
109112
ecmaVersion: 8,
110113
sourceType: 'module',
@@ -343,10 +346,22 @@ function getSeverity(error) {
343346
}
344347

345348
// Respond to message from parent thread
346-
self.addEventListener('message', event => {
347-
const { code, version } = event.data;
349+
self.addEventListener('message', async event => {
350+
const { code, version, title: filename, template } = event.data;
348351

349-
const validations = linter.verify(code, defaultConfig);
352+
let config = defaultConfig;
353+
let options = { filename };
354+
355+
if (template === 'vue-cli') {
356+
config = await getVueConfig(linter);
357+
config.rules = {
358+
...defaultConfig.rules,
359+
...config.rules,
360+
};
361+
options = { ...options, ...getVueVerifyOptions(filename) };
362+
}
363+
364+
const validations = linter.verify(code, config, options);
350365

351366
const markers = validations.map(error => {
352367
const { line: startL, column: startCol } = getPos(error, true);

packages/app/src/app/components/CodeEditor/Monaco/workers/utils/monkeypatch-babel-eslint.js renamed to packages/app/src/app/components/CodeEditor/Monaco/workers/linter/monkeypatch-babel-eslint.js

File renamed without changes.
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
export default {
2+
parser: 'vue-eslint-parser',
3+
parserOptions: Object.freeze({
4+
parser: 'babel-eslint',
5+
ecmaVersion: 2015,
6+
sourceType: 'module',
7+
ecmaFeatures: Object.freeze({
8+
jsx: true,
9+
experimentalObjectRestSpread: true,
10+
}),
11+
}),
12+
env: {
13+
browser: true,
14+
es6: true,
15+
},
16+
plugins: ['vue'],
17+
rules: {
18+
'vue/comment-directive': 'error',
19+
'vue/jsx-uses-vars': 'error',
20+
'vue/no-async-in-computed-properties': 'error',
21+
'vue/no-dupe-keys': 'error',
22+
'vue/no-duplicate-attributes': 'error',
23+
'vue/no-parsing-error': 'error',
24+
'vue/no-reserved-keys': 'error',
25+
'vue/no-shared-component-data': 'error',
26+
'vue/no-side-effects-in-computed-properties': 'error',
27+
'vue/no-template-key': 'error',
28+
'vue/no-textarea-mustache': 'error',
29+
'vue/no-unused-vars': 'error',
30+
'vue/require-component-is': 'error',
31+
'vue/require-render-return': 'error',
32+
'vue/require-v-for-key': 'error',
33+
'vue/require-valid-default-prop': 'error',
34+
'vue/return-in-computed-property': 'error',
35+
'vue/valid-template-root': 'error',
36+
'vue/valid-v-bind': 'error',
37+
'vue/valid-v-cloak': 'error',
38+
'vue/valid-v-else-if': 'error',
39+
'vue/valid-v-else': 'error',
40+
'vue/valid-v-for': 'error',
41+
'vue/valid-v-html': 'error',
42+
'vue/valid-v-if': 'error',
43+
'vue/valid-v-model': 'error',
44+
'vue/valid-v-on': 'error',
45+
'vue/valid-v-once': 'error',
46+
'vue/valid-v-pre': 'error',
47+
'vue/valid-v-show': 'error',
48+
'vue/valid-v-text': 'error',
49+
},
50+
};
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
import plugin from 'eslint-plugin-vue';
2+
import * as parser from 'vue-eslint-parser';
3+
import config from './default-config';
4+
5+
let pluginsInitialized = false;
6+
7+
export async function getConfig(linter) {
8+
if (!pluginsInitialized) {
9+
pluginsInitialized = true;
10+
11+
linter.defineParser('vue-eslint-parser', parser);
12+
Object.keys(plugin.rules).forEach(name => {
13+
linter.defineRule(`vue/${name}`, plugin.rules[name]);
14+
});
15+
}
16+
17+
return config;
18+
}
19+
20+
export function getVerifyOptions(filename: string) {
21+
if (filename.endsWith('.vue')) {
22+
return {
23+
preprocess: plugin.processors['.vue'].preprocess,
24+
postprocess: plugin.processors['.vue'].postprocess,
25+
};
26+
}
27+
28+
return {};
29+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// @flow
2+
import type { ConfigurationFile } from '../types';
3+
4+
const config: ConfigurationFile = {
5+
title: '.eslintrc',
6+
type: 'eslint',
7+
description: 'Configuration for the linter.',
8+
moreInfoUrl: 'https://eslint.org/docs/user-guide/configuring',
9+
10+
getDefaultCode: (template: string) => {
11+
return '{}';
12+
},
13+
14+
schema:
15+
'https://raw.githubusercontent.com/SchemaStore/schemastore/master/src/schemas/json/babelrc.json',
16+
};
17+
18+
export default config;

yarn.lock

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,10 @@ acorn@^5.0.0, acorn@^5.1.1, acorn@^5.2.1:
354354
version "5.2.1"
355355
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.2.1.tgz#317ac7821826c22c702d66189ab8359675f135d7"
356356

357+
acorn@^5.4.0:
358+
version "5.4.1"
359+
resolved "https://registry.yarnpkg.com/acorn/-/acorn-5.4.1.tgz#fdc58d9d17f4a4e98d102ded826a9b9759125102"
360+
357361
358362
version "1.0.2"
359363
resolved "https://registry.yarnpkg.com/add-dom-event-listener/-/add-dom-event-listener-1.0.2.tgz#8faed2c41008721cf111da1d30d995b85be42bed"
@@ -4851,6 +4855,12 @@ eslint-plugin-react@~7.4.0:
48514855
jsx-ast-utils "^2.0.0"
48524856
prop-types "^15.5.10"
48534857

4858+
eslint-plugin-vue@^4.2.2:
4859+
version "4.2.2"
4860+
resolved "https://registry.yarnpkg.com/eslint-plugin-vue/-/eslint-plugin-vue-4.2.2.tgz#63e55c61574af8ef983328ddd2688d6389a0214b"
4861+
dependencies:
4862+
vue-eslint-parser "^2.0.1"
4863+
48544864
eslint-restricted-globals@^0.1.1:
48554865
version "0.1.1"
48564866
resolved "https://registry.yarnpkg.com/eslint-restricted-globals/-/eslint-restricted-globals-0.1.1.tgz#35f0d5cbc64c2e3ed62e93b4b1a7af05ba7ed4d7"
@@ -4862,6 +4872,10 @@ eslint-scope@^3.7.1:
48624872
esrecurse "^4.1.0"
48634873
estraverse "^4.1.1"
48644874

4875+
eslint-visitor-keys@^1.0.0:
4876+
version "1.0.0"
4877+
resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#3f3180fb2e291017716acb4c9d6d5b5c34a6a81d"
4878+
48654879
eslint@CompuIves/eslint#add/define-parser:
48664880
version "4.7.0"
48674881
resolved "https://codeload.github.com/CompuIves/eslint/tar.gz/b893b0dcb55fd6b435993e2bb21ff48771d66f0d"
@@ -4922,6 +4936,13 @@ espree@^3.5.1:
49224936
acorn "^5.2.1"
49234937
acorn-jsx "^3.0.0"
49244938

4939+
espree@^3.5.2:
4940+
version "3.5.3"
4941+
resolved "https://registry.yarnpkg.com/espree/-/espree-3.5.3.tgz#931e0af64e7fbbed26b050a29daad1fc64799fa6"
4942+
dependencies:
4943+
acorn "^5.4.0"
4944+
acorn-jsx "^3.0.0"
4945+
49254946
esprima-fb@~15001.1001.0-dev-harmony-fb:
49264947
version "15001.1001.0-dev-harmony-fb"
49274948
resolved "https://registry.yarnpkg.com/esprima-fb/-/esprima-fb-15001.1001.0-dev-harmony-fb.tgz#43beb57ec26e8cf237d3dd8b33e42533577f2659"
@@ -9459,6 +9480,10 @@ [email protected]:
94599480
version "4.17.2"
94609481
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.2.tgz#34a3055babe04ce42467b607d700072c7ff6bf42"
94619482

9483+
lodash@^4:
9484+
version "4.17.5"
9485+
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.5.tgz#99a92d65c0272debe8c96b6057bc8fbfa3bed511"
9486+
94629487
lodash@~1.0.1:
94639488
version "1.0.2"
94649489
resolved "https://registry.yarnpkg.com/lodash/-/lodash-1.0.2.tgz#8f57560c83b59fc270bd3d561b690043430e2551"
@@ -14246,6 +14271,13 @@ string-length@^2.0.0:
1424614271
astral-regex "^1.0.0"
1424714272
strip-ansi "^4.0.0"
1424814273

14274+
string-replace-loader@^1.3.0:
14275+
version "1.3.0"
14276+
resolved "https://registry.yarnpkg.com/string-replace-loader/-/string-replace-loader-1.3.0.tgz#1d404a7bf5e2ec21b08ffc76d89445fbe49bc01d"
14277+
dependencies:
14278+
loader-utils "^1.1.0"
14279+
lodash "^4"
14280+
1424914281
[email protected], string-similarity@^1.2.0:
1425014282
version "1.2.0"
1425114283
resolved "https://registry.yarnpkg.com/string-similarity/-/string-similarity-1.2.0.tgz#d75153cb383846318b7a39a8d9292bb4db4e9c30"
@@ -15665,6 +15697,17 @@ [email protected]:
1566515697
dependencies:
1566615698
indexof "0.0.1"
1566715699

15700+
vue-eslint-parser@^2.0.1, vue-eslint-parser@^2.0.3:
15701+
version "2.0.3"
15702+
resolved "https://registry.yarnpkg.com/vue-eslint-parser/-/vue-eslint-parser-2.0.3.tgz#c268c96c6d94cfe3d938a5f7593959b0ca3360d1"
15703+
dependencies:
15704+
debug "^3.1.0"
15705+
eslint-scope "^3.7.1"
15706+
eslint-visitor-keys "^1.0.0"
15707+
espree "^3.5.2"
15708+
esquery "^1.0.0"
15709+
lodash "^4.17.4"
15710+
1566815711
vue-hot-reload-api@^2.2.4:
1566915712
version "2.2.4"
1567015713
resolved "https://registry.yarnpkg.com/vue-hot-reload-api/-/vue-hot-reload-api-2.2.4.tgz#683bd1d026c0d3b3c937d5875679e9a87ec6cd8f"

0 commit comments

Comments
 (0)