UNPKG

next

Version:

The React Framework

360 lines (359 loc) 17 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); Object.defineProperty(exports, "runLintCheck", { enumerable: true, get: function() { return runLintCheck; } }); const _fs = require("fs"); const _picocolors = require("../picocolors"); const _path = /*#__PURE__*/ _interop_require_default(require("path")); const _findup = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/find-up")); const _semver = /*#__PURE__*/ _interop_require_default(require("next/dist/compiled/semver")); const _commentjson = /*#__PURE__*/ _interop_require_wildcard(require("next/dist/compiled/comment-json")); const _customFormatter = require("./customFormatter"); const _writeDefaultConfig = require("./writeDefaultConfig"); const _hasEslintConfiguration = require("./hasEslintConfiguration"); const _writeOutputFile = require("./writeOutputFile"); const _findpagesdir = require("../find-pages-dir"); const _installdependencies = require("../install-dependencies"); const _hasnecessarydependencies = require("../has-necessary-dependencies"); const _log = /*#__PURE__*/ _interop_require_wildcard(require("../../build/output/log")); const _iserror = /*#__PURE__*/ _interop_require_wildcard(require("../is-error")); const _getpkgmanager = require("../helpers/get-pkg-manager"); const _getESLintPromptValues = require("./getESLintPromptValues"); function _interop_require_default(obj) { return obj && obj.__esModule ? obj : { default: obj }; } function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } function _interop_require_wildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || typeof obj !== "object" && typeof obj !== "function") { return { default: obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = { __proto__: null }; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for(var key in obj){ if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj.default = obj; if (cache) { cache.set(obj, newObj); } return newObj; } // 0 is off, 1 is warn, 2 is error. See https://eslint.org/docs/user-guide/configuring/rules#configuring-rules const VALID_SEVERITY = [ 'off', 'warn', 'error' ]; function isValidSeverity(severity) { return VALID_SEVERITY.includes(severity); } const requiredPackages = [ { file: 'eslint', pkg: 'eslint', exportsRestrict: false }, { file: 'eslint-config-next', pkg: 'eslint-config-next', exportsRestrict: false } ]; async function cliPrompt(cwd) { console.log((0, _picocolors.bold)(`${(0, _picocolors.cyan)('?')} How would you like to configure ESLint? https://nextjs.org/docs/app/api-reference/config/eslint`)); try { const cliSelect = (await Promise.resolve(require('next/dist/compiled/cli-select'))).default; const { value } = await cliSelect({ values: await (0, _getESLintPromptValues.getESLintPromptValues)(cwd), valueRenderer: ({ title, recommended }, selected)=>{ const name = selected ? (0, _picocolors.bold)((0, _picocolors.underline)((0, _picocolors.cyan)(title))) : title; return name + (recommended ? (0, _picocolors.bold)((0, _picocolors.yellow)(' (recommended)')) : ''); }, selected: (0, _picocolors.cyan)('❯ '), unselected: ' ' }); return { config: (value == null ? void 0 : value.config) ?? null }; } catch { return { config: null }; } } async function lint(baseDir, lintDirs, eslintrcFile, pkgJsonPath, { lintDuringBuild = false, eslintOptions = null, reportErrorsOnly = false, maxWarnings = -1, formatter = null, outputFile = null }) { try { var _mod_CLIEngine, _ESLint_getErrorResults; // Load ESLint after we're sure it exists: const deps = await (0, _hasnecessarydependencies.hasNecessaryDependencies)(baseDir, requiredPackages); const packageManager = (0, _getpkgmanager.getPkgManager)(baseDir); if (deps.missing.some((dep)=>dep.pkg === 'eslint')) { _log.error(`ESLint must be installed${lintDuringBuild ? ' in order to run during builds:' : ':'} ${(0, _picocolors.bold)((0, _picocolors.cyan)((packageManager === 'yarn' ? 'yarn add --dev' : packageManager === 'pnpm' ? 'pnpm install --save-dev' : 'npm install --save-dev') + ' eslint'))}`); return null; } const mod = await Promise.resolve(require(deps.resolved.get('eslint'))); // If V9 config was found, use flat config, or else use legacy. const useFlatConfig = eslintrcFile ? _path.default.basename(eslintrcFile).startsWith('eslint.config.') : false; let ESLint; // loadESLint is >= 8.57.0 // PR https://github.com/eslint/eslint/pull/18098 // Release https://github.com/eslint/eslint/releases/tag/v8.57.0 if ('loadESLint' in mod) { // By default, configType is `flat`. If `useFlatConfig` is false, the return value is `LegacyESLint`. // https://github.com/eslint/eslint/blob/1def4cdfab1f067c5089df8b36242cdf912b0eb6/lib/types/index.d.ts#L1609-L1613 ESLint = await mod.loadESLint({ useFlatConfig }); } else { // eslint < 8.57.0, use legacy ESLint ESLint = mod.ESLint; } let eslintVersion = (ESLint == null ? void 0 : ESLint.version) ?? ((_mod_CLIEngine = mod.CLIEngine) == null ? void 0 : _mod_CLIEngine.version); if (!eslintVersion || _semver.default.lt(eslintVersion, '7.0.0')) { return `${(0, _picocolors.red)('error')} - Your project has an older version of ESLint installed${eslintVersion ? ' (' + eslintVersion + ')' : ''}. Please upgrade to ESLint version 7 or above`; } let options = { useEslintrc: true, baseConfig: {}, errorOnUnmatchedPattern: false, extensions: [ '.js', '.jsx', '.ts', '.tsx' ], cache: true, ...eslintOptions }; if (_semver.default.gte(eslintVersion, '9.0.0') && useFlatConfig) { for (const option of [ 'useEslintrc', 'extensions', 'ignorePath', 'reportUnusedDisableDirectives', 'resolvePluginsRelativeTo', 'rulePaths', 'inlineConfig', 'maxWarnings' ]){ if (option in options) { delete options[option]; } } } let eslint = new ESLint(options); let nextEslintPluginIsEnabled = false; const nextRulesEnabled = new Map(); for (const configFile of [ eslintrcFile, pkgJsonPath ]){ if (!configFile) continue; const completeConfig = await eslint.calculateConfigForFile(configFile); if (!completeConfig) continue; const plugins = completeConfig.plugins; const hasNextPlugin = // in ESLint < 9, `plugins` value is string[] Array.isArray(plugins) ? plugins.includes('@next/next') : '@next/next' in plugins; if (hasNextPlugin) { nextEslintPluginIsEnabled = true; for (const [name, [severity]] of Object.entries(completeConfig.rules)){ if (!name.startsWith('@next/next/')) { continue; } if (typeof severity === 'number' && severity >= 0 && severity < VALID_SEVERITY.length) { nextRulesEnabled.set(name, VALID_SEVERITY[severity]); } else if (typeof severity === 'string' && isValidSeverity(severity)) { nextRulesEnabled.set(name, severity); } } break; } } const pagesDir = (0, _findpagesdir.findPagesDir)(baseDir).pagesDir; const pagesDirRules = pagesDir ? [ '@next/next/no-html-link-for-pages' ] : []; if (nextEslintPluginIsEnabled) { let updatedPagesDir = false; for (const rule of pagesDirRules){ var _options_baseConfig_rules, _options_baseConfig_rules1; if (!((_options_baseConfig_rules = options.baseConfig.rules) == null ? void 0 : _options_baseConfig_rules[rule]) && !((_options_baseConfig_rules1 = options.baseConfig.rules) == null ? void 0 : _options_baseConfig_rules1[rule.replace('@next/next', '@next/babel-plugin-next')])) { if (!options.baseConfig.rules) { options.baseConfig.rules = {}; } options.baseConfig.rules[rule] = [ 1, pagesDir ]; updatedPagesDir = true; } } if (updatedPagesDir) { eslint = new ESLint(options); } } else { _log.warn(''); _log.warn('The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/app/api-reference/config/eslint#migrating-existing-config'); } const lintStart = process.hrtime(); let results = await eslint.lintFiles(lintDirs); let selectedFormatter = null; if (options.fix) await ESLint.outputFixes(results); if (reportErrorsOnly) results = await ESLint.getErrorResults(results) // Only return errors if --quiet flag is used ; if (formatter) selectedFormatter = await eslint.loadFormatter(formatter); const formattedResult = await (0, _customFormatter.formatResults)(baseDir, results, selectedFormatter == null ? void 0 : selectedFormatter.format); const lintEnd = process.hrtime(lintStart); const totalWarnings = results.reduce((sum, file)=>sum + file.warningCount, 0); if (outputFile) await (0, _writeOutputFile.writeOutputFile)(outputFile, formattedResult.output); return { output: formattedResult.outputWithMessages, isError: ((_ESLint_getErrorResults = ESLint.getErrorResults(results)) == null ? void 0 : _ESLint_getErrorResults.length) > 0 || maxWarnings >= 0 && totalWarnings > maxWarnings, eventInfo: { durationInSeconds: lintEnd[0], eslintVersion: eslintVersion, lintedFilesCount: results.length, lintFix: !!options.fix, nextEslintPluginVersion: nextEslintPluginIsEnabled && deps.resolved.has('eslint-config-next') ? require(_path.default.join(_path.default.dirname(deps.resolved.get('eslint-config-next')), 'package.json')).version : null, nextEslintPluginErrorsCount: formattedResult.totalNextPluginErrorCount, nextEslintPluginWarningsCount: formattedResult.totalNextPluginWarningCount, nextRulesEnabled: Object.fromEntries(nextRulesEnabled) } }; } catch (err) { if (lintDuringBuild) { _log.error(`ESLint: ${(0, _iserror.default)(err) && err.message ? err.message.replace(/\n/g, ' ') : err}`); return null; } else { throw (0, _iserror.getProperError)(err); } } } async function runLintCheck(baseDir, lintDirs, opts) { const { lintDuringBuild = false, eslintOptions = null, reportErrorsOnly = false, maxWarnings = -1, formatter = null, outputFile = null, strict = false } = opts; try { // Find user's .eslintrc file // See: https://eslint.org/docs/user-guide/configuring/configuration-files#configuration-file-formats const eslintrcFile = await (0, _findup.default)([ // eslint v9 'eslint.config.js', 'eslint.config.mjs', 'eslint.config.cjs', // TS extensions require to install a separate package `jiti`. // https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files 'eslint.config.ts', 'eslint.config.mts', 'eslint.config.cts', // eslint <= v8 '.eslintrc.js', '.eslintrc.cjs', '.eslintrc.yaml', '.eslintrc.yml', '.eslintrc.json', '.eslintrc' ], { cwd: baseDir }) ?? null; const pkgJsonPath = await (0, _findup.default)('package.json', { cwd: baseDir }) ?? null; let packageJsonConfig = null; if (pkgJsonPath) { const pkgJsonContent = await _fs.promises.readFile(pkgJsonPath, { encoding: 'utf8' }); packageJsonConfig = _commentjson.parse(pkgJsonContent); } const config = await (0, _hasEslintConfiguration.hasEslintConfiguration)(eslintrcFile, packageJsonConfig); let deps; if (config.exists) { // Run if ESLint config exists return await lint(baseDir, lintDirs, eslintrcFile, pkgJsonPath, { lintDuringBuild, eslintOptions, reportErrorsOnly, maxWarnings, formatter, outputFile }); } else { // Display warning if no ESLint configuration is present inside // config file during "next build", no warning is shown when // no eslintrc file is present if (lintDuringBuild) { if (config.emptyPkgJsonConfig || config.emptyEslintrc) { _log.warn(`No ESLint configuration detected. Run ${(0, _picocolors.bold)((0, _picocolors.cyan)('next lint'))} to begin setup`); } return null; } else { // Ask user what config they would like to start with for first time "next lint" setup const { config: selectedConfig } = strict ? await (0, _getESLintPromptValues.getESLintStrictValue)(baseDir) : await cliPrompt(baseDir); if (selectedConfig == null) { // Show a warning if no option is selected in prompt _log.warn('If you set up ESLint yourself, we recommend adding the Next.js ESLint plugin. See https://nextjs.org/docs/app/api-reference/config/eslint#migrating-existing-config'); return null; } else { // Check if necessary deps installed, and install any that are missing deps = await (0, _hasnecessarydependencies.hasNecessaryDependencies)(baseDir, requiredPackages); if (deps.missing.length > 0) { deps.missing.forEach((dep)=>{ if (dep.pkg === 'eslint') { // pin to v9 to avoid breaking changes dep.pkg = 'eslint@^9'; } }); await (0, _installdependencies.installDependencies)(baseDir, deps.missing, true); } // Write default ESLint config. // Check for /pages and src/pages is to make sure this happens in Next.js folder if ([ 'app', 'src/app', 'pages', 'src/pages' ].some((dir)=>(0, _fs.existsSync)(_path.default.join(baseDir, dir)))) { await (0, _writeDefaultConfig.writeDefaultConfig)(baseDir, config, selectedConfig, eslintrcFile, pkgJsonPath, packageJsonConfig); } } _log.ready(`ESLint has successfully been configured. Run ${(0, _picocolors.bold)((0, _picocolors.cyan)('next lint'))} again to view warnings and errors.`); return null; } } } catch (err) { throw err; } } //# sourceMappingURL=runLintCheck.js.map