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");
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
/** @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.
* @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.
* @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.
return messages.reduce((stat, message) => {
if (message.fatal || message.severity === 2) {
stat.errorCount++;
+ if (message.fatal) {
+ stat.fatalErrorCount++;
+ }
if (message.fix) {
stat.fixableErrorCount++;
}
return stat;
}, {
errorCount: 0,
+ fatalErrorCount: 0,
warningCount: 0,
fixableErrorCount: 0,
fixableWarningCount: 0
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
const result = {
filePath,
messages,
+ suppressedMessages: linter.getSuppressedMessages(),
...calculateStatsPerFile(messages)
};
/**
* 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
*/
message
}
],
+ suppressedMessages: [],
errorCount: 0,
+ fatalErrorCount: 0,
warningCount: 1,
fixableErrorCount: 0,
fixableWarningCount: 0
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.
// Flatten used configs.
/** @type {ExtractedConfig[]} */
- const configs = [].concat(
- ...usedConfigArrays.map(getUsedExtractedConfigs)
- );
+ const configs = usedConfigArrays.flatMap(getUsedExtractedConfigs);
// Traverse rule configs.
for (const config of configs) {
* 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
* @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) {
/**
* 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;
// 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,
}
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
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,
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[]} */
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);
}
}
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,
});
}
-
- /**
- * 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.
/**
* 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) {
* 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) {
* 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) {
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 {
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;
}