]> git.proxmox.com Git - pve-eslint.git/blobdiff - eslint/lib/cli-engine/cli-engine.js
import 8.23.1 source
[pve-eslint.git] / eslint / lib / cli-engine / cli-engine.js
index 9a414061501e2d1209bc8d19cb24336892eaa923..fdc66198809c9162b4fb735e007994e4f60b5f6a 100644 (file)
@@ -27,16 +27,11 @@ const {
         naming,
         CascadingConfigArrayFactory,
         IgnorePattern,
-        getUsedExtractedConfigs
+        getUsedExtractedConfigs,
+        ModuleResolver
     }
 } = require("@eslint/eslintrc");
 
-/*
- * For some reason, ModuleResolver must be included via filepath instead of by
- * API exports in order to work properly. That's why this is separated out onto
- * its own require() statement.
- */
-const ModuleResolver = require("@eslint/eslintrc/lib/shared/relative-module-resolver");
 const { FileEnumerator } = require("./file-enumerator");
 
 const { Linter } = require("../linter");
@@ -46,7 +41,7 @@ const hash = require("./hash");
 const LintResultCache = require("./lint-result-cache");
 
 const debug = require("debug")("eslint:cli-engine");
-const validFixTypes = new Set(["problem", "suggestion", "layout"]);
+const validFixTypes = new Set(["directive", "problem", "suggestion", "layout"]);
 
 //------------------------------------------------------------------------------
 // Typedefs
@@ -56,12 +51,14 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
 /** @typedef {import("../shared/types").ConfigData} ConfigData */
 /** @typedef {import("../shared/types").DeprecatedRuleInfo} DeprecatedRuleInfo */
 /** @typedef {import("../shared/types").LintMessage} LintMessage */
+/** @typedef {import("../shared/types").SuppressedLintMessage} SuppressedLintMessage */
 /** @typedef {import("../shared/types").ParserOptions} ParserOptions */
 /** @typedef {import("../shared/types").Plugin} Plugin */
 /** @typedef {import("../shared/types").RuleConf} RuleConf */
 /** @typedef {import("../shared/types").Rule} Rule */
-/** @typedef {ReturnType<CascadingConfigArrayFactory["getConfigArrayForFile"]>} ConfigArray */
-/** @typedef {ReturnType<ConfigArray["extractConfig"]>} ExtractedConfig */
+/** @typedef {import("../shared/types").FormatterFunction} FormatterFunction */
+/** @typedef {ReturnType<CascadingConfigArrayFactory.getConfigArrayForFile>} ConfigArray */
+/** @typedef {ReturnType<ConfigArray.extractConfig>} ExtractedConfig */
 
 /**
  * The options to configure a CLI engine with.
@@ -96,7 +93,9 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
  * @typedef {Object} LintResult
  * @property {string} filePath The path to the file that was linted.
  * @property {LintMessage[]} messages All of the messages for the result.
+ * @property {SuppressedLintMessage[]} suppressedMessages All of the suppressed messages for the result.
  * @property {number} errorCount Number of errors for the result.
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
  * @property {number} warningCount Number of warnings for the result.
  * @property {number} fixableErrorCount Number of fixable errors for the result.
  * @property {number} fixableWarningCount Number of fixable warnings for the result.
@@ -109,6 +108,7 @@ const validFixTypes = new Set(["problem", "suggestion", "layout"]);
  * @typedef {Object} LintReport
  * @property {LintResult[]} results All of the result.
  * @property {number} errorCount Number of errors for the result.
+ * @property {number} fatalErrorCount Number of fatal errors for the result.
  * @property {number} warningCount Number of warnings for the result.
  * @property {number} fixableErrorCount Number of fixable errors for the result.
  * @property {number} fixableWarningCount Number of fixable warnings for the result.
@@ -161,6 +161,9 @@ function calculateStatsPerFile(messages) {
     return messages.reduce((stat, message) => {
         if (message.fatal || message.severity === 2) {
             stat.errorCount++;
+            if (message.fatal) {
+                stat.fatalErrorCount++;
+            }
             if (message.fix) {
                 stat.fixableErrorCount++;
             }
@@ -173,6 +176,7 @@ function calculateStatsPerFile(messages) {
         return stat;
     }, {
         errorCount: 0,
+        fatalErrorCount: 0,
         warningCount: 0,
         fixableErrorCount: 0,
         fixableWarningCount: 0
@@ -188,12 +192,14 @@ function calculateStatsPerFile(messages) {
 function calculateStatsPerRun(results) {
     return results.reduce((stat, result) => {
         stat.errorCount += result.errorCount;
+        stat.fatalErrorCount += result.fatalErrorCount;
         stat.warningCount += result.warningCount;
         stat.fixableErrorCount += result.fixableErrorCount;
         stat.fixableWarningCount += result.fixableWarningCount;
         return stat;
     }, {
         errorCount: 0,
+        fatalErrorCount: 0,
         warningCount: 0,
         fixableErrorCount: 0,
         fixableWarningCount: 0
@@ -260,6 +266,7 @@ function verifyText({
     const result = {
         filePath,
         messages,
+        suppressedMessages: linter.getSuppressedMessages(),
         ...calculateStatsPerFile(messages)
     };
 
@@ -279,7 +286,7 @@ function verifyText({
 /**
  * Returns result with warning by ignore settings
  * @param {string} filePath File path of checked code
- * @param {string} baseDir  Absolute path of base directory
+ * @param {string} baseDir Absolute path of base directory
  * @returns {LintResult} Result with single warning
  * @private
  */
@@ -306,7 +313,9 @@ function createIgnoreResult(filePath, baseDir) {
                 message
             }
         ],
+        suppressedMessages: [],
         errorCount: 0,
+        fatalErrorCount: 0,
         warningCount: 1,
         fixableErrorCount: 0,
         fixableWarningCount: 0
@@ -330,6 +339,23 @@ function getRule(ruleId, configArrays) {
     return builtInRules.get(ruleId) || null;
 }
 
+/**
+ * Checks whether a message's rule type should be fixed.
+ * @param {LintMessage} message The message to check.
+ * @param {ConfigArray[]} lastConfigArrays The list of config arrays that the last `executeOnFiles` or `executeOnText` used.
+ * @param {string[]} fixTypes An array of fix types to check.
+ * @returns {boolean} Whether the message should be fixed.
+ */
+function shouldMessageBeFixed(message, lastConfigArrays, fixTypes) {
+    if (!message.ruleId) {
+        return fixTypes.has("directive");
+    }
+
+    const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
+
+    return Boolean(rule && rule.meta && fixTypes.has(rule.meta.type));
+}
+
 /**
  * Collect used deprecated rules.
  * @param {ConfigArray[]} usedConfigArrays The config arrays which were used.
@@ -340,9 +366,7 @@ function *iterateRuleDeprecationWarnings(usedConfigArrays) {
 
     // Flatten used configs.
     /** @type {ExtractedConfig[]} */
-    const configs = [].concat(
-        ...usedConfigArrays.map(getUsedExtractedConfigs)
-    );
+    const configs = usedConfigArrays.flatMap(getUsedExtractedConfigs);
 
     // Traverse rule configs.
     for (const config of configs) {
@@ -390,7 +414,7 @@ function isErrorMessage(message) {
  * a directory or looks like a directory (ends in `path.sep`), in which case the file
  * name will be the `cacheFile/.cache_hashOfCWD`
  *
- * if cacheFile points to a file or looks like a file then in will just use that file
+ * if cacheFile points to a file or looks like a file then it will just use that file
  * @param {string} cacheFile The name of file to be used to store the cache
  * @param {string} cwd Current working directory
  * @returns {string} the resolved path to the cache file
@@ -462,6 +486,7 @@ function getCacheFile(cacheFile, cwd) {
  * @param {string[]|null} keys The keys to assign true.
  * @param {boolean} defaultValue The default value for each property.
  * @param {string} displayName The property name which is used in error message.
+ * @throws {Error} Requires array.
  * @returns {Record<string,boolean>} The boolean map.
  */
 function toBooleanMap(keys, defaultValue, displayName) {
@@ -525,13 +550,14 @@ function createConfigDataFromOptions(options) {
 /**
  * Checks whether a directory exists at the given location
  * @param {string} resolvedPath A path from the CWD
+ * @throws {Error} As thrown by `fs.statSync` or `fs.isDirectory`.
  * @returns {boolean} `true` if a directory exists
  */
 function directoryExists(resolvedPath) {
     try {
         return fs.statSync(resolvedPath).isDirectory();
     } catch (error) {
-        if (error && error.code === "ENOENT") {
+        if (error && (error.code === "ENOENT" || error.code === "ENOTDIR")) {
             return false;
         }
         throw error;
@@ -542,13 +568,18 @@ function directoryExists(resolvedPath) {
 // Public Interface
 //------------------------------------------------------------------------------
 
+/**
+ * Core CLI.
+ */
 class CLIEngine {
 
     /**
      * Creates a new instance of the core CLI engine.
      * @param {CLIEngineOptions} providedOptions The options for this instance.
+     * @param {Object} [additionalData] Additional settings that are not CLIEngineOptions.
+     * @param {Record<string,Plugin>|null} [additionalData.preloadedPlugins] Preloaded plugins.
      */
-    constructor(providedOptions) {
+    constructor(providedOptions, { preloadedPlugins } = {}) {
         const options = Object.assign(
             Object.create(null),
             defaultOptions,
@@ -561,6 +592,13 @@ class CLIEngine {
         }
 
         const additionalPluginPool = new Map();
+
+        if (preloadedPlugins) {
+            for (const [id, plugin] of Object.entries(preloadedPlugins)) {
+                additionalPluginPool.set(id, plugin);
+            }
+        }
+
         const cacheFilePath = getCacheFile(
             options.cacheLocation || options.cacheFile,
             options.cwd
@@ -577,8 +615,8 @@ class CLIEngine {
             useEslintrc: options.useEslintrc,
             builtInRules,
             loadRules,
-            eslintRecommendedPath: path.resolve(__dirname, "../../conf/eslint-recommended.js"),
-            eslintAllPath: path.resolve(__dirname, "../../conf/eslint-all.js")
+            getEslintRecommendedConfig: () => require("../../conf/eslint-recommended.js"),
+            getEslintAllConfig: () => require("../../conf/eslint-all.js")
         });
         const fileEnumerator = new FileEnumerator({
             configArrayFactory,
@@ -589,7 +627,7 @@ class CLIEngine {
             ignore: options.ignore
         });
         const lintResultCache =
-            options.cache ? new LintResultCache(cacheFilePath) : null;
+            options.cache ? new LintResultCache(cacheFilePath, options.cacheStrategy) : null;
         const linter = new Linter({ cwd: options.cwd });
 
         /** @type {ConfigArray[]} */
@@ -622,12 +660,7 @@ class CLIEngine {
             const originalFix = (typeof options.fix === "function")
                 ? options.fix : () => true;
 
-            options.fix = message => {
-                const rule = message.ruleId && getRule(message.ruleId, lastConfigArrays);
-                const matches = rule && rule.meta && fixTypes.has(rule.meta.type);
-
-                return matches && originalFix(message);
-            };
+            options.fix = message => shouldMessageBeFixed(message, lastConfigArrays, fixTypes) && originalFix(message);
         }
     }
 
@@ -653,11 +686,13 @@ class CLIEngine {
 
         results.forEach(result => {
             const filteredMessages = result.messages.filter(isErrorMessage);
+            const filteredSuppressedMessages = result.suppressedMessages.filter(isErrorMessage);
 
             if (filteredMessages.length > 0) {
                 filtered.push({
                     ...result,
                     messages: filteredMessages,
+                    suppressedMessages: filteredSuppressedMessages,
                     errorCount: filteredMessages.length,
                     warningCount: 0,
                     fixableErrorCount: result.fixableErrorCount,
@@ -680,26 +715,6 @@ class CLIEngine {
         });
     }
 
-
-    /**
-     * Add a plugin by passing its configuration
-     * @param {string} name Name of the plugin.
-     * @param {Plugin} pluginObject Plugin configuration object.
-     * @returns {void}
-     */
-    addPlugin(name, pluginObject) {
-        const {
-            additionalPluginPool,
-            configArrayFactory,
-            lastConfigArrays
-        } = internalSlotsMap.get(this);
-
-        additionalPluginPool.set(name, pluginObject);
-        configArrayFactory.clearCache();
-        lastConfigArrays.length = 1;
-        lastConfigArrays[0] = configArrayFactory.getConfigArrayForFile();
-    }
-
     /**
      * Resolves the patterns passed into executeOnFiles() into glob-based patterns
      * for easier handling.
@@ -729,6 +744,7 @@ class CLIEngine {
     /**
      * Executes the current configuration on an array of file and directory names.
      * @param {string[]} patterns An array of file and directory names.
+     * @throws {Error} As may be thrown by `fs.unlinkSync`.
      * @returns {LintReport} The results for all files that were linted.
      */
     executeOnFiles(patterns) {
@@ -935,6 +951,7 @@ class CLIEngine {
      * This is the same logic used by the ESLint CLI executable to determine
      * configuration for each file it processes.
      * @param {string} filePath The path of the file to retrieve a config object for.
+     * @throws {Error} If filepath a directory path.
      * @returns {ConfigData} A configuration object for the file.
      */
     getConfigForFile(filePath) {
@@ -983,7 +1000,8 @@ class CLIEngine {
      * Returns the formatter representing the given format or null if the `format` is not a string.
      * @param {string} [format] The name of the format to load or the path to a
      *      custom formatter.
-     * @returns {(Function|null)} The formatter function or null if the `format` is not a string.
+     * @throws {any} As may be thrown by requiring of formatter
+     * @returns {(FormatterFunction|null)} The formatter function or null if the `format` is not a string.
      */
     getFormatter(format) {
 
@@ -1003,7 +1021,7 @@ class CLIEngine {
             let formatterPath;
 
             // if there's a slash, then it's a file (TODO: this check seems dubious for scoped npm packages)
-            if (!namespace && normalizedFormatName.indexOf("/") > -1) {
+            if (!namespace && normalizedFormatName.includes("/")) {
                 formatterPath = path.resolve(cwd, normalizedFormatName);
             } else {
                 try {
@@ -1018,7 +1036,11 @@ class CLIEngine {
             try {
                 return require(formatterPath);
             } catch (ex) {
-                ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+                if (format === "table" || format === "codeframe") {
+                    ex.message = `The ${format} formatter is no longer part of core ESLint. Install it manually with \`npm install -D eslint-formatter-${format}\``;
+                } else {
+                    ex.message = `There was a problem loading formatter: ${formatterPath}\nError: ${ex.message}`;
+                }
                 throw ex;
             }