123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216 |
- const fs = require('fs')
- const globby = require('globby')
- const renamedArrayArgs = {
- ext: ['extensions'],
- rulesdir: ['rulePaths'],
- plugin: ['overrideConfig', 'plugins'],
- 'ignore-pattern': ['overrideConfig', 'ignorePatterns']
- }
- const renamedObjectArgs = {
- env: { key: ['overrideConfig', 'env'], def: true },
- global: { key: ['overrideConfig', 'globals'], def: false }
- }
- const renamedArgs = {
- 'inline-config': ['allowInlineConfig'],
- rule: ['overrideConfig', 'rules'],
- eslintrc: ['useEslintrc'],
- c: ['overrideConfigFile'],
- config: ['overrideConfigFile'],
- 'output-file': ['outputFile']
- }
- module.exports = async function lint (args = {}, api) {
- const path = require('path')
- const cwd = api.resolve('.')
- const { log, done, exit, chalk, loadModule } = require('@vue/cli-shared-utils')
- const { ESLint } = loadModule('eslint', cwd, true) || require('eslint')
- const extensions = require('./eslintOptions').extensions(api)
- const argsConfig = normalizeConfig(args)
- const config = Object.assign({
- extensions,
- fix: true,
- cwd
- }, argsConfig)
- const noFixWarnings = (argsConfig.fixWarnings === false)
- const noFixWarningsPredicate = (lintResult) => lintResult.severity === 2
- config.fix = config.fix && (noFixWarnings ? noFixWarningsPredicate : true)
- if (!config.overrideConfig) {
- config.overrideConfig = {}
- }
- if (!fs.existsSync(api.resolve('.eslintignore')) && !config.overrideConfig.ignorePatterns) {
- // .eslintrc.js files (ignored by default)
- // However, we need to lint & fix them so as to make the default generated project's
- // code style consistent with user's selected eslint config.
- // Though, if users provided their own `.eslintignore` file, we don't want to
- // add our own customized ignore pattern here (in eslint, ignorePattern is
- // an addition to eslintignore, i.e. it can't be overridden by user),
- // following the principle of least astonishment.
- config.overrideConfig.ignorePatterns = [
- '!.*.js',
- '!{src,tests}/**/.*.js'
- ]
- }
- /** @type {import('eslint').ESLint} */
- const eslint = new ESLint(Object.fromEntries([
- // File enumeration
- 'cwd',
- 'errorOnUnmatchedPattern',
- 'extensions',
- 'globInputPaths',
- 'ignore',
- 'ignorePath',
- // Linting
- 'allowInlineConfig',
- 'baseConfig',
- 'overrideConfig',
- 'overrideConfigFile',
- 'plugins',
- 'reportUnusedDisableDirectives',
- 'resolvePluginsRelativeTo',
- 'rulePaths',
- 'useEslintrc',
- // Autofix
- 'fix',
- 'fixTypes',
- // Cache-related
- 'cache',
- 'cacheLocation',
- 'cacheStrategy'
- ].map(k => [k, config[k]])))
- const defaultFilesToLint = []
- for (const pattern of [
- 'src',
- 'tests',
- // root config files
- '*.js',
- '.*.js'
- ]) {
- if ((await Promise.all(globby
- .sync(pattern, { cwd, absolute: true })
- .map(p => eslint.isPathIgnored(p))))
- .some(r => !r)) {
- defaultFilesToLint.push(pattern)
- }
- }
- const files = args._ && args._.length
- ? args._
- : defaultFilesToLint
- // mock process.cwd before executing
- // See:
- // https://github.com/vuejs/vue-cli/issues/2554
- // https://github.com/benmosher/eslint-plugin-import/issues/602
- // https://github.com/eslint/eslint/issues/11218
- const processCwd = process.cwd
- if (!api.invoking) {
- process.cwd = () => cwd
- }
- const resultResults = await eslint.lintFiles(files)
- const reportErrorCount = resultResults.reduce((p, c) => p + c.errorCount, 0)
- const reportWarningCount = resultResults.reduce((p, c) => p + c.warningCount, 0)
- process.cwd = processCwd
- const formatter = await eslint.loadFormatter(args.format || 'stylish')
- if (config.outputFile) {
- const outputFilePath = path.resolve(config.outputFile)
- try {
- fs.writeFileSync(outputFilePath, formatter.format(resultResults))
- log(`Lint results saved to ${chalk.blue(outputFilePath)}`)
- } catch (err) {
- log(`Error saving lint results to ${chalk.blue(outputFilePath)}: ${chalk.red(err)}`)
- }
- }
- if (config.fix) {
- await ESLint.outputFixes(resultResults)
- }
- const maxErrors = argsConfig.maxErrors || 0
- const maxWarnings = typeof argsConfig.maxWarnings === 'number' ? argsConfig.maxWarnings : Infinity
- const isErrorsExceeded = reportErrorCount > maxErrors
- const isWarningsExceeded = reportWarningCount > maxWarnings
- if (!isErrorsExceeded && !isWarningsExceeded) {
- if (!args.silent) {
- const hasFixed = resultResults.some(f => f.output)
- if (hasFixed) {
- log(`The following files have been auto-fixed:`)
- log()
- resultResults.forEach(f => {
- if (f.output) {
- log(` ${chalk.blue(path.relative(cwd, f.filePath))}`)
- }
- })
- log()
- }
- if (reportWarningCount || reportErrorCount) {
- console.log(formatter.format(resultResults))
- } else {
- done(hasFixed ? `All lint errors auto-fixed.` : `No lint errors found!`)
- }
- }
- } else {
- console.log(formatter.format(resultResults))
- if (isErrorsExceeded && typeof argsConfig.maxErrors === 'number') {
- log(`Eslint found too many errors (maximum: ${argsConfig.maxErrors}).`)
- }
- if (isWarningsExceeded) {
- log(`Eslint found too many warnings (maximum: ${argsConfig.maxWarnings}).`)
- }
- exit(1)
- }
- }
- function normalizeConfig (args) {
- const config = {}
- for (const key in args) {
- if (renamedArrayArgs[key]) {
- applyConfig(renamedArrayArgs[key], args[key].split(','))
- } else if (renamedObjectArgs[key]) {
- const obj = arrayToBoolObject(args[key].split(','), renamedObjectArgs[key].def)
- applyConfig(renamedObjectArgs[key].key, obj)
- } else if (renamedArgs[key]) {
- applyConfig(renamedArgs[key], args[key])
- } else if (key !== '_') {
- config[camelize(key)] = args[key]
- }
- }
- return config
- function applyConfig ([...keyPaths], value) {
- let targetConfig = config
- const lastKey = keyPaths.pop()
- for (const k of keyPaths) {
- targetConfig = targetConfig[k] || (targetConfig[k] = {})
- }
- targetConfig[lastKey] = value
- }
- function arrayToBoolObject (array, defaultBool) {
- const object = {}
- for (const element of array) {
- const [key, value] = element.split(':')
- object[key] = value != null ? value === 'true' : defaultBool
- }
- return object
- }
- }
- function camelize (str) {
- return str.replace(/-(\w)/g, (_, c) => c ? c.toUpperCase() : '')
- }
|