UNPKG

@acuris/eslint-config

Version:
393 lines (337 loc) 11 kB
#!/usr/bin/env node 'use strict' if (!global.__v8__compile__cache) { try { require('v8-compile-cache') global.__v8__compile__cache = true } catch (_) {} } const path = require('path') const fs = require('fs') const { tryParseAcurisEslintOptions, translateOptionsForCLIEngine } = require('./lib/acuris-eslint-options') const cliOptions = tryParseAcurisEslintOptions() const options = cliOptions.options || {} if (options.cwd) { process.chdir(path.resolve(options.cwd)) } const { eslintRequire, eslintTryRequire, getEslintVersion, eslintPackageJsonPath } = require('../core/node-modules') const { startPrettierService } = require('./lib/eslint-prettier/prettier-service') const { version: packageVersion } = require('../package.json') const chalk = require('chalk') let _handledErrors let timerStarted = null let fixedFiles = 0 let iteratedFiles = 0 process.on('uncaughtException', handleError) if (chalk.level > 0) { require('util').inspect.defaultOptions.colors = true } const fix = options.fix && !options.fixDryRun && !options.stdin if (!cliOptions) { if (!process.exitCode) { process.exitCode = 1 } } else if (cliOptions.command !== undefined) { require('./lib/run-command')(cliOptions) } else { const appTitle = `${chalk.redBright('-')} ${chalk.greenBright(cliOptions.programName)} ${chalk.blueBright( `v${packageVersion}` )}` if (cliOptions.canLog) { if (fix) { console.info(appTitle, chalk.cyan('--fix')) } else { console.info(appTitle) } console.time(appTitle) timerStarted = appTitle } eslint() .then((exitCode) => { endTimeLog() if (exitCode && !process.exitCode) { process.exitCode = exitCode } }) .catch(handleError) } async function eslint() { let endIterationPromise let results let ESLint let engine if (options.debug) { try { const eslintDebug = eslintTryRequire('debug') if (eslintDebug && eslintDebug.enable) { eslintDebug.enable('eslint:*,-eslint:code-path') } } catch (_) {} } const endMonkeyPatch = monkeyPatchEslint() try { ESLint = eslintRequire('./lib/eslint/eslint.js').ESLint const translatedOptions = translateOptionsForCLIEngine(cliOptions) engine = new ESLint(translatedOptions) results = options.stdin ? await engine.lintText(await fs.promises.readFile(0, 'utf8'), options.stdinFilename, false) : await engine.lintFiles(cliOptions.list) } finally { endIterationPromise = endMonkeyPatch && endMonkeyPatch() } if (fix) { const isAbsolute = path.isAbsolute const writeFile = fs.promises.writeFile await Promise.all( results .filter((r) => r && typeof r.output === 'string' && isAbsolute(r.filePath)) .map(async (r) => { await writeFile(r.filePath, r.output) ++fixedFiles }) ) } const endIterationResult = await endIterationPromise if (endIterationResult) { fixedFiles += endIterationResult.prettified results.push(...endIterationResult.errors) } if (options.quiet) { // Quiet mode enabled - filtering out warnings results = ESLint.getErrorResults(results) } else { results = filterOutEslintWarnings(results) } if (await printEslintResults(engine, results, options.format, options.outputFile)) { const status = getResultsStatus(results) const tooManyWarnings = status === 1 if (tooManyWarnings) { console.error(chalk.redBright(`ESLint found too many warnings (maximum: ${options.maxWarnings})\n`)) } return status } return 2 } function getResultsStatus(results) { let warningCount = 0 for (let i = 0; i < results.length; ++i) { const item = results[i] if (item.errorCount > 0) { return 2 } warningCount += item.warningCount || 0 } return options.maxWarnings >= 0 && warningCount > options.maxWarnings ? 1 : 0 } async function printEslintResults(engine, results, format, outputFile) { let formatter try { formatter = await engine.loadFormatter(format) } catch (error) { console.error(chalk.redBright(error)) return false } const rulesMetaProvider = {} Reflect.defineProperty(rulesMetaProvider, 'rulesMeta', { get() { const value = {} Reflect.defineProperty(this, 'rulesMeta', { value }) for (const [k, rule] of engine.getRules()) { value[k] = rule.meta } return value } }) const output = formatter.format(results, rulesMetaProvider) if (!output) { return true } if (outputFile) { const filePath = path.resolve(process.cwd(), outputFile) if (fs.existsSync(filePath) && (await fs.promises.stat(filePath)).isDirectory()) { console.error(chalk.redBright('Cannot write to output file path, it is a directory: %s'), outputFile) return false } try { require('./lib/fs-utils').mkdirSync(path.dirname(filePath)) await fs.promises.writeFile(filePath, output) } catch (ex) { console.error(chalk.redBright('There was a problem writing the output file:\n%s'), ex) return false } return true } console.info(output) return true } function endTimeLog() { const msg = timerStarted if (msg) { timerStarted = null const exitCode = process.exitCode const args = [msg] if (exitCode) { args.push(chalk.redBright(`exit code ${exitCode}`)) args.push(chalk.gray(`- ${iteratedFiles} files processed`)) } else if (iteratedFiles > 0) { args.push(chalk.greenBright('ok')) args.push(chalk.gray('-')) args.push(chalk.greenBright(iteratedFiles)) args.push(chalk.green('file processed')) } else { args.push(chalk.gray('-')) args.push(chalk.yellowBright('0')) args.push(chalk.yellow('file processed')) } if (fix) { if (fixedFiles > 0) { args.push( chalk.gray('- ') + chalk.cyanBright(fixedFiles) + chalk.cyan(` file${fixedFiles === 1 ? '' : 's'} fixed`) ) } else { args.push(chalk.gray(`- ${fixedFiles} files fixed`)) } } console.timeLog(...args) } } function handleError(error) { if (!_handledErrors) { _handledErrors = new WeakSet() } else if (_handledErrors.has(error)) { return } _handledErrors.add(error) if (!process.exitCode) { process.exitCode = 2 } endTimeLog() if (!error) { error = new Error(`${cliOptions.programName} failed`) } console.error(chalk.redBright('\nOops! Something went wrong! :(\n')) if (typeof error.messageTemplate === 'string' && error.messageTemplate.length > 0) { try { const eslintPath = path.dirname(eslintPackageJsonPath()) const template = eslintRequire('lodash').template( fs.readFileSync(path.resolve(eslintPath, `./messages/${error.messageTemplate}.txt`), 'utf-8') ) console.error(`\nESLint: ${getEslintVersion() || '<not found>'}.\n\n${template(error.messageData || {})}`) return } catch (_error) {} } console.error(error.showStack === undefined || error.showStack === true ? error : `${error}`) console.log() if (error.code === 'MODULE_NOT_FOUND') { let moduleError = `${error.message}` || '' moduleError = moduleError.split('\n')[0] || 'Module not found' console.error( chalk.red('- acuris-eslint:'), chalk.redBright(`${moduleError},`), chalk.redBright(`did you run ${chalk.yellowBright('npm install')}?`) ) console.error() process.exit(3) } } function filterOutEslintWarnings(results) { let filtered = null for (let i = 0, len = results.length; i !== len; ++i) { const item = results[i] if (item.warningCount === 1 && item.errorCount === 0) { const messages = item.messages if (messages && messages.length === 1) { const msg = messages[0] const text = msg.message // Filter out "file ignored" messages for husky/lint-staged + .eslintignore if ( text === 'File ignored because of a matching ignore pattern. Use "--no-ignore" to override.' || (typeof text === 'string' && text.startsWith('File ignored by default. ')) ) { if (filtered === null) { filtered = results.slice(0, i) } continue } } } if (filtered !== null) { filtered.push(item) } } return filtered || results } function monkeyPatchEslint() { const fsCache = require('./lib/fs-cache') const stopFsCache = fsCache.startFsCache().stop stopFsCache() const fileEnumeratorProto = eslintRequire('./lib/cli-engine/file-enumerator.js').FileEnumerator.prototype const old_isIgnoredFile = fileEnumeratorProto._isIgnoredFile const oldIterateFiles = fileEnumeratorProto.iterateFiles const oldIsTargetPath = fileEnumeratorProto.isTargetPath fileEnumeratorProto.iterateFiles = iterateFiles if (options.lintStaged || options.ignoreUnknownExtensions) { fileEnumeratorProto._isIgnoredFile = _isIgnoredFile } const prettierService = fix && startPrettierService({ debug: options.debug }) if (prettierService) { // Monkey patch eslint FileEnumerator to pass ignored files to prettier fileEnumeratorProto.isTargetPath = isTargetPath } const endAsync = async () => { const result = (prettierService && (await prettierService.end())) || { prettified: 0, errors: [], iteratedFiles } result.iteratedFiles = iterateFiles return result } const end = () => { if (fileEnumeratorProto.iterateFiles === iterateFiles) { fileEnumeratorProto.iterateFiles = oldIterateFiles } if (fileEnumeratorProto._isIgnoredFile === _isIgnoredFile) { fileEnumeratorProto._isIgnoredFile = old_isIgnoredFile } if (fileEnumeratorProto.isTargetPath === isTargetPath) { fileEnumeratorProto.isTargetPath = oldIsTargetPath } return endAsync() } function isTargetPath(filepath, providedConfig) { const result = oldIsTargetPath.call(this, filepath, providedConfig) if ( !result && providedConfig && path.isAbsolute(filepath) && !old_isIgnoredFile.call(this, filepath, { ...providedConfig, dotfiles: true, direct: false }) ) { ++iteratedFiles prettierService.prettify(filepath) } return result } function* iterateFiles(patternOrPatterns) { const buffer = [] let count = 0 for (const item of oldIterateFiles.call(this, patternOrPatterns)) { if (item.ignored) { continue // Avoid "File ignored because of a matching ignore pattern" warning. } if (count < 54) { ++iteratedFiles buffer.push(item) ++count } else { yield* buffer buffer.length = 0 count = 0 } } yield* buffer } function _isIgnoredFile(filePath, { config, dotfiles, direct }) { return ( old_isIgnoredFile.call(this, filePath, { config, dotfiles, direct }) || !oldIsTargetPath.call(this, filePath, config) ) } return end }