|
|
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() : '')}
|