Skip to content

Commit dd04add

Browse files
committed
feat: support using ESLint to lint TypeScript
1 parent 364fddf commit dd04add

File tree

20 files changed

+255
-49
lines changed

20 files changed

+255
-49
lines changed

__mocks__/inquirer.js

+7-4
Original file line numberDiff line numberDiff line change
@@ -39,12 +39,15 @@ exports.prompt = prompts => {
3939
expect(message).toMatch(a.message)
4040
}
4141

42+
const choices = typeof prompt.choices === 'function'
43+
? prompt.choices(answers)
44+
: prompt.choices
4245
if (a.choices) {
43-
expect(prompt.choices.length).toBe(a.choices.length)
46+
expect(choices.length).toBe(a.choices.length)
4447
a.choices.forEach((c, i) => {
4548
const expected = a.choices[i]
4649
if (expected) {
47-
expect(prompt.choices[i].name).toMatch(expected)
50+
expect(choices[i].name).toMatch(expected)
4851
}
4952
})
5053
}
@@ -56,12 +59,12 @@ exports.prompt = prompts => {
5659

5760
if (a.choose != null) {
5861
expect(prompt.type === 'list' || prompt.type === 'rawList').toBe(true)
59-
setValue(prompt.choices[a.choose].value)
62+
setValue(choices[a.choose].value)
6063
}
6164

6265
if (a.check != null) {
6366
expect(prompt.type).toBe('checkbox')
64-
setValue(a.check.map(i => prompt.choices[i].value))
67+
setValue(a.check.map(i => choices[i].value))
6568
}
6669

6770
if (a.confirm != null) {

packages/@vue/cli-plugin-eslint/__tests__/eslintGenerator.spec.js

+24
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,30 @@ test('prettier', async () => {
6161
expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-prettier')
6262
})
6363

64+
test('typescript', async () => {
65+
const { pkg } = await generateWithPlugin([
66+
{
67+
id: 'eslint',
68+
apply: require('../generator'),
69+
options: {
70+
config: 'prettier'
71+
}
72+
},
73+
{
74+
id: 'typescript',
75+
apply: require('@vue/cli-plugin-typescript/generator'),
76+
options: {}
77+
}
78+
])
79+
80+
expect(pkg.scripts.lint).toBeTruthy()
81+
expect(pkg.eslintConfig).toEqual({
82+
extends: ['plugin:vue/essential', '@vue/prettier', '@vue/typescript']
83+
})
84+
expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-prettier')
85+
expect(pkg.devDependencies).toHaveProperty('@vue/eslint-config-typescript')
86+
})
87+
6488
test('lint on save', async () => {
6589
const { pkg } = await generateWithPlugin({
6690
id: 'eslint',
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,20 @@
1-
module.exports = {
2-
extensions: ['.js', '.vue'],
3-
parserOptions: {
4-
parser: require.resolve('babel-eslint')
5-
},
6-
globals: ['process'],
7-
rules: {
8-
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
9-
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
1+
module.exports = api => {
2+
const options = {
3+
extensions: ['.js', '.vue'],
4+
globals: ['process'],
5+
rules: {
6+
'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
7+
'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
8+
}
109
}
10+
11+
if (api.hasPlugin('typescript')) {
12+
options.extensions.push('.ts')
13+
} else {
14+
options.parserOptions = {
15+
parser: require.resolve('babel-eslint')
16+
}
17+
}
18+
19+
return options
1120
}

packages/@vue/cli-plugin-eslint/generator.js

+9-1
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,14 @@ module.exports = (api, { config, lintOn = [] }) => {
3333
pkg.eslintConfig.extends.push('eslint:recommended')
3434
}
3535

36+
// typescript support
37+
if (api.hasPlugin('typescript')) {
38+
pkg.eslintConfig.extends.push('@vue/typescript')
39+
Object.assign(pkg.devDependencies, {
40+
'@vue/eslint-config-typescript': '^3.0.0-alpha.9'
41+
})
42+
}
43+
3644
if (lintOn.includes('save')) {
3745
pkg.vue = {
3846
lintOnSave: true // eslint-loader configured in runtime plugin
@@ -71,7 +79,7 @@ module.exports = (api, { config, lintOn = [] }) => {
7179
// lint & fix after create to ensure files adhere to chosen config
7280
if (config && config !== 'base') {
7381
api.onCreateComplete(() => {
74-
require('./lint')(api.resolve('.'), { silent: true })
82+
require('./lint')({ silent: true }, api)
7583
})
7684
}
7785
}

packages/@vue/cli-plugin-eslint/index.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = (api, { lintOnSave }) => {
22
if (lintOnSave) {
3-
const options = require('./eslintOptions')
3+
const options = require('./eslintOptions')(api)
44
api.chainWebpack(webpackConfig => {
55
webpackConfig.module
66
.rule('eslint')
@@ -27,6 +27,6 @@ module.exports = (api, { lintOnSave }) => {
2727
},
2828
details: 'For more options, see https://eslint.org/docs/user-guide/command-line-interface#options'
2929
}, args => {
30-
require('./lint')(api.resolve('.'), args)
30+
require('./lint')(args, api)
3131
})
3232
}

packages/@vue/cli-plugin-eslint/lint.js

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
module.exports = function lint (cwd, args = {}) {
1+
module.exports = function lint (args = {}, api) {
2+
const cwd = api.resolve('.')
23
const { CLIEngine } = require('eslint')
3-
const options = require('./eslintOptions')
4+
const options = require('./eslintOptions')(api)
45
const { done } = require('@vue/cli-shared-utils')
56

67
const files = args._ && args._.length ? args._ : ['src', 'test']

packages/@vue/cli-plugin-typescript/__tests__/tsGenerator.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ test('lint', async () => {
6565
id: 'ts',
6666
apply: require('../generator'),
6767
options: {
68-
lint: true,
68+
tsLint: true,
6969
lintOn: ['save', 'commit']
7070
}
7171
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
jest.setTimeout(10000)
2+
3+
const create = require('@vue/cli-test-utils/createTestProject')
4+
5+
test('should work', async () => {
6+
const project = await create('ts-lint', {
7+
plugins: {
8+
'@vue/cli-plugin-eslint': {
9+
config: 'prettier'
10+
},
11+
'@vue/cli-plugin-typescript': {
12+
classComponent: true
13+
}
14+
}
15+
})
16+
const { read, write, run } = project
17+
const main = await read('src/main.ts')
18+
expect(main).toMatch(';')
19+
const app = await read('src/App.vue')
20+
expect(main).toMatch(';')
21+
// remove semicolons
22+
const updatedMain = main.replace(/;/g, '')
23+
await write('src/main.ts', updatedMain)
24+
// for Vue file, only remove semis in script section
25+
const updatedApp = app.replace(/<script(.|\n)*\/script>/, $ => {
26+
return $.replace(/;/g, '')
27+
})
28+
await write('src/App.vue', updatedApp)
29+
// lint
30+
await run('vue-cli-service lint')
31+
expect(await read('src/main.ts')).toMatch(';')
32+
33+
const lintedApp = await read('src/App.vue')
34+
expect(lintedApp).toMatch(';')
35+
// test if ESLint is fixing vue files properly
36+
expect(lintedApp).toBe(app)
37+
})

packages/@vue/cli-plugin-typescript/__tests__/tsPluginLint.spec.js renamed to packages/@vue/cli-plugin-typescript/__tests__/tsPluginTSLint.spec.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ test('should work', async () => {
66
const project = await create('ts-lint', {
77
plugins: {
88
'@vue/cli-plugin-typescript': {
9-
lint: true
9+
tsLint: true
1010
}
1111
}
1212
})

packages/@vue/cli-plugin-typescript/generator/index.js

+2-4
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
module.exports = (api, {
22
classComponent,
3-
lint,
3+
tsLint,
44
lintOn = [],
55
experimentalCompileTsWithBabel
66
}) => {
@@ -46,7 +46,7 @@ module.exports = (api, {
4646
}
4747
}
4848

49-
if (lint) {
49+
if (tsLint && !api.hasPlugin('eslint')) {
5050
api.extendPackage({
5151
scripts: {
5252
lint: 'vue-cli-service lint'
@@ -98,8 +98,6 @@ module.exports = (api, {
9898
})
9999
}
100100

101-
// TODO cater to e2e test plugins
102-
103101
api.render('./template', {
104102
isTest: process.env.VUE_CLI_TEST || process.env.VUE_CLI_DEBUG,
105103
hasMocha,

packages/@vue/cli-plugin-typescript/generator/template/tslint.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
<%_ if (options.lint) { _%>
1+
<%_ if (options.tsLint) { _%>
22
{
33
"defaultSeverity": "warning",
44
"extends": [

packages/@vue/cli-plugin-typescript/index.js

+14-12
Original file line numberDiff line numberDiff line change
@@ -88,16 +88,18 @@ module.exports = (api, {
8888
}])
8989
})
9090

91-
api.registerCommand('lint', {
92-
descriptions: 'lint source files with TSLint',
93-
usage: 'vue-cli-service lint [options] [...files]',
94-
options: {
95-
'--format [formatter]': 'specify formatter (default: codeFrame)',
96-
'--no-fix': 'do not fix errors',
97-
'--formatters-dir [dir]': 'formatter directory',
98-
'--rules-dir [dir]': 'rules directory'
99-
}
100-
}, args => {
101-
return require('./lib/tslint')(args, api)
102-
})
91+
if (!api.hasPlugin('eslint')) {
92+
api.registerCommand('lint', {
93+
descriptions: 'lint source files with TSLint',
94+
usage: 'vue-cli-service lint [options] [...files]',
95+
options: {
96+
'--format [formatter]': 'specify formatter (default: codeFrame)',
97+
'--no-fix': 'do not fix errors',
98+
'--formatters-dir [dir]': 'formatter directory',
99+
'--rules-dir [dir]': 'rules directory'
100+
}
101+
}, args => {
102+
return require('./lib/tslint')(args, api)
103+
})
104+
}
103105
}

packages/@vue/cli/lib/promptModules/__tests__/typescript.spec.js

+55-2
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ const assertPromptModule = require('@vue/cli-test-utils/assertPromptModule')
66
const moduleToTest = require('../typescript')
77
const linterModule = require('../linter')
88

9-
test('should work', async () => {
9+
test('with TSLint', async () => {
1010
const expectedPrompts = [
1111
{
1212
message: 'features',
@@ -21,6 +21,11 @@ test('should work', async () => {
2121
message: 'Use Babel',
2222
confirm: true
2323
},
24+
{
25+
message: 'Pick a linter / formatter',
26+
choices: ['TSLint', 'error prevention', 'Airbnb', 'Standard', 'Prettier'],
27+
choose: [0]
28+
},
2429
{
2530
message: 'Pick additional lint features',
2631
choices: ['on save', 'on commit'],
@@ -32,7 +37,7 @@ test('should work', async () => {
3237
plugins: {
3338
'@vue/cli-plugin-typescript': {
3439
classComponent: true,
35-
lint: true,
40+
tsLint: true,
3641
lintOn: ['save', 'commit'],
3742
useTsWithBabel: true
3843
}
@@ -46,3 +51,51 @@ test('should work', async () => {
4651
{ plguinsOnly: true }
4752
)
4853
})
54+
55+
test('with ESLint', async () => {
56+
const expectedPrompts = [
57+
{
58+
message: 'features',
59+
choices: ['TypeScript', 'Linter'],
60+
check: [0, 1]
61+
},
62+
{
63+
message: 'Use class-style component',
64+
confirm: true
65+
},
66+
{
67+
message: 'Use Babel',
68+
confirm: true
69+
},
70+
{
71+
message: 'Pick a linter / formatter',
72+
choices: ['TSLint', 'error prevention', 'Airbnb', 'Standard', 'Prettier'],
73+
choose: [2]
74+
},
75+
{
76+
message: 'Pick additional lint features',
77+
choices: ['on save', 'on commit'],
78+
check: [0, 1]
79+
}
80+
]
81+
82+
const expectedOptions = {
83+
plugins: {
84+
'@vue/cli-plugin-eslint': {
85+
config: 'airbnb',
86+
lintOn: ['save', 'commit']
87+
},
88+
'@vue/cli-plugin-typescript': {
89+
classComponent: true,
90+
useTsWithBabel: true
91+
}
92+
}
93+
}
94+
95+
await assertPromptModule(
96+
[moduleToTest, linterModule],
97+
expectedPrompts,
98+
expectedOptions,
99+
{ plguinsOnly: true }
100+
)
101+
})

packages/@vue/cli/lib/promptModules/linter.js

+12-7
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,19 @@ module.exports = cli => {
1010

1111
cli.injectPrompt({
1212
name: 'eslintConfig',
13-
when: answers => (
14-
answers.features.includes('linter') &&
15-
!answers.features.includes('ts')
16-
),
13+
when: answers => answers.features.includes('linter'),
1714
type: 'list',
1815
message: 'Pick a linter / formatter config:',
19-
choices: [
16+
choices: answers => [
17+
...(
18+
answers.features.includes('ts')
19+
? [{
20+
name: `TSLint`,
21+
value: 'tslint',
22+
short: 'TSLint'
23+
}]
24+
: []
25+
),
2026
{
2127
name: 'ESLint with error prevention only',
2228
value: 'base',
@@ -58,8 +64,7 @@ module.exports = cli => {
5864
})
5965

6066
cli.onPromptComplete((answers, options) => {
61-
if (answers.features.includes('linter') &&
62-
!answers.features.includes('ts')) {
67+
if (answers.features.includes('linter') && answers.eslintConfig !== 'tslint') {
6368
options.plugins['@vue/cli-plugin-eslint'] = {
6469
config: answers.eslintConfig,
6570
lintOn: answers.lintOn

packages/@vue/cli/lib/promptModules/typescript.js

+2-2
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ module.exports = cli => {
3535
const tsOptions = {
3636
classComponent: answers.tsClassComponent
3737
}
38-
if (answers.features.includes('linter')) {
39-
tsOptions.lint = true
38+
if (answers.eslintConfig === 'tslint') {
39+
tsOptions.tsLint = true
4040
tsOptions.lintOn = answers.lintOn
4141
}
4242
if (answers.useTsWithBabel) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
__tests__/
2+
__mocks__/
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# @vue/eslint-config-typescript
2+
3+
> eslint-config-typescript for vue-cli

0 commit comments

Comments
 (0)