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