2 * @fileoverview Main class using flat config
3 * @author Nicholas C. Zakas
8 //------------------------------------------------------------------------------
10 //------------------------------------------------------------------------------
12 // Note: Node.js 12 does not support fs/promises.
13 const fs
= require("fs").promises
;
14 const path
= require("path");
15 const findUp
= require("find-up");
16 const { version
} = require("../../package.json");
17 const { Linter
} = require("../linter");
18 const { getRuleFromConfig
} = require("../config/flat-config-helpers");
19 const { gitignoreToMinimatch
} = require("@humanwhocodes/gitignore-to-minimatch");
28 } = require("@eslint/eslintrc");
36 isArrayOfNonEmptyString
,
42 } = require("./eslint-helpers");
43 const { pathToFileURL
} = require("url");
44 const { FlatConfigArray
} = require("../config/flat-config-array");
45 const LintResultCache
= require("../cli-engine/lint-result-cache");
48 * This is necessary to allow overwriting writeFile for testing purposes.
49 * We can just use fs/promises once we drop Node.js 12 support.
52 //------------------------------------------------------------------------------
54 //------------------------------------------------------------------------------
56 // For VSCode IntelliSense
57 /** @typedef {import("../shared/types").ConfigData} ConfigData */
58 /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
59 /** @typedef {import("../shared/types").LintMessage} LintMessage */
60 /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
61 /** @typedef {import("../shared/types").Plugin} Plugin */
62 /** @typedef {import("../shared/types").RuleConf} RuleConf */
63 /** @typedef {import("../shared/types").Rule} Rule */
64 /** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
67 * The options with which to configure the ESLint instance.
68 * @typedef {Object} FlatESLintOptions
69 * @property {boolean} [allowInlineConfig] Enable or disable inline configuration comments.
70 * @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
71 * @property {boolean} [cache] Enable result caching.
72 * @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
73 * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
74 * @property {string} [cwd] The value to use for the current working directory.
75 * @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
76 * @property {boolean|Function} [fix] Execute in autofix mode. If a function, should return a boolean.
77 * @property {string[]} [fixTypes] Array of rule types to apply fixes for.
78 * @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.
79 * @property {boolean} [ignore] False disables use of .eslintignore.
80 * @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
81 * @property {string[]} [ignorePatterns] Ignore file patterns to use in addition to .eslintignore.
82 * @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
83 * @property {boolean|string} [overrideConfigFile] Searches for default config file when falsy;
84 * doesn't do any config file lookup when `true`; considered to be a config filename
86 * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
87 * @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
90 //------------------------------------------------------------------------------
92 //------------------------------------------------------------------------------
94 const FLAT_CONFIG_FILENAME
= "eslint.config.js";
95 const debug
= require("debug")("eslint:flat-eslint");
96 const removedFormatters
= new Set(["table", "codeframe"]);
97 const privateMembers
= new WeakMap();
100 * It will calculate the error and warning count for collection of messages per file
101 * @param {LintMessage[]} messages Collection of messages
102 * @returns {Object} Contains the stats
105 function calculateStatsPerFile(messages
) {
106 return messages
.reduce((stat
, message
) => {
107 if (message
.fatal
|| message
.severity
=== 2) {
110 stat
.fatalErrorCount
++;
113 stat
.fixableErrorCount
++;
118 stat
.fixableWarningCount
++;
126 fixableErrorCount
: 0,
127 fixableWarningCount
: 0
132 * It will calculate the error and warning count for collection of results from all files
133 * @param {LintResult[]} results Collection of messages from all the files
134 * @returns {Object} Contains the stats
137 function calculateStatsPerRun(results
) {
138 return results
.reduce((stat
, result
) => {
139 stat
.errorCount
+= result
.errorCount
;
140 stat
.fatalErrorCount
+= result
.fatalErrorCount
;
141 stat
.warningCount
+= result
.warningCount
;
142 stat
.fixableErrorCount
+= result
.fixableErrorCount
;
143 stat
.fixableWarningCount
+= result
.fixableWarningCount
;
149 fixableErrorCount
: 0,
150 fixableWarningCount
: 0
155 * Loads global ignore patterns from an ignore file (usually .eslintignore).
156 * @param {string} filePath The filename to load.
157 * @returns {ignore} A function encapsulating the ignore patterns.
158 * @throws {Error} If the file cannot be read.
161 async
function loadIgnoreFilePatterns(filePath
) {
162 debug(`Loading ignore file: ${filePath}`);
165 const ignoreFileText
= await fs
.readFile(filePath
, { encoding
: "utf8" });
167 return ignoreFileText
169 .filter(line
=> line
.trim() !== "" && !line
.startsWith("#"));
172 debug(`Error reading ignore file: ${filePath}`);
173 e
.message
= `Cannot read ignore file: ${filePath}\nError: ${e.message}`;
179 * Create rulesMeta object.
180 * @param {Map<string,Rule>} rules a map of rules from which to generate the object.
181 * @returns {Object} metadata for all enabled rules.
183 function createRulesMeta(rules
) {
184 return Array
.from(rules
).reduce((retVal
, [id
, rule
]) => {
185 retVal
[id
] = rule
.meta
;
190 /** @type {WeakMap<ExtractedConfig, DeprecatedRuleInfo[]>} */
191 const usedDeprecatedRulesCache
= new WeakMap();
194 * Create used deprecated rule list.
195 * @param {CLIEngine} eslint The CLIEngine instance.
196 * @param {string} maybeFilePath The absolute path to a lint target file or `"<text>"`.
197 * @returns {DeprecatedRuleInfo[]} The used deprecated rule list.
199 function getOrFindUsedDeprecatedRules(eslint
, maybeFilePath
) {
203 } = privateMembers
.get(eslint
);
204 const filePath
= path
.isAbsolute(maybeFilePath
)
206 : path
.join(cwd
, "__placeholder__.js");
207 const config
= configs
.getConfig(filePath
);
209 // Most files use the same config, so cache it.
210 if (config
&& !usedDeprecatedRulesCache
.has(config
)) {
214 for (const [ruleId
, ruleConf
] of Object
.entries(config
.rules
)) {
215 if (getRuleSeverity(ruleConf
) === 0) {
218 const rule
= getRuleFromConfig(ruleId
, config
);
219 const meta
= rule
&& rule
.meta
;
221 if (meta
&& meta
.deprecated
) {
222 retv
.push({ ruleId
, replacedBy
: meta
.replacedBy
|| [] });
228 usedDeprecatedRulesCache
.set(config
, Object
.freeze(retv
));
231 return config
? usedDeprecatedRulesCache
.get(config
) : Object
.freeze([]);
235 * Processes the linting results generated by a CLIEngine linting report to
236 * match the ESLint class's API.
237 * @param {CLIEngine} eslint The CLIEngine instance.
238 * @param {CLIEngineLintReport} report The CLIEngine linting report to process.
239 * @returns {LintResult[]} The processed linting results.
241 function processLintReport(eslint
, { results
}) {
246 return getOrFindUsedDeprecatedRules(eslint
, this.filePath
);
250 for (const result
of results
) {
251 Object
.defineProperty(result
, "usedDeprecatedRules", descriptor
);
258 * An Array.prototype.sort() compatible compare function to order results by their file path.
259 * @param {LintResult} a The first lint result.
260 * @param {LintResult} b The second lint result.
261 * @returns {number} An integer representing the order in which the two results should occur.
263 function compareResultsByFilePath(a
, b
) {
264 if (a
.filePath
< b
.filePath
) {
268 if (a
.filePath
> b
.filePath
) {
276 * Searches from the current working directory up until finding the
277 * given flat config filename.
278 * @param {string} cwd The current working directory to search from.
279 * @returns {Promise<string|null>} The filename if found or `null` if not.
281 function findFlatConfigFile(cwd
) {
283 FLAT_CONFIG_FILENAME
,
289 * Load the config array from the given filename.
290 * @param {string} filePath The filename to load from.
291 * @param {Object} options Options to help load the config file.
292 * @param {string} options.basePath The base path for the config array.
293 * @param {boolean} options.shouldIgnore Whether to honor ignore patterns.
294 * @returns {Promise<FlatConfigArray>} The config array loaded from the config file.
296 async
function loadFlatConfigFile(filePath
, { basePath
, shouldIgnore
}) {
297 debug(`Loading config from ${filePath}`);
299 const fileURL
= pathToFileURL(filePath
);
301 debug(`Config file URL is ${fileURL}`);
303 const module
= await
import(fileURL
);
305 return new FlatConfigArray(module
.default, {
312 * Calculates the config array for this run based on inputs.
313 * @param {FlatESLint} eslint The instance to create the config array for.
314 * @param {import("./eslint").ESLintOptions} options The ESLint instance options.
315 * @returns {FlatConfigArray} The config array for `eslint``.
317 async
function calculateConfigArray(eslint
, {
321 ignore
: shouldIgnore
,
326 // check for cached instance
327 const slots
= privateMembers
.get(eslint
);
330 return slots
.configs
;
333 // determine where to load config file from
337 if (typeof configFile
=== "string") {
338 debug(`Override config file path is ${configFile}`);
339 configFilePath
= path
.resolve(cwd
, configFile
);
340 } else if (configFile
!== false) {
341 debug("Searching for eslint.config.js");
342 configFilePath
= await
findFlatConfigFile(cwd
);
344 if (!configFilePath
) {
345 throw new Error("Could not find config file.");
348 basePath
= path
.resolve(path
.dirname(configFilePath
));
354 if (configFilePath
) {
355 configs
= await
loadFlatConfigFile(configFilePath
, {
360 configs
= new FlatConfigArray([], { basePath
, shouldIgnore
});
363 // add in any configured defaults
364 configs
.push(...slots
.defaultConfigs
);
366 let allIgnorePatterns
= [];
369 // load ignore file if necessary
372 ignoreFilePath
= path
.resolve(cwd
, ignorePath
);
373 allIgnorePatterns
= await
loadIgnoreFilePatterns(ignoreFilePath
);
375 ignoreFilePath
= path
.resolve(cwd
, ".eslintignore");
377 // no error if .eslintignore doesn't exist`
378 if (fileExists(ignoreFilePath
)) {
379 allIgnorePatterns
= await
loadIgnoreFilePatterns(ignoreFilePath
);
384 // append command line ignore patterns
385 if (ignorePatterns
) {
386 if (typeof ignorePatterns
=== "string") {
387 allIgnorePatterns
.push(ignorePatterns
);
389 allIgnorePatterns
.push(...ignorePatterns
);
394 * If the config file basePath is different than the cwd, then
395 * the ignore patterns won't work correctly. Here, we adjust the
396 * ignore pattern to include the correct relative path. Patterns
397 * loaded from ignore files are always relative to the cwd, whereas
398 * the config file basePath can be an ancestor of the cwd.
400 if (basePath
!== cwd
&& allIgnorePatterns
.length
) {
402 const relativeIgnorePath
= path
.relative(basePath
, cwd
);
404 allIgnorePatterns
= allIgnorePatterns
.map(pattern
=> {
405 const negated
= pattern
.startsWith("!");
406 const basePattern
= negated
? pattern
.slice(1) : pattern
;
409 * Ignore patterns are considered relative to a directory
410 * when the pattern contains a slash in a position other
411 * than the last character. If that's the case, we need to
412 * add the relative ignore path to the current pattern to
413 * get the correct behavior. Otherwise, no change is needed.
415 if (!basePattern
.includes("/") || basePattern
.endsWith("/")) {
419 return (negated
? "!" : "") +
420 path
.posix
.join(relativeIgnorePath
, basePattern
);
424 if (allIgnorePatterns
.length
) {
427 * Ignore patterns are added to the end of the config array
428 * so they can override default ignores.
431 ignores
: allIgnorePatterns
.map(gitignoreToMinimatch
)
435 if (overrideConfig
) {
436 if (Array
.isArray(overrideConfig
)) {
437 configs
.push(...overrideConfig
);
439 configs
.push(overrideConfig
);
443 await configs
.normalize();
445 // cache the config array for this instance
446 slots
.configs
= configs
;
452 * Processes an source code using ESLint.
453 * @param {Object} config The config object.
454 * @param {string} config.text The source code to verify.
455 * @param {string} config.cwd The path to the current working directory.
456 * @param {string|undefined} config.filePath The path to the file of `text`. If this is undefined, it uses `<text>`.
457 * @param {FlatConfigArray} config.configs The config.
458 * @param {boolean} config.fix If `true` then it does fix.
459 * @param {boolean} config.allowInlineConfig If `true` then it uses directive comments.
460 * @param {boolean} config.reportUnusedDisableDirectives If `true` then it reports unused `eslint-disable` comments.
461 * @param {Linter} config.linter The linter instance to verify.
462 * @returns {LintResult} The result of linting.
465 function verifyText({
468 filePath
: providedFilePath
,
472 reportUnusedDisableDirectives
,
475 const filePath
= providedFilePath
|| "<text>";
477 debug(`Lint ${filePath}`);
481 * `config.extractConfig(filePath)` requires an absolute path, but `linter`
482 * doesn't know CWD, so it gives `linter` an absolute path always.
484 const filePathToVerify
= filePath
=== "<text>" ? path
.join(cwd
, "__placeholder__.js") : filePath
;
485 const { fixed
, messages
, output
} = linter
.verifyAndFix(
490 filename
: filePathToVerify
,
492 reportUnusedDisableDirectives
,
495 * Check if the linter should adopt a given code block or not.
496 * @param {string} blockFilename The virtual filename of a code block.
497 * @returns {boolean} `true` if the linter should adopt the code block.
499 filterCodeBlock(blockFilename
) {
500 return configs
.isExplicitMatch(blockFilename
);
507 filePath
: filePath
=== "<text>" ? filePath
: path
.resolve(filePath
),
509 suppressedMessages
: linter
.getSuppressedMessages(),
510 ...calculateStatsPerFile(messages
)
514 result
.output
= output
;
518 result
.errorCount
+ result
.warningCount
> 0 &&
519 typeof result
.output
=== "undefined"
521 result
.source
= text
;
528 * Checks whether a message's rule type should be fixed.
529 * @param {LintMessage} message The message to check.
530 * @param {FlatConfig} config The config for the file that generated the message.
531 * @param {string[]} fixTypes An array of fix types to check.
532 * @returns {boolean} Whether the message should be fixed.
534 function shouldMessageBeFixed(message
, config
, fixTypes
) {
535 if (!message
.ruleId
) {
536 return fixTypes
.has("directive");
539 const rule
= message
.ruleId
&& getRuleFromConfig(message
.ruleId
, config
);
541 return Boolean(rule
&& rule
.meta
&& fixTypes
.has(rule
.meta
.type
));
545 * Collect used deprecated rules.
546 * @param {Array<FlatConfig>} configs The configs to evaluate.
547 * @returns {IterableIterator<DeprecatedRuleInfo>} Used deprecated rules.
549 function *iterateRuleDeprecationWarnings(configs
) {
550 const processedRuleIds
= new Set();
552 for (const config
of configs
) {
553 for (const [ruleId
, ruleConfig
] of Object
.entries(config
.rules
)) {
555 // Skip if it was processed.
556 if (processedRuleIds
.has(ruleId
)) {
559 processedRuleIds
.add(ruleId
);
561 // Skip if it's not used.
562 if (!getRuleSeverity(ruleConfig
)) {
565 const rule
= getRuleFromConfig(ruleId
, config
);
567 // Skip if it's not deprecated.
568 if (!(rule
&& rule
.meta
&& rule
.meta
.deprecated
)) {
572 // This rule was used and deprecated.
575 replacedBy
: rule
.meta
.replacedBy
|| []
581 //-----------------------------------------------------------------------------
583 //-----------------------------------------------------------------------------
586 * Primary Node.js API for ESLint.
591 * Creates a new instance of the main ESLint API.
592 * @param {FlatESLintOptions} options The options for this instance.
594 constructor(options
= {}) {
596 const defaultConfigs
= [];
597 const processedOptions
= processOptions(options
);
598 const linter
= new Linter({
599 cwd
: processedOptions
.cwd
,
603 const cacheFilePath
= getCacheFile(
604 processedOptions
.cacheLocation
,
608 const lintResultCache
= processedOptions
.cache
609 ? new LintResultCache(cacheFilePath
, processedOptions
.cacheStrategy
)
612 privateMembers
.set(this, {
613 options
: processedOptions
,
618 defaultIgnores
: () => false,
623 * If additional plugins are passed in, add that to the default
624 * configs for this instance.
626 if (options
.plugins
) {
630 for (const [pluginName
, plugin
] of Object
.entries(options
.plugins
)) {
631 plugins
[naming
.getShorthandName(pluginName
, "eslint-plugin")] = plugin
;
634 defaultConfigs
.push({
645 static get version() {
650 * Outputs fixes from the given results to files.
651 * @param {LintResult[]} results The lint results.
652 * @returns {Promise<void>} Returns a promise that is used to track side effects.
654 static async
outputFixes(results
) {
655 if (!Array
.isArray(results
)) {
656 throw new Error("'results' must be an array");
662 if (typeof result
!== "object" || result
=== null) {
663 throw new Error("'results' must include only objects");
666 typeof result
.output
=== "string" &&
667 path
.isAbsolute(result
.filePath
)
670 .map(r
=> fs
.writeFile(r
.filePath
, r
.output
))
675 * Returns results that only contains errors.
676 * @param {LintResult[]} results The results to filter.
677 * @returns {LintResult[]} The filtered results.
679 static getErrorResults(results
) {
682 results
.forEach(result
=> {
683 const filteredMessages
= result
.messages
.filter(isErrorMessage
);
684 const filteredSuppressedMessages
= result
.suppressedMessages
.filter(isErrorMessage
);
686 if (filteredMessages
.length
> 0) {
689 messages
: filteredMessages
,
690 suppressedMessages
: filteredSuppressedMessages
,
691 errorCount
: filteredMessages
.length
,
693 fixableErrorCount
: result
.fixableErrorCount
,
694 fixableWarningCount
: 0
703 * Returns meta objects for each rule represented in the lint results.
704 * @param {LintResult[]} results The results to fetch rules meta for.
705 * @returns {Object} A mapping of ruleIds to rule meta objects.
706 * @throws {TypeError} When the results object wasn't created from this ESLint instance.
707 * @throws {TypeError} When a plugin or rule is missing.
709 getRulesMetaForResults(results
) {
711 const resultRules
= new Map();
713 // short-circuit simple case
714 if (results
.length
=== 0) {
718 const { configs
} = privateMembers
.get(this);
721 * We can only accurately return rules meta information for linting results if the
722 * results were created by this instance. Otherwise, the necessary rules data is
723 * not available. So if the config array doesn't already exist, just throw an error
724 * to let the user know we can't do anything here.
727 throw new TypeError("Results object was not created from this ESLint instance.");
730 for (const result
of results
) {
733 * Normalize filename for <text>.
735 const filePath
= result
.filePath
=== "<text>"
736 ? "__placeholder__.js" : result
.filePath
;
739 * All of the plugin and rule information is contained within the
740 * calculated config for the given file.
742 const config
= configs
.getConfig(filePath
);
743 const allMessages
= result
.messages
.concat(result
.suppressedMessages
);
745 for (const { ruleId
} of allMessages
) {
746 const rule
= getRuleFromConfig(ruleId
, config
);
748 // ensure the rule exists
750 throw new TypeError(`Could not find the rule "${ruleId}".`);
753 resultRules
.set(ruleId
, rule
);
757 return createRulesMeta(resultRules
);
761 * Executes the current configuration on an array of file and directory names.
762 * @param {string|string[]} patterns An array of file and directory names.
763 * @returns {Promise<LintResult[]>} The results of linting the file patterns given.
765 async
lintFiles(patterns
) {
766 if (!isNonEmptyString(patterns
) && !isArrayOfNonEmptyString(patterns
)) {
767 throw new Error("'patterns' must be a non-empty string or an array of non-empty strings");
774 options
: eslintOptions
775 } = privateMembers
.get(this);
776 const configs
= await
calculateConfigArray(this, eslintOptions
);
783 reportUnusedDisableDirectives
,
785 errorOnUnmatchedPattern
787 const startTime
= Date
.now();
788 const usedConfigs
= [];
789 const fixTypesSet
= fixTypes
? new Set(fixTypes
) : null;
791 // Delete cache file; should this be done here?
792 if (!cache
&& cacheFilePath
) {
793 debug(`Deleting cache file at ${cacheFilePath}`);
796 await fs
.unlink(cacheFilePath
);
798 const errorCode
= error
&& error
.code
;
800 // Ignore errors when no such file exists or file system is read only (and cache file does not exist)
801 if (errorCode
!== "ENOENT" && !(errorCode
=== "EROFS" && !(await fs
.exists(cacheFilePath
)))) {
807 const filePaths
= await
findFiles({
808 patterns
: typeof patterns
=== "string" ? [patterns
] : patterns
,
812 errorOnUnmatchedPattern
815 debug(`${filePaths.length} files found in: ${Date.now() - startTime}ms`);
818 * Because we need to process multiple files, including reading from disk,
819 * it is most efficient to start by reading each file via promises so that
820 * they can be done in parallel. Then, we can lint the returned text. This
821 * ensures we are waiting the minimum amount of time in between lints.
823 const results
= await Promise
.all(
825 filePaths
.map(({ filePath
, ignored
}) => {
828 * If a filename was entered that matches an ignore
829 * pattern, then notify the user.
832 return createIgnoreResult(filePath
, cwd
);
835 const config
= configs
.getConfig(filePath
);
838 * Sometimes a file found through a glob pattern will
839 * be ignored. In this case, `config` will be undefined
840 * and we just silently ignore the file.
847 * Store used configs for:
848 * - this method uses to collect used deprecated rules.
849 * - `--fix-type` option uses to get the loaded rule's meta data.
851 if (!usedConfigs
.includes(config
)) {
852 usedConfigs
.push(config
);
855 // Skip if there is cached result.
856 if (lintResultCache
) {
858 lintResultCache
.getCachedLintResults(filePath
, config
);
862 cachedResult
.messages
&&
863 cachedResult
.messages
.length
> 0;
865 if (hadMessages
&& fix
) {
866 debug(`Reprocessing cached file to allow autofix: ${filePath}`);
868 debug(`Skipping file since it hasn't changed: ${filePath}`);
875 // set up fixer for fixtypes if necessary
878 if (fix
&& fixTypesSet
) {
880 // save original value of options.fix in case it's a function
881 const originalFix
= (typeof fix
=== "function")
884 fixer
= message
=> shouldMessageBeFixed(message
, config
, fixTypesSet
) && originalFix(message
);
887 return fs
.readFile(filePath
, "utf8")
891 const result
= verifyText({
898 reportUnusedDisableDirectives
,
903 * Store the lint result in the LintResultCache.
904 * NOTE: The LintResultCache will remove the file source and any
905 * other properties that are difficult to serialize, and will
906 * hydrate those properties back in on future lint runs.
908 if (lintResultCache
) {
909 lintResultCache
.setCachedLintResults(filePath
, config
, result
);
918 // Persist the cache to disk.
919 if (lintResultCache
) {
920 lintResultCache
.reconcile();
923 let usedDeprecatedRules
;
924 const finalResults
= results
.filter(result
=> !!result
);
926 return processLintReport(this, {
927 results
: finalResults
,
928 ...calculateStatsPerRun(finalResults
),
930 // Initialize it lazily because CLI and `ESLint` API don't use it.
931 get usedDeprecatedRules() {
932 if (!usedDeprecatedRules
) {
933 usedDeprecatedRules
= Array
.from(
934 iterateRuleDeprecationWarnings(usedConfigs
)
937 return usedDeprecatedRules
;
943 * Executes the current configuration on text.
944 * @param {string} code A string of JavaScript code to lint.
945 * @param {Object} [options] The options.
946 * @param {string} [options.filePath] The path to the file of the source code.
947 * @param {boolean} [options.warnIgnored] When set to true, warn if given filePath is an ignored path.
948 * @returns {Promise<LintResult[]>} The results of linting the string of code given.
950 async
lintText(code
, options
= {}) {
952 // Parameter validation
954 if (typeof code
!== "string") {
955 throw new Error("'code' must be a string");
958 if (typeof options
!== "object") {
959 throw new Error("'options' must be an object, null, or undefined");
962 // Options validation
970 const unknownOptionKeys
= Object
.keys(unknownOptions
);
972 if (unknownOptionKeys
.length
> 0) {
973 throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
976 if (filePath
!== void 0 && !isNonEmptyString(filePath
)) {
977 throw new Error("'options.filePath' must be a non-empty string or undefined");
980 if (typeof warnIgnored
!== "boolean") {
981 throw new Error("'options.warnIgnored' must be a boolean or undefined");
984 // Now we can get down to linting
988 options
: eslintOptions
989 } = privateMembers
.get(this);
990 const configs
= await
calculateConfigArray(this, eslintOptions
);
995 reportUnusedDisableDirectives
998 const startTime
= Date
.now();
999 const resolvedFilename
= path
.resolve(cwd
, filePath
|| "__placeholder__.js");
1002 // Clear the last used config arrays.
1003 if (resolvedFilename
&& await
this.isPathIgnored(resolvedFilename
)) {
1005 results
.push(createIgnoreResult(resolvedFilename
, cwd
));
1010 config
= configs
.getConfig(resolvedFilename
);
1013 results
.push(verifyText({
1015 filePath
: resolvedFilename
.endsWith("__placeholder__.js") ? "<text>" : resolvedFilename
,
1020 reportUnusedDisableDirectives
,
1025 debug(`Linting complete in: ${Date.now() - startTime}ms`);
1026 let usedDeprecatedRules
;
1028 return processLintReport(this, {
1030 ...calculateStatsPerRun(results
),
1032 // Initialize it lazily because CLI and `ESLint` API don't use it.
1033 get usedDeprecatedRules() {
1034 if (!usedDeprecatedRules
) {
1035 usedDeprecatedRules
= Array
.from(
1036 iterateRuleDeprecationWarnings(config
)
1039 return usedDeprecatedRules
;
1046 * Returns the formatter representing the given formatter name.
1047 * @param {string} [name] The name of the formatter to load.
1048 * The following values are allowed:
1049 * - `undefined` ... Load `stylish` builtin formatter.
1050 * - A builtin formatter name ... Load the builtin formatter.
1051 * - A thirdparty formatter name:
1052 * - `foo` → `eslint-formatter-foo`
1053 * - `@foo` → `@foo/eslint-formatter`
1054 * - `@foo/bar` → `@foo/eslint-formatter-bar`
1055 * - A file path ... Load the file.
1056 * @returns {Promise<Formatter>} A promise resolving to the formatter object.
1057 * This promise will be rejected if the given formatter was not found or not
1060 async
loadFormatter(name
= "stylish") {
1061 if (typeof name
!== "string") {
1062 throw new Error("'name' must be a string");
1065 // replace \ with / for Windows compatibility
1066 const normalizedFormatName
= name
.replace(/\\/gu, "/");
1067 const namespace = naming.getNamespaceFromTerm(normalizedFormatName);
1070 const { cwd } = privateMembers.get(this).options;
1075 // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
1076 if (!namespace && normalizedFormatName.includes("/")) {
1077 formatterPath = path.resolve(cwd, normalizedFormatName);
1080 const npmFormat = naming.normalizePackageName(normalizedFormatName, "eslint
-formatter
");
1082 // TODO: This is pretty dirty...would be nice to clean up at some point.
1083 formatterPath = ModuleResolver.resolve(npmFormat, path.join(cwd, "__placeholder__
.js
"));
1085 formatterPath = path.resolve(__dirname, "../", "cli
-engine
", "formatters
", `${normalizedFormatName}.js`);
1092 formatter = (await import(pathToFileURL(formatterPath))).default;
1095 // check for formatters that have been removed
1096 if (removedFormatters.has(name)) {
1097 ex.message = `The ${name} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${name}\``;
1099 ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
1106 if (typeof formatter !== "function") {
1107 throw new TypeError(`Formatter must be a function, but got a ${typeof formatter}.`);
1110 const eslint = this;
1115 * The main formatter method.
1116 * @param {LintResults[]} results The lint results to format.
1117 * @returns {string} The formatted lint results.
1120 let rulesMeta = null;
1122 results.sort(compareResultsByFilePath);
1124 return formatter(results, {
1128 rulesMeta = eslint.getRulesMetaForResults(results);
1139 * Returns a configuration object for the given file based on the CLI options.
1140 * This is the same logic used by the ESLint CLI executable to determine
1141 * configuration for each file it processes.
1142 * @param {string} filePath The path of the file to retrieve a config object for.
1143 * @returns {Promise<ConfigData|undefined>} A configuration object for the file
1144 * or `undefined` if there is no configuration data for the object.
1146 async calculateConfigForFile(filePath) {
1147 if (!isNonEmptyString(filePath)) {
1148 throw new Error("'filePath' must be a non
-empty string
");
1150 const options = privateMembers.get(this).options;
1151 const absolutePath = path.resolve(options.cwd, filePath);
1152 const configs = await calculateConfigArray(this, options);
1154 return configs.getConfig(absolutePath);
1158 * Checks if a given path is ignored by ESLint.
1159 * @param {string} filePath The path of the file to check.
1160 * @returns {Promise<boolean>} Whether or not the given path is ignored.
1162 async isPathIgnored(filePath) {
1163 const config = await this.calculateConfigForFile(filePath);
1165 return config === void 0;
1169 //------------------------------------------------------------------------------
1171 //------------------------------------------------------------------------------