You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
216 lines
6.3 KiB
216 lines
6.3 KiB
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() : '') |
|
}
|
|
|