Skip to content

Commit c70a074

Browse files
authored
Add Vue Eslint (codesandbox#571)
* Add Vue Eslint * Fix error * Add sharp back * Revert "Add sharp back" This reverts commit 7c388fb. * Revert "Revert "Add sharp back"" This reverts commit e2a1cef. * Reset cache * Empty commit
1 parent f2a01f1 commit c70a074

File tree

14 files changed

+361
-54
lines changed

14 files changed

+361
-54
lines changed

.circleci/config.yml

Lines changed: 18 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -9,30 +9,30 @@ jobs:
99
- checkout
1010
- restore_cache:
1111
keys:
12-
- v3-dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
13-
- v3-dependency-cache-{{ .Branch }}
14-
- v3-dependency-cache
12+
- v4-dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
13+
- v4-dependency-cache-{{ .Branch }}
14+
- v4-dependency-cache
1515
- restore_cache:
1616
keys:
17-
- v3-standalone-dependency-cache-{{ .Branch }}-{{ checksum "standalone-packages/codesandbox-browserfs/yarn.lock" }}
18-
- v3-standalone-dependency-cache-{{ .Branch }}
19-
- v3-standalone-dependency-cache
17+
- v4-standalone-dependency-cache-{{ .Branch }}-{{ checksum "standalone-packages/codesandbox-browserfs/yarn.lock" }}
18+
- v4-standalone-dependency-cache-{{ .Branch }}
19+
- v4-standalone-dependency-cache
2020
- run:
2121
name: Install Dependencies
2222
command: yarn install
2323
- save_cache:
24-
key: v3-dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
24+
key: v4-dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
2525
paths:
2626
- node_modules
2727
- save_cache:
28-
key: v3-standalone-dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
28+
key: v4-standalone-dependency-cache-{{ .Branch }}-{{ checksum "yarn.lock" }}
2929
paths:
3030
- standalone-packages/codesandbox-browserfs/node_modules
3131
- run:
3232
name: Build
3333
command: yarn build:dependents
3434
- save_cache:
35-
key: v3-repo-{{ .Environment.CIRCLE_SHA1 }}
35+
key: v4-repo-{{ .Environment.CIRCLE_SHA1 }}
3636
paths:
3737
- ~/codesandbox-client
3838

@@ -42,19 +42,19 @@ jobs:
4242
working_directory: ~/codesandbox-client
4343
steps:
4444
- restore_cache:
45-
key: v3-repo-{{ .Environment.CIRCLE_SHA1 }}
45+
key: v4-repo-{{ .Environment.CIRCLE_SHA1 }}
4646
# We do this to compare sizes from these builds with master
4747
- restore_cache:
48-
key: v3-prod-app-build-cache-master
48+
key: v4-prod-app-build-cache-master
4949
- run:
5050
name: Build Application
5151
command: yarn build:prod
5252
- save_cache:
53-
key: v3-prod-app-build-cache-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
53+
key: v4-prod-app-build-cache-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
5454
paths:
5555
- ./packages/app/www
5656
- save_cache:
57-
key: v3-prod-build-cache-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
57+
key: v4-prod-build-cache-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
5858
paths:
5959
- ./www
6060
- store_artifacts:
@@ -67,7 +67,7 @@ jobs:
6767
working_directory: ~/codesandbox-client
6868
steps:
6969
- restore_cache:
70-
key: v3-repo-{{ .Environment.CIRCLE_SHA1 }}
70+
key: v4-repo-{{ .Environment.CIRCLE_SHA1 }}
7171
- run:
7272
name: Start Test Server
7373
command: yarn start:test
@@ -91,7 +91,7 @@ jobs:
9191
working_directory: ~/codesandbox-client
9292
steps:
9393
- restore_cache:
94-
key: v3-repo-{{ .Environment.CIRCLE_SHA1 }}
94+
key: v4-repo-{{ .Environment.CIRCLE_SHA1 }}
9595
- run:
9696
name: Test
9797
command: yarn test --ci --testResultsProcessor="jest-junit"
@@ -104,7 +104,7 @@ jobs:
104104
working_directory: ~/codesandbox-client
105105
steps:
106106
- restore_cache:
107-
key: v3-repo-{{ .Environment.CIRCLE_SHA1 }}
107+
key: v4-repo-{{ .Environment.CIRCLE_SHA1 }}
108108
- run:
109109
name: Lint
110110
command: yarn lint
@@ -115,9 +115,9 @@ jobs:
115115
working_directory: ~/codesandbox-client
116116
steps:
117117
- restore_cache:
118-
key: v3-repo-{{ .Environment.CIRCLE_SHA1 }}
118+
key: v4-repo-{{ .Environment.CIRCLE_SHA1 }}
119119
- restore_cache:
120-
key: v3-prod-build-cache-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
120+
key: v4-prod-build-cache-{{ .Environment.CIRCLE_BRANCH }}-{{ .Environment.CIRCLE_SHA1 }}
121121
- add_ssh_keys:
122122
fingerprints:
123123
- "f7:f1:e6:60:96:24:d9:cd:1b:8b:c0:34:e7:ee:fa:82"

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141
],
4242
"devDependencies": {
4343
"all-contributors-cli": "^4.3.0",
44-
"babel-eslint": "^7.2.3",
44+
"babel-eslint": "^8.2.1",
4545
"eslint": "CompuIves/eslint#add/define-parser",
4646
"eslint-config-airbnb": "^15.0.1",
4747
"eslint-config-prettier": "^2.1.1",

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: 25 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import Linter from 'eslint/lib/linter';
22

3-
import monkeypatch from './utils/monkeypatch-babel-eslint';
3+
import monkeypatch from './monkeypatch-babel-eslint';
44

55
/* eslint-disable global-require */
66
const allRules = {
@@ -104,7 +104,6 @@ const restrictedGlobals = [
104104
];
105105

106106
const defaultConfig = {
107-
extends: ['prettier', 'prettier/react', 'prettier/flowtype', 'react-app'],
108107
parserOptions: {
109108
ecmaVersion: 8,
110109
sourceType: 'module',
@@ -310,9 +309,10 @@ monkeypatch({}, defaultConfig.parserOptions);
310309

311310
const linter = new Linter();
312311

313-
linter.defineParser('babel-eslint', {
314-
parse: require('babel-eslint').parseNoPatch, // eslint-disable-line global-require
315-
});
312+
linter.defineParser(
313+
'babel-eslint',
314+
require('babel-eslint') // eslint-disable-line global-require
315+
);
316316

317317
linter.defineRules(allRules);
318318

@@ -343,10 +343,27 @@ function getSeverity(error) {
343343
}
344344

345345
// Respond to message from parent thread
346-
self.addEventListener('message', event => {
347-
const { code, version } = event.data;
346+
self.addEventListener('message', async event => {
347+
const { code, version, title: filename, template } = event.data;
348+
349+
let config = defaultConfig;
350+
let options = { filename };
351+
352+
if (template === 'vue-cli') {
353+
const {
354+
getConfig: getVueConfig,
355+
getVerifyOptions: getVueVerifyOptions,
356+
} = await import('./vue');
357+
358+
config = await getVueConfig(linter);
359+
config.rules = {
360+
...defaultConfig.rules,
361+
...config.rules,
362+
};
363+
options = { ...options, ...getVueVerifyOptions(filename) };
364+
}
348365

349-
const validations = linter.verify(code, defaultConfig);
366+
const validations = linter.verify(code, config, options);
350367

351368
const markers = validations.map(error => {
352369
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+
}

0 commit comments

Comments
 (0)