• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/**
2 * @fileoverview Main CLI object.
3 * @author Nicholas C. Zakas
4 */
5
6"use strict";
7
8/*
9 * The CLI object should *not* call process.exit() directly. It should only return
10 * exit codes. This allows other programs to use the CLI object and still control
11 * when the program exits.
12 */
13
14//------------------------------------------------------------------------------
15// Requirements
16//------------------------------------------------------------------------------
17
18const fs = require("fs");
19const path = require("path");
20const defaultOptions = require("../../conf/default-cli-options");
21const pkg = require("../../package.json");
22const ConfigOps = require("@eslint/eslintrc/lib/shared/config-ops");
23const naming = require("@eslint/eslintrc/lib/shared/naming");
24const ModuleResolver = require("../shared/relative-module-resolver");
25const { Linter } = require("../linter");
26const builtInRules = require("../rules");
27const { CascadingConfigArrayFactory } = require("./cascading-config-array-factory");
28const { IgnorePattern, getUsedExtractedConfigs } = require("./config-array");
29const { FileEnumerator } = require("./file-enumerator");
30const hash = require("./hash");
31const LintResultCache = require("./lint-result-cache");
32
33const debug = require("debug")("eslint:cli-engine");
34const validFixTypes = new Set(["problem", "suggestion", "layout"]);
35
36//------------------------------------------------------------------------------
37// Typedefs
38//------------------------------------------------------------------------------
39
40// For VSCode IntelliSense
41/** @typedef {import("../shared/types").ConfigData} ConfigData */
42/** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
43/** @typedef {import("../shared/types").LintMessage} LintMessage */
44/** @typedef {import("../shared/types").ParserOptions} ParserOptions */
45/** @typedef {import("../shared/types").Plugin} Plugin */
46/** @typedef {import("../shared/types").RuleConf} RuleConf */
47/** @typedef {import("../shared/types").Rule} Rule */
48/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
49/** @typedef {ReturnType<ConfigArray["extractConfig"]>} ExtractedConfig */
50
51/**
52 * The options to configure a CLI engine with.
53 * @typedef {Object} CLIEngineOptions
54 * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
55 * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this CLIEngine instance
56 * @property {boolean} [cache] Enable result caching.
57 * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
58 * @property {string} [configFile] The configuration file to use.
59 * @property {string} [cwd] The value to use for the current working directory.
60 * @property {string[]} [envs] An array of environments to load.
61 * @property {string[]|null} [extensions] An array of file extensions to check.
62 * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
63 * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
64 * @property {string[]} [globals] An array of global variables to declare.
65 * @property {boolean} [ignore] False disables use of .eslintignore.
66 * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
67 * @property {string|string[]} [ignorePattern] One or more glob patterns to ignore.
68 * @property {boolean} [useEslintrc] False disables looking for .eslintrc
69 * @property {string} [parser] The name of the parser to use.
70 * @property {ParserOptions} [parserOptions] An object of parserOption settings to use.
71 * @property {string[]} [plugins] An array of plugins to load.
72 * @property {Record<string,RuleConf>} [rules] An object of rules to use.
73 * @property {string[]} [rulePaths] An array of directories to load custom rules from.
74 * @property {boolean} [reportUnusedDisableDirectives] `true` adds reports for unused eslint-disable directives
75 * @property {boolean} [globInputPaths] Set to false to skip glob resolution of input file paths to lint (default: true). If false, each input file paths is assumed to be a non-glob path to an existing file.
76 * @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD
77 */
78
79/**
80 * A linting result.
81 * @typedef {Object} LintResult
82 * @property {string} filePath The path to the file that was linted.
83 * @property {LintMessage[]} messages All of the messages for the result.
84 * @property {number} errorCount Number of errors for the result.
85 * @property {number} warningCount Number of warnings for the result.
86 * @property {number} fixableErrorCount Number of fixable errors for the result.
87 * @property {number} fixableWarningCount Number of fixable warnings for the result.
88 * @property {string} [source] The source code of the file that was linted.
89 * @property {string} [output] The source code of the file that was linted, with as many fixes applied as possible.
90 */
91
92/**
93 * Linting results.
94 * @typedef {Object} LintReport
95 * @property {LintResult[]} results All of the result.
96 * @property {number} errorCount Number of errors for the result.
97 * @property {number} warningCount Number of warnings for the result.
98 * @property {number} fixableErrorCount Number of fixable errors for the result.
99 * @property {number} fixableWarningCount Number of fixable warnings for the result.
100 * @property {DeprecatedRuleInfo[]} usedDeprecatedRules The list of used deprecated rules.
101 */
102
103/**
104 * Private data for CLIEngine.
105 * @typedef {Object} CLIEngineInternalSlots
106 * @property {Map<string, Plugin>} additionalPluginPool The map for additional plugins.
107 * @property {string} cacheFilePath The path to the cache of lint results.
108 * @property {CascadingConfigArrayFactory} configArrayFactory The factory of configs.
109 * @property {(filePath: string) => boolean} defaultIgnores The default predicate function to check if a file ignored or not.
110 * @property {FileEnumerator} fileEnumerator The file enumerator.
111 * @property {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
112 * @property {LintResultCache|null} lintResultCache The cache of lint results.
113 * @property {Linter} linter The linter instance which has loaded rules.
114 * @property {CLIEngineOptions} options The normalized options of this instance.
115 */
116
117//------------------------------------------------------------------------------
118// Helpers
119//------------------------------------------------------------------------------
120
121/** @type {WeakMap<CLIEngine, CLIEngineInternalSlots>} */
122const internalSlotsMap = new WeakMap();
123
124/**
125 * Determines if each fix type in an array is supported by ESLint and throws
126 * an error if not.
127 * @param {string[]} fixTypes An array of fix types to check.
128 * @returns {void}
129 * @throws {Error} If an invalid fix type is found.
130 */
131function validateFixTypes(fixTypes) {
132    for (const fixType of fixTypes) {
133        if (!validFixTypes.has(fixType)) {
134            throw new Error(`Invalid fix type "${fixType}" found.`);
135        }
136    }
137}
138
139/**
140 * It will calculate the error and warning count for collection of messages per file
141 * @param {LintMessage[]} messages Collection of messages
142 * @returns {Object} Contains the stats
143 * @private
144 */
145function calculateStatsPerFile(messages) {
146    return messages.reduce((stat, message) => {
147        if (message.fatal || message.severity === 2) {
148            stat.errorCount++;
149            if (message.fix) {
150                stat.fixableErrorCount++;
151            }
152        } else {
153            stat.warningCount++;
154            if (message.fix) {
155                stat.fixableWarningCount++;
156            }
157        }
158        return stat;
159    }, {
160        errorCount: 0,
161        warningCount: 0,
162        fixableErrorCount: 0,
163        fixableWarningCount: 0
164    });
165}
166
167/**
168 * It will calculate the error and warning count for collection of results from all files
169 * @param {LintResult[]} results Collection of messages from all the files
170 * @returns {Object} Contains the stats
171 * @private
172 */
173function calculateStatsPerRun(results) {
174    return results.reduce((stat, result) => {
175        stat.errorCount += result.errorCount;
176        stat.warningCount += result.warningCount;
177        stat.fixableErrorCount += result.fixableErrorCount;
178        stat.fixableWarningCount += result.fixableWarningCount;
179        return stat;
180    }, {
181        errorCount: 0,
182        warningCount: 0,
183        fixableErrorCount: 0,
184        fixableWarningCount: 0
185    });
186}
187
188/**
189 * Processes an source code using ESLint.
190 * @param {Object} config The config object.
191 * @param {string} config.text The source code to verify.
192 * @param {string} config.cwd The path to the current working directory.
193 * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
194 * @param {ConfigArray} config.config The config.
195 * @param {boolean} config.fix If `true` then it does fix.
196 * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
197 * @param {boolean} config.reportUnusedDisableDirectives If `true` then it reports unused `eslint-disable` comments.
198 * @param {FileEnumerator} config.fileEnumerator The file enumerator to check if a path is a target or not.
199 * @param {Linter} config.linter The linter instance to verify.
200 * @returns {LintResult} The result of linting.
201 * @private
202 */
203function verifyText({
204    text,
205    cwd,
206    filePath: providedFilePath,
207    config,
208    fix,
209    allowInlineConfig,
210    reportUnusedDisableDirectives,
211    fileEnumerator,
212    linter
213}) {
214    const filePath = providedFilePath || "<text>";
215
216    debug(`Lint ${filePath}`);
217
218    /*
219     * Verify.
220     * `config.extractConfig(filePath)` requires an absolute path, but `linter`
221     * doesn't know CWD, so it gives `linter` an absolute path always.
222     */
223    const filePathToVerify = filePath === "<text>" ? path.join(cwd, filePath) : filePath;
224    const { fixed, messages, output } = linter.verifyAndFix(
225        text,
226        config,
227        {
228            allowInlineConfig,
229            filename: filePathToVerify,
230            fix,
231            reportUnusedDisableDirectives,
232
233            /**
234             * Check if the linter should adopt a given code block or not.
235             * @param {string} blockFilename The virtual filename of a code block.
236             * @returns {boolean} `true` if the linter should adopt the code block.
237             */
238            filterCodeBlock(blockFilename) {
239                return fileEnumerator.isTargetPath(blockFilename);
240            }
241        }
242    );
243
244    // Tweak and return.
245    const result = {
246        filePath,
247        messages,
248        ...calculateStatsPerFile(messages)
249    };
250
251    if (fixed) {
252        result.output = output;
253    }
254    if (
255        result.errorCount + result.warningCount > 0 &&
256        typeof result.output === "undefined"
257    ) {
258        result.source = text;
259    }
260
261    return result;
262}
263
264/**
265 * Returns result with warning by ignore settings
266 * @param {string} filePath File path of checked code
267 * @param {string} baseDir  Absolute path of base directory
268 * @returns {LintResult} Result with single warning
269 * @private
270 */
271function createIgnoreResult(filePath, baseDir) {
272    let message;
273    const isHidden = filePath.split(path.sep)
274        .find(segment => /^\./u.test(segment));
275    const isInNodeModules = baseDir && path.relative(baseDir, filePath).startsWith("node_modules");
276
277    if (isHidden) {
278        message = "File ignored by default.  Use a negated ignore pattern (like \"--ignore-pattern '!<relative/path/to/filename>'\") to override.";
279    } else if (isInNodeModules) {
280        message = "File ignored by default. Use \"--ignore-pattern '!node_modules/*'\" to override.";
281    } else {
282        message = "File ignored because of a matching ignore pattern. Use \"--no-ignore\" to override.";
283    }
284
285    return {
286        filePath: path.resolve(filePath),
287        messages: [
288            {
289                fatal: false,
290                severity: 1,
291                message
292            }
293        ],
294        errorCount: 0,
295        warningCount: 1,
296        fixableErrorCount: 0,
297        fixableWarningCount: 0
298    };
299}
300
301/**
302 * Get a rule.
303 * @param {string} ruleId The rule ID to get.
304 * @param {ConfigArray[]} configArrays The config arrays that have plugin rules.
305 * @returns {Rule|null} The rule or null.
306 */
307function getRule(ruleId, configArrays) {
308    for (const configArray of configArrays) {
309        const rule = configArray.pluginRules.get(ruleId);
310
311        if (rule) {
312            return rule;
313        }
314    }
315    return builtInRules.get(ruleId) || null;
316}
317
318/**
319 * Collect used deprecated rules.
320 * @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
321 * @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules.
322 */
323function *iterateRuleDeprecationWarnings(usedConfigArrays) {
324    const processedRuleIds = new Set();
325
326    // Flatten used configs.
327    /** @type {ExtractedConfig[]} */
328    const configs = [].concat(
329        ...usedConfigArrays.map(getUsedExtractedConfigs)
330    );
331
332    // Traverse rule configs.
333    for (const config of configs) {
334        for (const [ruleId, ruleConfig] of Object.entries(config.rules)) {
335
336            // Skip if it was processed.
337            if (processedRuleIds.has(ruleId)) {
338                continue;
339            }
340            processedRuleIds.add(ruleId);
341
342            // Skip if it's not used.
343            if (!ConfigOps.getRuleSeverity(ruleConfig)) {
344                continue;
345            }
346            const rule = getRule(ruleId, usedConfigArrays);
347
348            // Skip if it's not deprecated.
349            if (!(rule && rule.meta && rule.meta.deprecated)) {
350                continue;
351            }
352
353            // This rule was used and deprecated.
354            yield {
355                ruleId,
356                replacedBy: rule.meta.replacedBy || []
357            };
358        }
359    }
360}
361
362/**
363 * Checks if the given message is an error message.
364 * @param {LintMessage} message The message to check.
365 * @returns {boolean} Whether or not the message is an error message.
366 * @private
367 */
368function isErrorMessage(message) {
369    return message.severity === 2;
370}
371
372
373/**
374 * return the cacheFile to be used by eslint, based on whether the provided parameter is
375 * a directory or looks like a directory (ends in `path.sep`), in which case the file
376 * name will be the `cacheFile/.cache_hashOfCWD`
377 *
378 * if cacheFile points to a file or looks like a file then in will just use that file
379 * @param {string} cacheFile The name of file to be used to store the cache
380 * @param {string} cwd Current working directory
381 * @returns {string} the resolved path to the cache file
382 */
383function getCacheFile(cacheFile, cwd) {
384
385    /*
386     * make sure the path separators are normalized for the environment/os
387     * keeping the trailing path separator if present
388     */
389    const normalizedCacheFile = path.normalize(cacheFile);
390
391    const resolvedCacheFile = path.resolve(cwd, normalizedCacheFile);
392    const looksLikeADirectory = normalizedCacheFile.slice(-1) === path.sep;
393
394    /**
395     * return the name for the cache file in case the provided parameter is a directory
396     * @returns {string} the resolved path to the cacheFile
397     */
398    function getCacheFileForDirectory() {
399        return path.join(resolvedCacheFile, `.cache_${hash(cwd)}`);
400    }
401
402    let fileStats;
403
404    try {
405        fileStats = fs.lstatSync(resolvedCacheFile);
406    } catch {
407        fileStats = null;
408    }
409
410
411    /*
412     * in case the file exists we need to verify if the provided path
413     * is a directory or a file. If it is a directory we want to create a file
414     * inside that directory
415     */
416    if (fileStats) {
417
418        /*
419         * is a directory or is a file, but the original file the user provided
420         * looks like a directory but `path.resolve` removed the `last path.sep`
421         * so we need to still treat this like a directory
422         */
423        if (fileStats.isDirectory() || looksLikeADirectory) {
424            return getCacheFileForDirectory();
425        }
426
427        // is file so just use that file
428        return resolvedCacheFile;
429    }
430
431    /*
432     * here we known the file or directory doesn't exist,
433     * so we will try to infer if its a directory if it looks like a directory
434     * for the current operating system.
435     */
436
437    // if the last character passed is a path separator we assume is a directory
438    if (looksLikeADirectory) {
439        return getCacheFileForDirectory();
440    }
441
442    return resolvedCacheFile;
443}
444
445/**
446 * Convert a string array to a boolean map.
447 * @param {string[]|null} keys The keys to assign true.
448 * @param {boolean} defaultValue The default value for each property.
449 * @param {string} displayName The property name which is used in error message.
450 * @returns {Record<string,boolean>} The boolean map.
451 */
452function toBooleanMap(keys, defaultValue, displayName) {
453    if (keys && !Array.isArray(keys)) {
454        throw new Error(`${displayName} must be an array.`);
455    }
456    if (keys && keys.length > 0) {
457        return keys.reduce((map, def) => {
458            const [key, value] = def.split(":");
459
460            if (key !== "__proto__") {
461                map[key] = value === void 0
462                    ? defaultValue
463                    : value === "true";
464            }
465
466            return map;
467        }, {});
468    }
469    return void 0;
470}
471
472/**
473 * Create a config data from CLI options.
474 * @param {CLIEngineOptions} options The options
475 * @returns {ConfigData|null} The created config data.
476 */
477function createConfigDataFromOptions(options) {
478    const {
479        ignorePattern,
480        parser,
481        parserOptions,
482        plugins,
483        rules
484    } = options;
485    const env = toBooleanMap(options.envs, true, "envs");
486    const globals = toBooleanMap(options.globals, false, "globals");
487
488    if (
489        env === void 0 &&
490        globals === void 0 &&
491        (ignorePattern === void 0 || ignorePattern.length === 0) &&
492        parser === void 0 &&
493        parserOptions === void 0 &&
494        plugins === void 0 &&
495        rules === void 0
496    ) {
497        return null;
498    }
499    return {
500        env,
501        globals,
502        ignorePatterns: ignorePattern,
503        parser,
504        parserOptions,
505        plugins,
506        rules
507    };
508}
509
510/**
511 * Checks whether a directory exists at the given location
512 * @param {string} resolvedPath A path from the CWD
513 * @returns {boolean} `true` if a directory exists
514 */
515function directoryExists(resolvedPath) {
516    try {
517        return fs.statSync(resolvedPath).isDirectory();
518    } catch (error) {
519        if (error && error.code === "ENOENT") {
520            return false;
521        }
522        throw error;
523    }
524}
525
526//------------------------------------------------------------------------------
527// Public Interface
528//------------------------------------------------------------------------------
529
530class CLIEngine {
531
532    /**
533     * Creates a new instance of the core CLI engine.
534     * @param {CLIEngineOptions} providedOptions The options for this instance.
535     */
536    constructor(providedOptions) {
537        const options = Object.assign(
538            Object.create(null),
539            defaultOptions,
540            { cwd: process.cwd() },
541            providedOptions
542        );
543
544        if (options.fix === void 0) {
545            options.fix = false;
546        }
547
548        const additionalPluginPool = new Map();
549        const cacheFilePath = getCacheFile(
550            options.cacheLocation || options.cacheFile,
551            options.cwd
552        );
553        const configArrayFactory = new CascadingConfigArrayFactory({
554            additionalPluginPool,
555            baseConfig: options.baseConfig || null,
556            cliConfig: createConfigDataFromOptions(options),
557            cwd: options.cwd,
558            ignorePath: options.ignorePath,
559            resolvePluginsRelativeTo: options.resolvePluginsRelativeTo,
560            rulePaths: options.rulePaths,
561            specificConfigPath: options.configFile,
562            useEslintrc: options.useEslintrc
563        });
564        const fileEnumerator = new FileEnumerator({
565            configArrayFactory,
566            cwd: options.cwd,
567            extensions: options.extensions,
568            globInputPaths: options.globInputPaths,
569            errorOnUnmatchedPattern: options.errorOnUnmatchedPattern,
570            ignore: options.ignore
571        });
572        const lintResultCache =
573            options.cache ? new LintResultCache(cacheFilePath) : null;
574        const linter = new Linter({ cwd: options.cwd });
575
576        /** @type {ConfigArray[]} */
577        const lastConfigArrays = [configArrayFactory.getConfigArrayForFile()];
578
579        // Store private data.
580        internalSlotsMap.set(this, {
581            additionalPluginPool,
582            cacheFilePath,
583            configArrayFactory,
584            defaultIgnores: IgnorePattern.createDefaultIgnore(options.cwd),
585            fileEnumerator,
586            lastConfigArrays,
587            lintResultCache,
588            linter,
589            options
590        });
591
592        // setup special filter for fixes
593        if (options.fix && options.fixTypes && options.fixTypes.length > 0) {
594            debug(`Using fix types ${options.fixTypes}`);
595
596            // throw an error if any invalid fix types are found
597            validateFixTypes(options.fixTypes);
598
599            // convert to Set for faster lookup
600            const fixTypes = new Set(options.fixTypes);
601
602            // save original value of options.fix in case it's a function
603            const originalFix = (typeof options.fix === "function")
604                ? options.fix : () => true;
605
606            options.fix = message => {
607                const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
608                const matches = rule && rule.meta && fixTypes.has(rule.meta.type);
609
610                return matches && originalFix(message);
611            };
612        }
613    }
614
615    getRules() {
616        const { lastConfigArrays } = internalSlotsMap.get(this);
617
618        return new Map(function *() {
619            yield* builtInRules;
620
621            for (const configArray of lastConfigArrays) {
622                yield* configArray.pluginRules;
623            }
624        }());
625    }
626
627    /**
628     * Returns results that only contains errors.
629     * @param {LintResult[]} results The results to filter.
630     * @returns {LintResult[]} The filtered results.
631     */
632    static getErrorResults(results) {
633        const filtered = [];
634
635        results.forEach(result => {
636            const filteredMessages = result.messages.filter(isErrorMessage);
637
638            if (filteredMessages.length > 0) {
639                filtered.push({
640                    ...result,
641                    messages: filteredMessages,
642                    errorCount: filteredMessages.length,
643                    warningCount: 0,
644                    fixableErrorCount: result.fixableErrorCount,
645                    fixableWarningCount: 0
646                });
647            }
648        });
649
650        return filtered;
651    }
652
653    /**
654     * Outputs fixes from the given results to files.
655     * @param {LintReport} report The report object created by CLIEngine.
656     * @returns {void}
657     */
658    static outputFixes(report) {
659        report.results.filter(result => Object.prototype.hasOwnProperty.call(result, "output")).forEach(result => {
660            fs.writeFileSync(result.filePath, result.output);
661        });
662    }
663
664
665    /**
666     * Add a plugin by passing its configuration
667     * @param {string} name Name of the plugin.
668     * @param {Plugin} pluginObject Plugin configuration object.
669     * @returns {void}
670     */
671    addPlugin(name, pluginObject) {
672        const {
673            additionalPluginPool,
674            configArrayFactory,
675            lastConfigArrays
676        } = internalSlotsMap.get(this);
677
678        additionalPluginPool.set(name, pluginObject);
679        configArrayFactory.clearCache();
680        lastConfigArrays.length = 1;
681        lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
682    }
683
684    /**
685     * Resolves the patterns passed into executeOnFiles() into glob-based patterns
686     * for easier handling.
687     * @param {string[]} patterns The file patterns passed on the command line.
688     * @returns {string[]} The equivalent glob patterns.
689     */
690    resolveFileGlobPatterns(patterns) {
691        const { options } = internalSlotsMap.get(this);
692
693        if (options.globInputPaths === false) {
694            return patterns.filter(Boolean);
695        }
696
697        const extensions = (options.extensions || [".js"]).map(ext => ext.replace(/^\./u, ""));
698        const dirSuffix = `/**/*.{${extensions.join(",")}}`;
699
700        return patterns.filter(Boolean).map(pathname => {
701            const resolvedPath = path.resolve(options.cwd, pathname);
702            const newPath = directoryExists(resolvedPath)
703                ? pathname.replace(/[/\\]$/u, "") + dirSuffix
704                : pathname;
705
706            return path.normalize(newPath).replace(/\\/gu, "/");
707        });
708    }
709
710    /**
711     * Executes the current configuration on an array of file and directory names.
712     * @param {string[]} patterns An array of file and directory names.
713     * @returns {LintReport} The results for all files that were linted.
714     */
715    executeOnFiles(patterns) {
716        const {
717            cacheFilePath,
718            fileEnumerator,
719            lastConfigArrays,
720            lintResultCache,
721            linter,
722            options: {
723                allowInlineConfig,
724                cache,
725                cwd,
726                fix,
727                reportUnusedDisableDirectives
728            }
729        } = internalSlotsMap.get(this);
730        const results = [];
731        const startTime = Date.now();
732
733        // Clear the last used config arrays.
734        lastConfigArrays.length = 0;
735
736        // Delete cache file; should this do here?
737        if (!cache) {
738            try {
739                fs.unlinkSync(cacheFilePath);
740            } catch (error) {
741                const errorCode = error && error.code;
742
743                // Ignore errors when no such file exists or file system is read only (and cache file does not exist)
744                if (errorCode !== "ENOENT" && !(errorCode === "EROFS" && !fs.existsSync(cacheFilePath))) {
745                    throw error;
746                }
747            }
748        }
749
750        // Iterate source code files.
751        for (const { config, filePath, ignored } of fileEnumerator.iterateFiles(patterns)) {
752            if (ignored) {
753                results.push(createIgnoreResult(filePath, cwd));
754                continue;
755            }
756
757            /*
758             * Store used configs for:
759             * - this method uses to collect used deprecated rules.
760             * - `getRules()` method uses to collect all loaded rules.
761             * - `--fix-type` option uses to get the loaded rule's meta data.
762             */
763            if (!lastConfigArrays.includes(config)) {
764                lastConfigArrays.push(config);
765            }
766
767            // Skip if there is cached result.
768            if (lintResultCache) {
769                const cachedResult =
770                    lintResultCache.getCachedLintResults(filePath, config);
771
772                if (cachedResult) {
773                    const hadMessages =
774                        cachedResult.messages &&
775                        cachedResult.messages.length > 0;
776
777                    if (hadMessages && fix) {
778                        debug(`Reprocessing cached file to allow autofix: ${filePath}`);
779                    } else {
780                        debug(`Skipping file since it hasn't changed: ${filePath}`);
781                        results.push(cachedResult);
782                        continue;
783                    }
784                }
785            }
786
787            // Do lint.
788            const result = verifyText({
789                text: fs.readFileSync(filePath, "utf8"),
790                filePath,
791                config,
792                cwd,
793                fix,
794                allowInlineConfig,
795                reportUnusedDisableDirectives,
796                fileEnumerator,
797                linter
798            });
799
800            results.push(result);
801
802            /*
803             * Store the lint result in the LintResultCache.
804             * NOTE: The LintResultCache will remove the file source and any
805             * other properties that are difficult to serialize, and will
806             * hydrate those properties back in on future lint runs.
807             */
808            if (lintResultCache) {
809                lintResultCache.setCachedLintResults(filePath, config, result);
810            }
811        }
812
813        // Persist the cache to disk.
814        if (lintResultCache) {
815            lintResultCache.reconcile();
816        }
817
818        debug(`Linting complete in: ${Date.now() - startTime}ms`);
819        let usedDeprecatedRules;
820
821        return {
822            results,
823            ...calculateStatsPerRun(results),
824
825            // Initialize it lazily because CLI and `ESLint` API don't use it.
826            get usedDeprecatedRules() {
827                if (!usedDeprecatedRules) {
828                    usedDeprecatedRules = Array.from(
829                        iterateRuleDeprecationWarnings(lastConfigArrays)
830                    );
831                }
832                return usedDeprecatedRules;
833            }
834        };
835    }
836
837    /**
838     * Executes the current configuration on text.
839     * @param {string} text A string of JavaScript code to lint.
840     * @param {string} [filename] An optional string representing the texts filename.
841     * @param {boolean} [warnIgnored] Always warn when a file is ignored
842     * @returns {LintReport} The results for the linting.
843     */
844    executeOnText(text, filename, warnIgnored) {
845        const {
846            configArrayFactory,
847            fileEnumerator,
848            lastConfigArrays,
849            linter,
850            options: {
851                allowInlineConfig,
852                cwd,
853                fix,
854                reportUnusedDisableDirectives
855            }
856        } = internalSlotsMap.get(this);
857        const results = [];
858        const startTime = Date.now();
859        const resolvedFilename = filename && path.resolve(cwd, filename);
860
861
862        // Clear the last used config arrays.
863        lastConfigArrays.length = 0;
864        if (resolvedFilename && this.isPathIgnored(resolvedFilename)) {
865            if (warnIgnored) {
866                results.push(createIgnoreResult(resolvedFilename, cwd));
867            }
868        } else {
869            const config = configArrayFactory.getConfigArrayForFile(
870                resolvedFilename || "__placeholder__.js"
871            );
872
873            /*
874             * Store used configs for:
875             * - this method uses to collect used deprecated rules.
876             * - `getRules()` method uses to collect all loaded rules.
877             * - `--fix-type` option uses to get the loaded rule's meta data.
878             */
879            lastConfigArrays.push(config);
880
881            // Do lint.
882            results.push(verifyText({
883                text,
884                filePath: resolvedFilename,
885                config,
886                cwd,
887                fix,
888                allowInlineConfig,
889                reportUnusedDisableDirectives,
890                fileEnumerator,
891                linter
892            }));
893        }
894
895        debug(`Linting complete in: ${Date.now() - startTime}ms`);
896        let usedDeprecatedRules;
897
898        return {
899            results,
900            ...calculateStatsPerRun(results),
901
902            // Initialize it lazily because CLI and `ESLint` API don't use it.
903            get usedDeprecatedRules() {
904                if (!usedDeprecatedRules) {
905                    usedDeprecatedRules = Array.from(
906                        iterateRuleDeprecationWarnings(lastConfigArrays)
907                    );
908                }
909                return usedDeprecatedRules;
910            }
911        };
912    }
913
914    /**
915     * Returns a configuration object for the given file based on the CLI options.
916     * This is the same logic used by the ESLint CLI executable to determine
917     * configuration for each file it processes.
918     * @param {string} filePath The path of the file to retrieve a config object for.
919     * @returns {ConfigData} A configuration object for the file.
920     */
921    getConfigForFile(filePath) {
922        const { configArrayFactory, options } = internalSlotsMap.get(this);
923        const absolutePath = path.resolve(options.cwd, filePath);
924
925        if (directoryExists(absolutePath)) {
926            throw Object.assign(
927                new Error("'filePath' should not be a directory path."),
928                { messageTemplate: "print-config-with-directory-path" }
929            );
930        }
931
932        return configArrayFactory
933            .getConfigArrayForFile(absolutePath)
934            .extractConfig(absolutePath)
935            .toCompatibleObjectAsConfigFileContent();
936    }
937
938    /**
939     * Checks if a given path is ignored by ESLint.
940     * @param {string} filePath The path of the file to check.
941     * @returns {boolean} Whether or not the given path is ignored.
942     */
943    isPathIgnored(filePath) {
944        const {
945            configArrayFactory,
946            defaultIgnores,
947            options: { cwd, ignore }
948        } = internalSlotsMap.get(this);
949        const absolutePath = path.resolve(cwd, filePath);
950
951        if (ignore) {
952            const config = configArrayFactory
953                .getConfigArrayForFile(absolutePath)
954                .extractConfig(absolutePath);
955            const ignores = config.ignores || defaultIgnores;
956
957            return ignores(absolutePath);
958        }
959
960        return defaultIgnores(absolutePath);
961    }
962
963    /**
964     * Returns the formatter representing the given format or null if the `format` is not a string.
965     * @param {string} [format] The name of the format to load or the path to a
966     *      custom formatter.
967     * @returns {(Function|null)} The formatter function or null if the `format` is not a string.
968     */
969    getFormatter(format) {
970
971        // default is stylish
972        const resolvedFormatName = format || "stylish";
973
974        // only strings are valid formatters
975        if (typeof resolvedFormatName === "string") {
976
977            // replace \ with / for Windows compatibility
978            const normalizedFormatName = resolvedFormatName.replace(/\\/gu, "/");
979
980            const slots = internalSlotsMap.get(this);
981            const cwd = slots ? slots.options.cwd : process.cwd();
982            const namespace = naming.getNamespaceFromTerm(normalizedFormatName);
983
984            let formatterPath;
985
986            // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
987            if (!namespace && normalizedFormatName.indexOf("/") > -1) {
988                formatterPath = path.resolve(cwd, normalizedFormatName);
989            } else {
990                try {
991                    const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint-formatter");
992
993                    formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__.js"));
994                } catch {
995                    formatterPath = path.resolve(__dirname, "formatters", normalizedFormatName);
996                }
997            }
998
999            try {
1000                return require(formatterPath);
1001            } catch (ex) {
1002                ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
1003                throw ex;
1004            }
1005
1006        } else {
1007            return null;
1008        }
1009    }
1010}
1011
1012CLIEngine.version = pkg.version;
1013CLIEngine.getFormatter = CLIEngine.prototype.getFormatter;
1014
1015module.exports = {
1016    CLIEngine,
1017
1018    /**
1019     * Get the internal slots of a given CLIEngine instance for tests.
1020     * @param {CLIEngine} instance The CLIEngine instance to get.
1021     * @returns {CLIEngineInternalSlots} The internal slots.
1022     */
1023    getCLIEngineInternalSlots(instance) {
1024        return internalSlotsMap.get(instance);
1025    }
1026};
1027