Skip to content
/ vue-cli Public
  • Sponsor vuejs/vue-cli

  • Notifications You must be signed in to change notification settings
  • Fork 6.3k

Commit 099ba93

Browse files
authoredSep 29, 2021
feat(cli-plugin-eslint): use ESLint class instead of CLIEngine (#6714)
1 parent 5f86080 commit 099ba93

File tree

7 files changed

+170
-56
lines changed

7 files changed

+170
-56
lines changed
 

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

+50
Original file line numberDiff line numberDiff line change
@@ -270,3 +270,53 @@ test(`should use formatter 'codeframe'`, async () => {
270270

271271
await donePromise
272272
})
273+
274+
test(`should work with eslint v8`, async () => {
275+
const project = await create('eslint-v8', {
276+
plugins: {
277+
'@vue/cli-plugin-babel': {},
278+
'@vue/cli-plugin-eslint': {
279+
config: 'airbnb',
280+
lintOn: 'save'
281+
}
282+
}
283+
})
284+
const { read, write, run } = project
285+
await run('npm i -D eslint@^8.0.0-0 eslint-formatter-codeframe')
286+
// should've applied airbnb autofix
287+
const main = await read('src/main.js')
288+
expect(main).toMatch(';')
289+
// remove semicolons
290+
const updatedMain = main.replace(/;/g, '')
291+
await write('src/main.js', updatedMain)
292+
// lint
293+
await run('vue-cli-service lint')
294+
expect(await read('src/main.js')).toMatch(';')
295+
})
296+
297+
test(`should work with eslint args`, async () => {
298+
const project = await create('eslint-with-args', {
299+
plugins: {
300+
'@vue/cli-plugin-babel': {},
301+
'@vue/cli-plugin-eslint': {
302+
config: 'airbnb',
303+
lintOn: 'save'
304+
}
305+
}
306+
})
307+
const { read, write, run } = project
308+
await write('src/main.js', `
309+
foo() // Check for apply --global
310+
$('hi!') // Check for apply --env
311+
foo=42
312+
`)
313+
// result file name
314+
const resultsFile = 'lint_results.json'
315+
// lint
316+
await run(`vue-cli-service lint --ext .js --plugin vue --env jquery --global foo:true --format json --output-file ${resultsFile}`)
317+
expect(await read('src/main.js')).toMatch(';')
318+
319+
const resultsContents = JSON.parse(await read(resultsFile))
320+
const resultForMain = resultsContents.find(({ filePath }) => filePath.endsWith('src/main.js'))
321+
expect(resultForMain.messages.length).toBe(0)
322+
})

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -69,8 +69,8 @@ module.exports = (api, { config, lintOn = [] }, rootOptions, invoking) => {
6969
api.assertCliVersion('^4.0.0-beta.0')
7070
} catch (e) {
7171
if (config && config !== 'base') {
72-
api.onCreateComplete(() => {
73-
require('../lint')({ silent: true }, api)
72+
api.onCreateComplete(async () => {
73+
await require('../lint')({ silent: true }, api)
7474
})
7575
}
7676
}
@@ -84,9 +84,9 @@ module.exports = (api, { config, lintOn = [] }, rootOptions, invoking) => {
8484
// FIXME: at the moment we have to catch the bug and silently fail. Need to fix later.
8585
module.exports.hooks = (api) => {
8686
// lint & fix after create to ensure files adhere to chosen config
87-
api.afterAnyInvoke(() => {
87+
api.afterAnyInvoke(async () => {
8888
try {
89-
require('../lint')({ silent: true }, api)
89+
await require('../lint')({ silent: true }, api)
9090
} catch (e) {}
9191
})
9292
}

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

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,8 +77,8 @@ module.exports = (api, options) => {
7777
details:
7878
'For more options, see https://eslint.org/docs/user-guide/command-line-interface#options'
7979
},
80-
args => {
81-
require('./lint')(args, api)
80+
async args => {
81+
await require('./lint')(args, api)
8282
}
8383
)
8484
}

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

+102-39
Original file line numberDiff line numberDiff line change
@@ -2,28 +2,31 @@ const fs = require('fs')
22
const globby = require('globby')
33

44
const renamedArrayArgs = {
5-
ext: 'extensions',
6-
env: 'envs',
7-
global: 'globals',
8-
rulesdir: 'rulePaths',
9-
plugin: 'plugins',
10-
'ignore-pattern': 'ignorePattern'
5+
ext: ['extensions'],
6+
rulesdir: ['rulePaths'],
7+
plugin: ['overrideConfig', 'plugins'],
8+
'ignore-pattern': ['overrideConfig', 'ignorePatterns']
9+
}
10+
11+
const renamedObjectArgs = {
12+
env: { key: ['overrideConfig', 'env'], def: true },
13+
global: { key: ['overrideConfig', 'globals'], def: false }
1114
}
1215

1316
const renamedArgs = {
14-
'inline-config': 'allowInlineConfig',
15-
rule: 'rules',
16-
eslintrc: 'useEslintrc',
17-
c: 'configFile',
18-
config: 'configFile',
19-
'output-file': 'outputFile'
17+
'inline-config': ['allowInlineConfig'],
18+
rule: ['overrideConfig', 'rules'],
19+
eslintrc: ['useEslintrc'],
20+
c: ['overrideConfigFile'],
21+
config: ['overrideConfigFile'],
22+
'output-file': ['outputFile']
2023
}
2124

22-
module.exports = function lint (args = {}, api) {
25+
module.exports = async function lint (args = {}, api) {
2326
const path = require('path')
2427
const cwd = api.resolve('.')
2528
const { log, done, exit, chalk, loadModule } = require('@vue/cli-shared-utils')
26-
const { CLIEngine } = loadModule('eslint', cwd, true) || require('eslint')
29+
const { ESLint } = loadModule('eslint', cwd, true) || require('eslint')
2730
const extensions = require('./eslintOptions').extensions(api)
2831

2932
const argsConfig = normalizeConfig(args)
@@ -37,34 +40,71 @@ module.exports = function lint (args = {}, api) {
3740
const noFixWarningsPredicate = (lintResult) => lintResult.severity === 2
3841
config.fix = config.fix && (noFixWarnings ? noFixWarningsPredicate : true)
3942

40-
if (!fs.existsSync(api.resolve('.eslintignore')) && !config.ignorePattern) {
43+
if (!config.overrideConfig) {
44+
config.overrideConfig = {}
45+
}
46+
47+
if (!fs.existsSync(api.resolve('.eslintignore')) && !config.overrideConfig.ignorePatterns) {
4148
// .eslintrc.js files (ignored by default)
4249
// However, we need to lint & fix them so as to make the default generated project's
4350
// code style consistent with user's selected eslint config.
4451
// Though, if users provided their own `.eslintignore` file, we don't want to
4552
// add our own customized ignore pattern here (in eslint, ignorePattern is
4653
// an addition to eslintignore, i.e. it can't be overridden by user),
4754
// following the principle of least astonishment.
48-
config.ignorePattern = [
55+
config.overrideConfig.ignorePatterns = [
4956
'!.*.js',
5057
'!{src,tests}/**/.*.js'
5158
]
5259
}
53-
54-
const engine = new CLIEngine(config)
55-
56-
const defaultFilesToLint = [
60+
/** @type {import('eslint').ESLint} */
61+
const eslint = new ESLint(Object.fromEntries([
62+
63+
// File enumeration
64+
'cwd',
65+
'errorOnUnmatchedPattern',
66+
'extensions',
67+
'globInputPaths',
68+
'ignore',
69+
'ignorePath',
70+
71+
// Linting
72+
'allowInlineConfig',
73+
'baseConfig',
74+
'overrideConfig',
75+
'overrideConfigFile',
76+
'plugins',
77+
'reportUnusedDisableDirectives',
78+
'resolvePluginsRelativeTo',
79+
'rulePaths',
80+
'useEslintrc',
81+
82+
// Autofix
83+
'fix',
84+
'fixTypes',
85+
86+
// Cache-related
87+
'cache',
88+
'cacheLocation',
89+
'cacheStrategy'
90+
].map(k => [k, config[k]])))
91+
92+
const defaultFilesToLint = []
93+
94+
for (const pattern of [
5795
'src',
5896
'tests',
5997
// root config files
6098
'*.js',
6199
'.*.js'
62-
]
63-
.filter(pattern =>
64-
globby
65-
.sync(pattern, { cwd, absolute: true })
66-
.some(p => !engine.isPathIgnored(p))
67-
)
100+
]) {
101+
if ((await Promise.all(globby
102+
.sync(pattern, { cwd, absolute: true })
103+
.map(p => eslint.isPathIgnored(p))))
104+
.some(r => !r)) {
105+
defaultFilesToLint.push(pattern)
106+
}
107+
}
68108

69109
const files = args._ && args._.length
70110
? args._
@@ -79,51 +119,53 @@ module.exports = function lint (args = {}, api) {
79119
if (!api.invoking) {
80120
process.cwd = () => cwd
81121
}
82-
const report = engine.executeOnFiles(files)
122+
const resultResults = await eslint.lintFiles(files)
123+
const reportErrorCount = resultResults.reduce((p, c) => p + c.errorCount, 0)
124+
const reportWarningCount = resultResults.reduce((p, c) => p + c.warningCount, 0)
83125
process.cwd = processCwd
84126

85-
const formatter = engine.getFormatter(args.format || 'codeframe')
127+
const formatter = await eslint.loadFormatter(args.format || 'codeframe')
86128

87129
if (config.outputFile) {
88130
const outputFilePath = path.resolve(config.outputFile)
89131
try {
90-
fs.writeFileSync(outputFilePath, formatter(report.results))
132+
fs.writeFileSync(outputFilePath, formatter.format(resultResults))
91133
log(`Lint results saved to ${chalk.blue(outputFilePath)}`)
92134
} catch (err) {
93135
log(`Error saving lint results to ${chalk.blue(outputFilePath)}: ${chalk.red(err)}`)
94136
}
95137
}
96138

97139
if (config.fix) {
98-
CLIEngine.outputFixes(report)
140+
await ESLint.outputFixes(resultResults)
99141
}
100142

101143
const maxErrors = argsConfig.maxErrors || 0
102144
const maxWarnings = typeof argsConfig.maxWarnings === 'number' ? argsConfig.maxWarnings : Infinity
103-
const isErrorsExceeded = report.errorCount > maxErrors
104-
const isWarningsExceeded = report.warningCount > maxWarnings
145+
const isErrorsExceeded = reportErrorCount > maxErrors
146+
const isWarningsExceeded = reportWarningCount > maxWarnings
105147

106148
if (!isErrorsExceeded && !isWarningsExceeded) {
107149
if (!args.silent) {
108-
const hasFixed = report.results.some(f => f.output)
150+
const hasFixed = resultResults.some(f => f.output)
109151
if (hasFixed) {
110152
log(`The following files have been auto-fixed:`)
111153
log()
112-
report.results.forEach(f => {
154+
resultResults.forEach(f => {
113155
if (f.output) {
114156
log(` ${chalk.blue(path.relative(cwd, f.filePath))}`)
115157
}
116158
})
117159
log()
118160
}
119-
if (report.warningCount || report.errorCount) {
120-
console.log(formatter(report.results))
161+
if (reportWarningCount || reportErrorCount) {
162+
console.log(formatter.format(resultResults))
121163
} else {
122164
done(hasFixed ? `All lint errors auto-fixed.` : `No lint errors found!`)
123165
}
124166
}
125167
} else {
126-
console.log(formatter(report.results))
168+
console.log(formatter.format(resultResults))
127169
if (isErrorsExceeded && typeof argsConfig.maxErrors === 'number') {
128170
log(`Eslint found too many errors (maximum: ${argsConfig.maxErrors}).`)
129171
}
@@ -138,14 +180,35 @@ function normalizeConfig (args) {
138180
const config = {}
139181
for (const key in args) {
140182
if (renamedArrayArgs[key]) {
141-
config[renamedArrayArgs[key]] = args[key].split(',')
183+
applyConfig(renamedArrayArgs[key], args[key].split(','))
184+
} else if (renamedObjectArgs[key]) {
185+
const obj = arrayToBoolObject(args[key].split(','), renamedObjectArgs[key].def)
186+
applyConfig(renamedObjectArgs[key].key, obj)
142187
} else if (renamedArgs[key]) {
143-
config[renamedArgs[key]] = args[key]
188+
applyConfig(renamedArgs[key], args[key])
144189
} else if (key !== '_') {
145190
config[camelize(key)] = args[key]
146191
}
147192
}
148193
return config
194+
195+
function applyConfig ([...keyPaths], value) {
196+
let targetConfig = config
197+
const lastKey = keyPaths.pop()
198+
for (const k of keyPaths) {
199+
targetConfig = targetConfig[k] || (targetConfig[k] = {})
200+
}
201+
targetConfig[lastKey] = value
202+
}
203+
204+
function arrayToBoolObject (array, defaultBool) {
205+
const object = {}
206+
for (const element of array) {
207+
const [key, value] = element.split(':')
208+
object[key] = value != null ? value === 'true' : defaultBool
209+
}
210+
return object
211+
}
149212
}
150213

151214
function camelize (str) {

‎packages/@vue/cli-service/types/cli-service-test.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ const servicePlugin: ServicePlugin = (api, options) => {
1717
},
1818
details: 'For more options, see https://eslint.org/docs/user-guide/command-line-interface#options'
1919
},
20-
args => {
21-
require('./lint')(args, api)
20+
async args => {
21+
await require('./lint')(args, api)
2222
}
2323
)
2424
api.registerCommand('lint', args => {})

‎scripts/buildEditorConfig.js

+9-8
Original file line numberDiff line numberDiff line change
@@ -10,18 +10,19 @@
1010

1111
const fs = require('fs')
1212
const path = require('path')
13-
const CLIEngine = require('eslint').CLIEngine
13+
const ESLint = require('eslint').ESLint
1414

1515
// Convert eslint rules to editorconfig rules.
16-
function convertRules (config) {
16+
async function convertRules (config) {
1717
const result = {}
1818

19-
const eslintRules = new CLIEngine({
19+
const eslint = new ESLint({
2020
useEslintrc: false,
2121
baseConfig: {
2222
extends: [require.resolve(`@vue/eslint-config-${config}`)]
2323
}
24-
}).getConfigForFile().rules
24+
})
25+
const eslintRules = (await eslint.calculateConfigForFile()).rules
2526

2627
const getRuleOptions = (ruleName, defaultOptions = []) => {
2728
const ruleConfig = eslintRules[ruleName]
@@ -90,7 +91,7 @@ function convertRules (config) {
9091
return result
9192
}
9293

93-
exports.buildEditorConfig = function buildEditorConfig () {
94+
exports.buildEditorConfig = async function buildEditorConfig () {
9495
console.log('Building EditorConfig files...')
9596
// Get built-in eslint configs
9697
const configList = fs.readdirSync(path.resolve(__dirname, '../packages/@vue/'))
@@ -100,10 +101,10 @@ exports.buildEditorConfig = function buildEditorConfig () {
100101
})
101102
.filter(x => x)
102103

103-
configList.forEach(config => {
104+
await Promise.all(configList.map(async config => {
104105
let content = '[*.{js,jsx,ts,tsx,vue}]\n'
105106

106-
const editorconfig = convertRules(config)
107+
const editorconfig = await convertRules(config)
107108

108109
// `eslint-config-prettier` & `eslint-config-typescript` do not have any style rules
109110
if (!Object.keys(editorconfig).length) {
@@ -119,6 +120,6 @@ exports.buildEditorConfig = function buildEditorConfig () {
119120
fs.mkdirSync(templateDir)
120121
}
121122
fs.writeFileSync(`${templateDir}/_editorconfig`, content)
122-
})
123+
}))
123124
console.log('EditorConfig files up-to-date.')
124125
}

‎scripts/release.js

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ const release = async () => {
8888
})
8989
delete process.env.PREFIX
9090

91-
// buildEditorConfig()
91+
// await buildEditorConfig()
9292

9393
try {
9494
await execa('git', ['add', '-A'], { stdio: 'inherit' })

0 commit comments

Comments
 (0)