123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969 |
- /**
- * @fileoverview Helper functions for ESLint class
- * @author Nicholas C. Zakas
- */
- "use strict";
- //-----------------------------------------------------------------------------
- // Requirements
- //-----------------------------------------------------------------------------
- const path = require("node:path");
- const fs = require("node:fs");
- const fsp = fs.promises;
- const isGlob = require("is-glob");
- const hash = require("../cli-engine/hash");
- const minimatch = require("minimatch");
- const globParent = require("glob-parent");
- //-----------------------------------------------------------------------------
- // Fixup references
- //-----------------------------------------------------------------------------
- const Minimatch = minimatch.Minimatch;
- const MINIMATCH_OPTIONS = { dot: true };
- //-----------------------------------------------------------------------------
- // Types
- //-----------------------------------------------------------------------------
- /**
- * @typedef {Object} GlobSearch
- * @property {Array<string>} patterns The normalized patterns to use for a search.
- * @property {Array<string>} rawPatterns The patterns as entered by the user
- * before doing any normalization.
- */
- //-----------------------------------------------------------------------------
- // Errors
- //-----------------------------------------------------------------------------
- /**
- * The error type when no files match a glob.
- */
- class NoFilesFoundError extends Error {
- /**
- * @param {string} pattern The glob pattern which was not found.
- * @param {boolean} globEnabled If `false` then the pattern was a glob pattern, but glob was disabled.
- */
- constructor(pattern, globEnabled) {
- super(`No files matching '${pattern}' were found${!globEnabled ? " (glob was disabled)" : ""}.`);
- this.messageTemplate = "file-not-found";
- this.messageData = { pattern, globDisabled: !globEnabled };
- }
- }
- /**
- * The error type when a search fails to match multiple patterns.
- */
- class UnmatchedSearchPatternsError extends Error {
- /**
- * @param {Object} options The options for the error.
- * @param {string} options.basePath The directory that was searched.
- * @param {Array<string>} options.unmatchedPatterns The glob patterns
- * which were not found.
- * @param {Array<string>} options.patterns The glob patterns that were
- * searched.
- * @param {Array<string>} options.rawPatterns The raw glob patterns that
- * were searched.
- */
- constructor({ basePath, unmatchedPatterns, patterns, rawPatterns }) {
- super(`No files matching '${rawPatterns}' in '${basePath}' were found.`);
- this.basePath = basePath;
- this.unmatchedPatterns = unmatchedPatterns;
- this.patterns = patterns;
- this.rawPatterns = rawPatterns;
- }
- }
- /**
- * The error type when there are files matched by a glob, but all of them have been ignored.
- */
- class AllFilesIgnoredError extends Error {
- /**
- * @param {string} pattern The glob pattern which was not found.
- */
- constructor(pattern) {
- super(`All files matched by '${pattern}' are ignored.`);
- this.messageTemplate = "all-matched-files-ignored";
- this.messageData = { pattern };
- }
- }
- //-----------------------------------------------------------------------------
- // General Helpers
- //-----------------------------------------------------------------------------
- /**
- * Check if a given value is a non-empty string or not.
- * @param {any} value The value to check.
- * @returns {boolean} `true` if `value` is a non-empty string.
- */
- function isNonEmptyString(value) {
- return typeof value === "string" && value.trim() !== "";
- }
- /**
- * Check if a given value is an array of non-empty strings or not.
- * @param {any} value The value to check.
- * @returns {boolean} `true` if `value` is an array of non-empty strings.
- */
- function isArrayOfNonEmptyString(value) {
- return Array.isArray(value) && value.length && value.every(isNonEmptyString);
- }
- /**
- * Check if a given value is an empty array or an array of non-empty strings.
- * @param {any} value The value to check.
- * @returns {boolean} `true` if `value` is an empty array or an array of non-empty
- * strings.
- */
- function isEmptyArrayOrArrayOfNonEmptyString(value) {
- return Array.isArray(value) && value.every(isNonEmptyString);
- }
- //-----------------------------------------------------------------------------
- // File-related Helpers
- //-----------------------------------------------------------------------------
- /**
- * Normalizes slashes in a file pattern to posix-style.
- * @param {string} pattern The pattern to replace slashes in.
- * @returns {string} The pattern with slashes normalized.
- */
- function normalizeToPosix(pattern) {
- return pattern.replace(/\\/gu, "/");
- }
- /**
- * Check if a string is a glob pattern or not.
- * @param {string} pattern A glob pattern.
- * @returns {boolean} `true` if the string is a glob pattern.
- */
- function isGlobPattern(pattern) {
- return isGlob(path.sep === "\\" ? normalizeToPosix(pattern) : pattern);
- }
- /**
- * Determines if a given glob pattern will return any results.
- * Used primarily to help with useful error messages.
- * @param {Object} options The options for the function.
- * @param {string} options.basePath The directory to search.
- * @param {string} options.pattern A glob pattern to match.
- * @returns {Promise<boolean>} True if there is a glob match, false if not.
- */
- async function globMatch({ basePath, pattern }) {
- let found = false;
- const { hfs } = await import("@humanfs/node");
- const patternToUse = path.isAbsolute(pattern)
- ? normalizeToPosix(path.relative(basePath, pattern))
- : pattern;
- const matcher = new Minimatch(patternToUse, MINIMATCH_OPTIONS);
- const walkSettings = {
- directoryFilter(entry) {
- return !found && matcher.match(entry.path, true);
- },
- entryFilter(entry) {
- if (found || entry.isDirectory) {
- return false;
- }
- if (matcher.match(entry.path)) {
- found = true;
- return true;
- }
- return false;
- }
- };
- if (await hfs.isDirectory(basePath)) {
- return hfs.walk(basePath, walkSettings).next().then(() => found);
- }
- return found;
- }
- /**
- * Searches a directory looking for matching glob patterns. This uses
- * the config array's logic to determine if a directory or file should
- * be ignored, so it is consistent with how ignoring works throughout
- * ESLint.
- * @param {Object} options The options for this function.
- * @param {string} options.basePath The directory to search.
- * @param {Array<string>} options.patterns An array of glob patterns
- * to match.
- * @param {Array<string>} options.rawPatterns An array of glob patterns
- * as the user inputted them. Used for errors.
- * @param {ConfigLoader|LegacyConfigLoader} options.configLoader The config array to use for
- * determining what to ignore.
- * @param {boolean} options.errorOnUnmatchedPattern Determines if an error
- * should be thrown when a pattern is unmatched.
- * @returns {Promise<Array<string>>} An array of matching file paths
- * or an empty array if there are no matches.
- * @throws {UnmatchedSearchPatternsError} If there is a pattern that doesn't
- * match any files.
- */
- async function globSearch({
- basePath,
- patterns,
- rawPatterns,
- configLoader,
- errorOnUnmatchedPattern
- }) {
- if (patterns.length === 0) {
- return [];
- }
- /*
- * In this section we are converting the patterns into Minimatch
- * instances for performance reasons. Because we are doing the same
- * matches repeatedly, it's best to compile those patterns once and
- * reuse them multiple times.
- *
- * To do that, we convert any patterns with an absolute path into a
- * relative path and normalize it to Posix-style slashes. We also keep
- * track of the relative patterns to map them back to the original
- * patterns, which we need in order to throw an error if there are any
- * unmatched patterns.
- */
- const relativeToPatterns = new Map();
- const matchers = patterns.map((pattern, i) => {
- const patternToUse = path.isAbsolute(pattern)
- ? normalizeToPosix(path.relative(basePath, pattern))
- : pattern;
- relativeToPatterns.set(patternToUse, patterns[i]);
- return new Minimatch(patternToUse, MINIMATCH_OPTIONS);
- });
- /*
- * We track unmatched patterns because we may want to throw an error when
- * they occur. To start, this set is initialized with all of the patterns.
- * Every time a match occurs, the pattern is removed from the set, making
- * it easy to tell if we have any unmatched patterns left at the end of
- * search.
- */
- const unmatchedPatterns = new Set([...relativeToPatterns.keys()]);
- const { hfs } = await import("@humanfs/node");
- const walk = hfs.walk(
- basePath,
- {
- async directoryFilter(entry) {
- if (!matchers.some(matcher => matcher.match(entry.path, true))) {
- return false;
- }
- const absolutePath = path.resolve(basePath, entry.path);
- const configs = await configLoader.loadConfigArrayForDirectory(absolutePath);
- return !configs.isDirectoryIgnored(absolutePath);
- },
- async entryFilter(entry) {
- const absolutePath = path.resolve(basePath, entry.path);
- // entries may be directories or files so filter out directories
- if (entry.isDirectory) {
- return false;
- }
- const configs = await configLoader.loadConfigArrayForFile(absolutePath);
- const config = configs.getConfig(absolutePath);
- /*
- * Optimization: We need to track when patterns are left unmatched
- * and so we use `unmatchedPatterns` to do that. There is a bit of
- * complexity here because the same file can be matched by more than
- * one pattern. So, when we start, we actually need to test every
- * pattern against every file. Once we know there are no remaining
- * unmatched patterns, then we can switch to just looking for the
- * first matching pattern for improved speed.
- */
- const matchesPattern = unmatchedPatterns.size > 0
- ? matchers.reduce((previousValue, matcher) => {
- const pathMatches = matcher.match(entry.path);
- /*
- * We updated the unmatched patterns set only if the path
- * matches and the file has a config. If the file has no
- * config, that means there wasn't a match for the
- * pattern so it should not be removed.
- *
- * Performance note: `getConfig()` aggressively caches
- * results so there is no performance penalty for calling
- * it multiple times with the same argument.
- */
- if (pathMatches && config) {
- unmatchedPatterns.delete(matcher.pattern);
- }
- return pathMatches || previousValue;
- }, false)
- : matchers.some(matcher => matcher.match(entry.path));
- return matchesPattern && config !== void 0;
- }
- }
- );
- const filePaths = [];
- if (await hfs.isDirectory(basePath)) {
- for await (const entry of walk) {
- filePaths.push(path.resolve(basePath, entry.path));
- }
- }
- // now check to see if we have any unmatched patterns
- if (errorOnUnmatchedPattern && unmatchedPatterns.size > 0) {
- throw new UnmatchedSearchPatternsError({
- basePath,
- unmatchedPatterns: [...unmatchedPatterns].map(
- pattern => relativeToPatterns.get(pattern)
- ),
- patterns,
- rawPatterns
- });
- }
- return filePaths;
- }
- /**
- * Throws an error for unmatched patterns. The error will only contain information about the first one.
- * Checks to see if there are any ignored results for a given search.
- * @param {Object} options The options for this function.
- * @param {string} options.basePath The directory to search.
- * @param {Array<string>} options.patterns An array of glob patterns
- * that were used in the original search.
- * @param {Array<string>} options.rawPatterns An array of glob patterns
- * as the user inputted them. Used for errors.
- * @param {Array<string>} options.unmatchedPatterns A non-empty array of glob patterns
- * that were unmatched in the original search.
- * @returns {void} Always throws an error.
- * @throws {NoFilesFoundError} If the first unmatched pattern
- * doesn't match any files even when there are no ignores.
- * @throws {AllFilesIgnoredError} If the first unmatched pattern
- * matches some files when there are no ignores.
- */
- async function throwErrorForUnmatchedPatterns({
- basePath,
- patterns,
- rawPatterns,
- unmatchedPatterns
- }) {
- const pattern = unmatchedPatterns[0];
- const rawPattern = rawPatterns[patterns.indexOf(pattern)];
- const patternHasMatch = await globMatch({
- basePath,
- pattern
- });
- if (patternHasMatch) {
- throw new AllFilesIgnoredError(rawPattern);
- }
- // if we get here there are truly no matches
- throw new NoFilesFoundError(rawPattern, true);
- }
- /**
- * Performs multiple glob searches in parallel.
- * @param {Object} options The options for this function.
- * @param {Map<string,GlobSearch>} options.searches
- * An array of glob patterns to match.
- * @param {ConfigLoader|LegacyConfigLoader} options.configLoader The config loader to use for
- * determining what to ignore.
- * @param {boolean} options.errorOnUnmatchedPattern Determines if an
- * unmatched glob pattern should throw an error.
- * @returns {Promise<Array<string>>} An array of matching file paths
- * or an empty array if there are no matches.
- */
- async function globMultiSearch({ searches, configLoader, errorOnUnmatchedPattern }) {
- /*
- * For convenience, we normalized the search map into an array of objects.
- * Next, we filter out all searches that have no patterns. This happens
- * primarily for the cwd, which is prepopulated in the searches map as an
- * optimization. However, if it has no patterns, it means all patterns
- * occur outside of the cwd and we can safely filter out that search.
- */
- const normalizedSearches = [...searches].map(
- ([basePath, { patterns, rawPatterns }]) => ({ basePath, patterns, rawPatterns })
- ).filter(({ patterns }) => patterns.length > 0);
- const results = await Promise.allSettled(
- normalizedSearches.map(
- ({ basePath, patterns, rawPatterns }) => globSearch({
- basePath,
- patterns,
- rawPatterns,
- configLoader,
- errorOnUnmatchedPattern
- })
- )
- );
- const filePaths = [];
- for (let i = 0; i < results.length; i++) {
- const result = results[i];
- const currentSearch = normalizedSearches[i];
- if (result.status === "fulfilled") {
- // if the search was successful just add the results
- if (result.value.length > 0) {
- filePaths.push(...result.value);
- }
- continue;
- }
- // if we make it here then there was an error
- const error = result.reason;
- // unexpected errors should be re-thrown
- if (!error.basePath) {
- throw error;
- }
- if (errorOnUnmatchedPattern) {
- await throwErrorForUnmatchedPatterns({
- ...currentSearch,
- unmatchedPatterns: error.unmatchedPatterns
- });
- }
- }
- return filePaths;
- }
- /**
- * Finds all files matching the options specified.
- * @param {Object} args The arguments objects.
- * @param {Array<string>} args.patterns An array of glob patterns.
- * @param {boolean} args.globInputPaths true to interpret glob patterns,
- * false to not interpret glob patterns.
- * @param {string} args.cwd The current working directory to find from.
- * @param {ConfigLoader|LegacyConfigLoader} args.configLoader The config loeader for the current run.
- * @param {boolean} args.errorOnUnmatchedPattern Determines if an unmatched pattern
- * should throw an error.
- * @returns {Promise<Array<string>>} The fully resolved file paths.
- * @throws {AllFilesIgnoredError} If there are no results due to an ignore pattern.
- * @throws {NoFilesFoundError} If no files matched the given patterns.
- */
- async function findFiles({
- patterns,
- globInputPaths,
- cwd,
- configLoader,
- errorOnUnmatchedPattern
- }) {
- const results = [];
- const missingPatterns = [];
- let globbyPatterns = [];
- let rawPatterns = [];
- const searches = new Map([[cwd, { patterns: globbyPatterns, rawPatterns: [] }]]);
- /*
- * This part is a bit involved because we need to account for
- * the different ways that the patterns can match directories.
- * For each different way, we need to decide if we should look
- * for a config file or just use the default config. (Directories
- * without a config file always use the default config.)
- *
- * Here are the cases:
- *
- * 1. A directory is passed directly (e.g., "subdir"). In this case, we
- * can assume that the user intends to lint this directory and we should
- * not look for a config file in the parent directory, because the only
- * reason to do that would be to ignore this directory (which we already
- * know we don't want to do). Instead, we use the default config until we
- * get to the directory that was passed, at which point we start looking
- * for config files again.
- *
- * 2. A dot (".") or star ("*"). In this case, we want to read
- * the config file in the current directory because the user is
- * explicitly asking to lint the current directory. Note that "."
- * will traverse into subdirectories while "*" will not.
- *
- * 3. A directory is passed in the form of "subdir/subsubdir".
- * In this case, we don't want to look for a config file in the
- * parent directory ("subdir"). We can skip looking for a config
- * file until `entry.depth` is greater than 1 because there's no
- * way that the pattern can match `entry.path` yet.
- *
- * 4. A directory glob pattern is passed (e.g., "subd*"). We want
- * this case to act like case 2 because it's unclear whether or not
- * any particular directory is meant to be traversed.
- *
- * 5. A recursive glob pattern is passed (e.g., "**"). We want this
- * case to act like case 2.
- */
- // check to see if we have explicit files and directories
- const filePaths = patterns.map(filePath => path.resolve(cwd, filePath));
- const stats = await Promise.all(
- filePaths.map(
- filePath => fsp.stat(filePath).catch(() => { })
- )
- );
- stats.forEach((stat, index) => {
- const filePath = filePaths[index];
- const pattern = normalizeToPosix(patterns[index]);
- if (stat) {
- // files are added directly to the list
- if (stat.isFile()) {
- results.push(filePath);
- }
- // directories need extensions attached
- if (stat.isDirectory()) {
- if (!searches.has(filePath)) {
- searches.set(filePath, { patterns: [], rawPatterns: [] });
- }
- ({ patterns: globbyPatterns, rawPatterns } = searches.get(filePath));
- globbyPatterns.push(`${normalizeToPosix(filePath)}/**`);
- rawPatterns.push(pattern);
- }
- return;
- }
- // save patterns for later use based on whether globs are enabled
- if (globInputPaths && isGlobPattern(pattern)) {
- /*
- * We are grouping patterns by their glob parent. This is done to
- * make it easier to determine when a config file should be loaded.
- */
- const basePath = path.resolve(cwd, globParent(pattern));
- if (!searches.has(basePath)) {
- searches.set(basePath, { patterns: [], rawPatterns: [] });
- }
- ({ patterns: globbyPatterns, rawPatterns } = searches.get(basePath));
- globbyPatterns.push(filePath);
- rawPatterns.push(pattern);
- } else {
- missingPatterns.push(pattern);
- }
- });
- // there were patterns that didn't match anything, tell the user
- if (errorOnUnmatchedPattern && missingPatterns.length) {
- throw new NoFilesFoundError(missingPatterns[0], globInputPaths);
- }
- // now we are safe to do the search
- const globbyResults = await globMultiSearch({
- searches,
- configLoader,
- errorOnUnmatchedPattern
- });
- return [
- ...new Set([
- ...results,
- ...globbyResults.map(filePath => path.resolve(filePath))
- ])
- ];
- }
- //-----------------------------------------------------------------------------
- // Results-related Helpers
- //-----------------------------------------------------------------------------
- /**
- * Checks if the given message is an error message.
- * @param {LintMessage} message The message to check.
- * @returns {boolean} Whether or not the message is an error message.
- * @private
- */
- function isErrorMessage(message) {
- return message.severity === 2;
- }
- /**
- * Returns result with warning by ignore settings
- * @param {string} filePath File path of checked code
- * @param {string} baseDir Absolute path of base directory
- * @param {"ignored"|"external"|"unconfigured"} configStatus A status that determines why the file is ignored
- * @returns {LintResult} Result with single warning
- * @private
- */
- function createIgnoreResult(filePath, baseDir, configStatus) {
- let message;
- switch (configStatus) {
- case "external":
- message = "File ignored because outside of base path.";
- break;
- case "unconfigured":
- message = "File ignored because no matching configuration was supplied.";
- break;
- default:
- {
- const isInNodeModules = baseDir && path.dirname(path.relative(baseDir, filePath)).split(path.sep).includes("node_modules");
- if (isInNodeModules) {
- message = "File ignored by default because it is located under the node_modules directory. Use ignore pattern \"!**/node_modules/\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
- } else {
- message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to disable file ignore settings or use \"--no-warn-ignored\" to suppress this warning.";
- }
- }
- break;
- }
- return {
- filePath: path.resolve(filePath),
- messages: [
- {
- ruleId: null,
- fatal: false,
- severity: 1,
- message,
- nodeType: null
- }
- ],
- suppressedMessages: [],
- errorCount: 0,
- warningCount: 1,
- fatalErrorCount: 0,
- fixableErrorCount: 0,
- fixableWarningCount: 0
- };
- }
- //-----------------------------------------------------------------------------
- // Options-related Helpers
- //-----------------------------------------------------------------------------
- /**
- * Check if a given value is a valid fix type or not.
- * @param {any} x The value to check.
- * @returns {boolean} `true` if `x` is valid fix type.
- */
- function isFixType(x) {
- return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
- }
- /**
- * Check if a given value is an array of fix types or not.
- * @param {any} x The value to check.
- * @returns {boolean} `true` if `x` is an array of fix types.
- */
- function isFixTypeArray(x) {
- return Array.isArray(x) && x.every(isFixType);
- }
- /**
- * The error for invalid options.
- */
- class ESLintInvalidOptionsError extends Error {
- constructor(messages) {
- super(`Invalid Options:\n- ${messages.join("\n- ")}`);
- this.code = "ESLINT_INVALID_OPTIONS";
- Error.captureStackTrace(this, ESLintInvalidOptionsError);
- }
- }
- /**
- * Validates and normalizes options for the wrapped CLIEngine instance.
- * @param {ESLintOptions} options The options to process.
- * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
- * @returns {ESLintOptions} The normalized options.
- */
- function processOptions({
- allowInlineConfig = true, // ← we cannot use `overrideConfig.noInlineConfig` instead because `allowInlineConfig` has side-effect that suppress warnings that show inline configs are ignored.
- baseConfig = null,
- cache = false,
- cacheLocation = ".eslintcache",
- cacheStrategy = "metadata",
- cwd = process.cwd(),
- errorOnUnmatchedPattern = true,
- fix = false,
- fixTypes = null, // ← should be null by default because if it's an array then it suppresses rules that don't have the `meta.type` property.
- flags = [],
- globInputPaths = true,
- ignore = true,
- ignorePatterns = null,
- overrideConfig = null,
- overrideConfigFile = null,
- plugins = {},
- stats = false,
- warnIgnored = true,
- passOnNoPatterns = false,
- ruleFilter = () => true,
- ...unknownOptions
- }) {
- const errors = [];
- const unknownOptionKeys = Object.keys(unknownOptions);
- if (unknownOptionKeys.length >= 1) {
- errors.push(`Unknown options: ${unknownOptionKeys.join(", ")}`);
- if (unknownOptionKeys.includes("cacheFile")) {
- errors.push("'cacheFile' has been removed. Please use the 'cacheLocation' option instead.");
- }
- if (unknownOptionKeys.includes("configFile")) {
- errors.push("'configFile' has been removed. Please use the 'overrideConfigFile' option instead.");
- }
- if (unknownOptionKeys.includes("envs")) {
- errors.push("'envs' has been removed.");
- }
- if (unknownOptionKeys.includes("extensions")) {
- errors.push("'extensions' has been removed.");
- }
- if (unknownOptionKeys.includes("resolvePluginsRelativeTo")) {
- errors.push("'resolvePluginsRelativeTo' has been removed.");
- }
- if (unknownOptionKeys.includes("globals")) {
- errors.push("'globals' has been removed. Please use the 'overrideConfig.languageOptions.globals' option instead.");
- }
- if (unknownOptionKeys.includes("ignorePath")) {
- errors.push("'ignorePath' has been removed.");
- }
- if (unknownOptionKeys.includes("ignorePattern")) {
- errors.push("'ignorePattern' has been removed. Please use the 'overrideConfig.ignorePatterns' option instead.");
- }
- if (unknownOptionKeys.includes("parser")) {
- errors.push("'parser' has been removed. Please use the 'overrideConfig.languageOptions.parser' option instead.");
- }
- if (unknownOptionKeys.includes("parserOptions")) {
- errors.push("'parserOptions' has been removed. Please use the 'overrideConfig.languageOptions.parserOptions' option instead.");
- }
- if (unknownOptionKeys.includes("rules")) {
- errors.push("'rules' has been removed. Please use the 'overrideConfig.rules' option instead.");
- }
- if (unknownOptionKeys.includes("rulePaths")) {
- errors.push("'rulePaths' has been removed. Please define your rules using plugins.");
- }
- if (unknownOptionKeys.includes("reportUnusedDisableDirectives")) {
- errors.push("'reportUnusedDisableDirectives' has been removed. Please use the 'overrideConfig.linterOptions.reportUnusedDisableDirectives' option instead.");
- }
- }
- if (typeof allowInlineConfig !== "boolean") {
- errors.push("'allowInlineConfig' must be a boolean.");
- }
- if (typeof baseConfig !== "object") {
- errors.push("'baseConfig' must be an object or null.");
- }
- if (typeof cache !== "boolean") {
- errors.push("'cache' must be a boolean.");
- }
- if (!isNonEmptyString(cacheLocation)) {
- errors.push("'cacheLocation' must be a non-empty string.");
- }
- if (
- cacheStrategy !== "metadata" &&
- cacheStrategy !== "content"
- ) {
- errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
- }
- if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
- errors.push("'cwd' must be an absolute path.");
- }
- if (typeof errorOnUnmatchedPattern !== "boolean") {
- errors.push("'errorOnUnmatchedPattern' must be a boolean.");
- }
- if (typeof fix !== "boolean" && typeof fix !== "function") {
- errors.push("'fix' must be a boolean or a function.");
- }
- if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
- errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
- }
- if (!isEmptyArrayOrArrayOfNonEmptyString(flags)) {
- errors.push("'flags' must be an array of non-empty strings.");
- }
- if (typeof globInputPaths !== "boolean") {
- errors.push("'globInputPaths' must be a boolean.");
- }
- if (typeof ignore !== "boolean") {
- errors.push("'ignore' must be a boolean.");
- }
- if (!isEmptyArrayOrArrayOfNonEmptyString(ignorePatterns) && ignorePatterns !== null) {
- errors.push("'ignorePatterns' must be an array of non-empty strings or null.");
- }
- if (typeof overrideConfig !== "object") {
- errors.push("'overrideConfig' must be an object or null.");
- }
- if (!isNonEmptyString(overrideConfigFile) && overrideConfigFile !== null && overrideConfigFile !== true) {
- errors.push("'overrideConfigFile' must be a non-empty string, null, or true.");
- }
- if (typeof passOnNoPatterns !== "boolean") {
- errors.push("'passOnNoPatterns' must be a boolean.");
- }
- if (typeof plugins !== "object") {
- errors.push("'plugins' must be an object or null.");
- } else if (plugins !== null && Object.keys(plugins).includes("")) {
- errors.push("'plugins' must not include an empty string.");
- }
- if (Array.isArray(plugins)) {
- errors.push("'plugins' doesn't add plugins to configuration to load. Please use the 'overrideConfig.plugins' option instead.");
- }
- if (typeof stats !== "boolean") {
- errors.push("'stats' must be a boolean.");
- }
- if (typeof warnIgnored !== "boolean") {
- errors.push("'warnIgnored' must be a boolean.");
- }
- if (typeof ruleFilter !== "function") {
- errors.push("'ruleFilter' must be a function.");
- }
- if (errors.length > 0) {
- throw new ESLintInvalidOptionsError(errors);
- }
- return {
- allowInlineConfig,
- baseConfig,
- cache,
- cacheLocation,
- cacheStrategy,
- // when overrideConfigFile is true that means don't do config file lookup
- configFile: overrideConfigFile === true ? false : overrideConfigFile,
- overrideConfig,
- cwd: path.normalize(cwd),
- errorOnUnmatchedPattern,
- fix,
- fixTypes,
- flags: [...flags],
- globInputPaths,
- ignore,
- ignorePatterns,
- stats,
- passOnNoPatterns,
- warnIgnored,
- ruleFilter
- };
- }
- //-----------------------------------------------------------------------------
- // Cache-related helpers
- //-----------------------------------------------------------------------------
- /**
- * return the cacheFile to be used by eslint, based on whether the provided parameter is
- * a directory or looks like a directory (ends in `path.sep`), in which case the file
- * name will be the `cacheFile/.cache_hashOfCWD`
- *
- * if cacheFile points to a file or looks like a file then in will just use that file
- * @param {string} cacheFile The name of file to be used to store the cache
- * @param {string} cwd Current working directory
- * @returns {string} the resolved path to the cache file
- */
- function getCacheFile(cacheFile, cwd) {
- /*
- * make sure the path separators are normalized for the environment/os
- * keeping the trailing path separator if present
- */
- const normalizedCacheFile = path.normalize(cacheFile);
- const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
- const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
- /**
- * return the name for the cache file in case the provided parameter is a directory
- * @returns {string} the resolved path to the cacheFile
- */
- function getCacheFileForDirectory() {
- return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
- }
- let fileStats;
- try {
- fileStats = fs.lstatSync(resolvedCacheFile);
- } catch {
- fileStats = null;
- }
- /*
- * in case the file exists we need to verify if the provided path
- * is a directory or a file. If it is a directory we want to create a file
- * inside that directory
- */
- if (fileStats) {
- /*
- * is a directory or is a file, but the original file the user provided
- * looks like a directory but `path.resolve` removed the `last path.sep`
- * so we need to still treat this like a directory
- */
- if (fileStats.isDirectory() || looksLikeADirectory) {
- return getCacheFileForDirectory();
- }
- // is file so just use that file
- return resolvedCacheFile;
- }
- /*
- * here we known the file or directory doesn't exist,
- * so we will try to infer if its a directory if it looks like a directory
- * for the current operating system.
- */
- // if the last character passed is a path separator we assume is a directory
- if (looksLikeADirectory) {
- return getCacheFileForDirectory();
- }
- return resolvedCacheFile;
- }
- //-----------------------------------------------------------------------------
- // Exports
- //-----------------------------------------------------------------------------
- module.exports = {
- findFiles,
- isNonEmptyString,
- isArrayOfNonEmptyString,
- createIgnoreResult,
- isErrorMessage,
- processOptions,
- getCacheFile
- };
|