UNPKG

@nx/eslint

Version:

The ESLint plugin for Nx contains executors, generators and utilities used for linting JavaScript/TypeScript projects within an Nx workspace.

183 lines (181 loc) • 8.56 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.default = run; const devkit_1 = require("@nx/devkit"); const fs_1 = require("fs"); const utils_1 = require("nx/src/tasks-runner/utils"); const path_1 = require("path"); const config_file_1 = require("../../utils/config-file"); const eslint_utils_1 = require("./utility/eslint-utils"); async function run(options, context) { // this is only used for the hasher delete options.hasTypeAwareRules; const systemRoot = context.root; // eslint resolves files relative to the current working directory. // We want these paths to always be resolved relative to the workspace // root to be able to run the lint executor from any subfolder. process.chdir(systemRoot); const projectName = context.projectName || '<???>'; const projectRoot = context.projectsConfigurations.projects[context.projectName].root; const printInfo = options.format && !options.silent; if (printInfo) { console.info(`\nLinting ${JSON.stringify(projectName)}...`); } options.cacheLocation = options.cacheLocation ? (0, devkit_1.joinPathFragments)(options.cacheLocation, projectName) : undefined; const { printConfig, errorOnUnmatchedPattern, ...normalizedOptions } = options; // locate the flat config file if it exists starting from the project root const flatConfigFilePath = (0, config_file_1.findFlatConfigFile)(projectRoot, context.root); const hasFlatConfig = flatConfigFilePath !== null; // while standard eslint uses by default closest config to the file, if otherwise not specified, // the flat config would be resolved starting from the cwd, which we changed to the workspace root // so we explicitly set the config path to the flat config file path we previously found if (hasFlatConfig && !normalizedOptions.eslintConfig) { normalizedOptions.eslintConfig = (0, path_1.relative)(systemRoot, flatConfigFilePath); } /** * We want users to have the option of not specifying the config path, and let * eslint automatically resolve the `.eslintrc.json` files in each folder. */ let eslintConfigPath = normalizedOptions.eslintConfig ? (0, path_1.resolve)(systemRoot, normalizedOptions.eslintConfig) : undefined; const { eslint, ESLint } = await (0, eslint_utils_1.resolveAndInstantiateESLint)(eslintConfigPath, normalizedOptions, hasFlatConfig); const version = ESLint.version?.split('.'); if (!version || version.length < 2 || Number(version[0]) < 7 || (Number(version[0]) === 7 && Number(version[1]) < 6)) { throw new Error('ESLint must be version 7.6 or higher.'); } if (printConfig) { try { const fileConfig = await eslint.calculateConfigForFile(printConfig); console.log(JSON.stringify(fileConfig, null, ' ')); return { success: true, }; } catch (err) { console.error(err); return { success: false, }; } } let lintResults = []; const normalizedLintFilePatterns = normalizedOptions.lintFilePatterns.map((pattern) => { return (0, utils_1.interpolate)(pattern, { workspaceRoot: '', projectRoot, projectName: context.projectName, }); }); try { lintResults = await eslint.lintFiles(normalizedLintFilePatterns); } catch (err) { if (err.message.includes('You must therefore provide a value for the "parserOptions.project" property for @typescript-eslint/parser')) { const ruleName = err.message.match(/rule '([^']+)':/)?.[1]; const reportedFile = err.message.match(/Occurred while linting (.+)$/)?.[1]; let eslintConfigPathForError = `for the project "${projectName}"`; if (eslintConfigPath) { eslintConfigPathForError = `"${path_1.posix.relative(context.root, eslintConfigPath)}"`; } else { const configPathForfile = hasFlatConfig ? (0, config_file_1.findFlatConfigFile)(projectRoot, context.root) : (0, config_file_1.findOldConfigFile)(reportedFile ?? projectRoot, context.root); if (configPathForfile) { eslintConfigPathForError = `"${path_1.posix.relative(context.root, configPathForfile)}"`; } } console.error(` Error: You have attempted to use ${ruleName ? `the lint rule "${ruleName}"` : 'a lint rule'} which requires the full TypeScript type-checker to be available, but you do not have "parserOptions.project" configured to point at your project tsconfig.json files in the relevant TypeScript file "overrides" block of your ESLint config ${eslintConfigPathForError} ${reportedFile ? `Occurred while linting ${reportedFile}` : ''} Please see https://nx.dev/recipes/tips-n-tricks/eslint for full guidance on how to resolve this issue. `); return { success: false, }; } // If some unexpected error, rethrow throw err; } if (lintResults.length === 0 && errorOnUnmatchedPattern) { const ignoredPatterns = (await Promise.all(normalizedLintFilePatterns.map(async (pattern) => (await eslint.isPathIgnored(pattern)) ? pattern : null))) .filter((pattern) => !!pattern) .map((pattern) => `- '${pattern}'`); if (ignoredPatterns.length) { const ignoreSection = hasFlatConfig ? `'ignores' configuration` : `'.eslintignore' file`; throw new Error(`All files matching the following patterns are ignored:\n${ignoredPatterns.join('\n')}\n\nPlease check your ${ignoreSection}.`); } throw new Error('Invalid lint configuration. Nothing to lint. Please check your lint target pattern(s).'); } // output fixes to disk, if applicable based on the options await ESLint.outputFixes(lintResults); // if quiet, only show errors if (normalizedOptions.quiet) { console.debug('Quiet mode enabled - filtering out warnings\n'); lintResults = ESLint.getErrorResults(lintResults); } const formatter = await eslint.loadFormatter(normalizedOptions.format); const formattedResults = await formatter.format(lintResults); if (normalizedOptions.outputFile) { const pathToOutputFile = (0, devkit_1.joinPathFragments)(context.root, normalizedOptions.outputFile); (0, fs_1.mkdirSync)((0, path_1.dirname)(pathToOutputFile), { recursive: true }); (0, fs_1.writeFileSync)(pathToOutputFile, formattedResults); } else { console.info(formattedResults); } const totals = getTotals(lintResults); if (printInfo) { outputPrintInfo(totals); } if (options.maxWarnings >= 0 && totals.warnings > options.maxWarnings) { console.info(`ESLint found too many warnings (maximum: ${options.maxWarnings}).`); } return { success: normalizedOptions.force || (totals.errors === 0 && (normalizedOptions.maxWarnings === -1 || totals.warnings <= normalizedOptions.maxWarnings)), }; } function getTotals(lintResults) { let errors = 0; let warnings = 0; let fixableErrors = 0; let fixableWarnings = 0; for (const result of lintResults) { errors += result.errorCount || 0; warnings += result.warningCount || 0; fixableErrors += result.fixableErrorCount || 0; fixableWarnings += result.fixableWarningCount || 0; } return { errors, warnings, fixableErrors, fixableWarnings, }; } function pluralizedOutput(word, count) { return `${count} ${word}${count === 1 ? '' : 's'}`; } function outputPrintInfo({ errors, warnings, fixableErrors, fixableWarnings, }) { const total = warnings + errors; const totalFixable = fixableErrors + fixableWarnings; if (total <= 0) { console.info('\u2714 All files pass linting\n'); return; } console.info(`\u2716 ${pluralizedOutput('problem', total)} (${pluralizedOutput('error', errors)}, ${pluralizedOutput('warning', warnings)})\n`); if (totalFixable <= 0) return; console.info(` ${pluralizedOutput('error', fixableErrors)} and ${pluralizedOutput('warning', fixableWarnings)} are potentially fixable with the \`--fix\` option.\n`); }