const { promisify } = require("util");
const { CLIEngine, getCLIEngineInternalSlots } = require("../cli-engine/cli-engine");
const BuiltinRules = require("../rules");
-const { getRuleSeverity } = require("../shared/config-ops");
+const {
+ Legacy: {
+ ConfigOps: {
+ getRuleSeverity
+ }
+ }
+} = require("@eslint/eslintrc");
const { version } = require("../../package.json");
//------------------------------------------------------------------------------
/** @typedef {import("../shared/types").LintMessage} LintMessage */
/** @typedef {import("../shared/types").Plugin} Plugin */
/** @typedef {import("../shared/types").Rule} Rule */
-/** @typedef {import("./load-formatter").Formatter} Formatter */
+
+/**
+ * The main formatter object.
+ * @typedef Formatter
+ * @property {function(LintResult[]): string | Promise<string>} format format function.
+ */
/**
* The options with which to configure the ESLint instance.
* @property {ConfigData} [baseConfig] Base config object, extended by all configs used with this instance
* @property {boolean} [cache] Enable result caching.
* @property {string} [cacheLocation] The cache file to use instead of .eslintcache.
+ * @property {"metadata" | "content"} [cacheStrategy] The strategy used to detect changed files.
* @property {string} [cwd] The value to use for the current working directory.
* @property {boolean} [errorOnUnmatchedPattern] If `false` then `ESLint#lintFiles()` doesn't throw even if no target files found. Defaults to `true`.
* @property {string[]} [extensions] An array of file extensions to check.
* @property {string} [ignorePath] The ignore file to use instead of .eslintignore.
* @property {ConfigData} [overrideConfig] Override config object, overrides all configs used with this instance
* @property {string} [overrideConfigFile] The configuration file to use.
- * @property {Record<string,Plugin>} [plugins] An array of plugin implementations.
+ * @property {Record<string,Plugin>|null} [plugins] Preloaded plugins. This is a map-like object, keys are plugin IDs and each value is implementation.
* @property {"error" | "warn" | "off"} [reportUnusedDisableDirectives] the severity to report unused eslint-disable directives.
* @property {string} [resolvePluginsRelativeTo] The folder where plugins should be resolved from, defaulting to the CWD.
* @property {string[]} [rulePaths] An array of directories to load custom rules from.
* @returns {boolean} `true` if `x` is valid fix type.
*/
function isFixType(x) {
- return x === "problem" || x === "suggestion" || x === "layout";
+ return x === "directive" || x === "problem" || x === "suggestion" || x === "layout";
}
/**
/**
* Validates and normalizes options for the wrapped CLIEngine instance.
* @param {ESLintOptions} options The options to process.
+ * @throws {ESLintInvalidOptionsError} If of any of a variety of type errors.
* @returns {ESLintOptions} The normalized options.
*/
function processOptions({
baseConfig = null,
cache = false,
cacheLocation = ".eslintcache",
+ cacheStrategy = "metadata",
cwd = process.cwd(),
errorOnUnmatchedPattern = true,
extensions = null, // ← should be null by default because if it's an array then it suppresses RFC20 feature.
if (!isNonEmptyString(cacheLocation)) {
errors.push("'cacheLocation' must be a non-empty string.");
}
+ if (
+ cacheStrategy !== "metadata" &&
+ cacheStrategy !== "content"
+ ) {
+ errors.push("'cacheStrategy' must be any of \"metadata\", \"content\".");
+ }
if (!isNonEmptyString(cwd) || !path.isAbsolute(cwd)) {
errors.push("'cwd' must be an absolute path.");
}
errors.push("'fix' must be a boolean or a function.");
}
if (fixTypes !== null && !isFixTypeArray(fixTypes)) {
- errors.push("'fixTypes' must be an array of any of \"problem\", \"suggestion\", and \"layout\".");
+ errors.push("'fixTypes' must be an array of any of \"directive\", \"problem\", \"suggestion\", and \"layout\".");
}
if (typeof globInputPaths !== "boolean") {
errors.push("'globInputPaths' must be a boolean.");
errors.push("'rulePaths' must be an array of non-empty strings.");
}
if (typeof useEslintrc !== "boolean") {
- errors.push("'useElintrc' must be a boolean.");
+ errors.push("'useEslintrc' must be a boolean.");
}
if (errors.length > 0) {
baseConfig,
cache,
cacheLocation,
+ cacheStrategy,
configFile: overrideConfigFile,
cwd,
errorOnUnmatchedPattern,
return 0;
}
+/**
+ * Main API.
+ */
class ESLint {
/**
*/
constructor(options = {}) {
const processedOptions = processOptions(options);
- const cliEngine = new CLIEngine(processedOptions);
+ const cliEngine = new CLIEngine(processedOptions, { preloadedPlugins: options.plugins });
const {
- additionalPluginPool,
configArrayFactory,
lastConfigArrays
} = getCLIEngineInternalSlots(cliEngine);
let updated = false;
- /*
- * Address `plugins` to add plugin implementations.
- * Operate the `additionalPluginPool` internal slot directly to avoid
- * using `addPlugin(id, plugin)` method that resets cache everytime.
- */
- if (options.plugins) {
- for (const [id, plugin] of Object.entries(options.plugins)) {
- additionalPluginPool.set(id, plugin);
- updated = true;
- }
- }
-
/*
* Address `overrideConfig` to set override config.
* Operate the `configArrayFactory` internal slot directly because this
return CLIEngine.getErrorResults(results);
}
+ /**
+ * Returns meta objects for each rule represented in the lint results.
+ * @param {LintResult[]} results The results to fetch rules meta for.
+ * @returns {Object} A mapping of ruleIds to rule meta objects.
+ */
+ getRulesMetaForResults(results) {
+
+ const resultRuleIds = new Set();
+
+ // first gather all ruleIds from all results
+
+ for (const result of results) {
+ for (const { ruleId } of result.messages) {
+ resultRuleIds.add(ruleId);
+ }
+ }
+
+ // create a map of all rules in the results
+
+ const { cliEngine } = privateMembersMap.get(this);
+ const rules = cliEngine.getRules();
+ const resultRules = new Map();
+
+ for (const [ruleId, rule] of rules) {
+ if (resultRuleIds.has(ruleId)) {
+ resultRules.set(ruleId, rule);
+ }
+ }
+
+ return createRulesMeta(resultRules);
+
+ }
+
/**
* Executes the current configuration on an array of file and directory names.
* @param {string[]} patterns An array of file and directory names.
...unknownOptions
} = options || {};
- for (const key of Object.keys(unknownOptions)) {
- throw new Error(`'options' must not include the unknown option '${key}'`);
+ const unknownOptionKeys = Object.keys(unknownOptions);
+
+ if (unknownOptionKeys.length > 0) {
+ throw new Error(`'options' must not include the unknown option(s): ${unknownOptionKeys.join(", ")}`);
}
+
if (filePath !== void 0 && !isNonEmptyString(filePath)) {
throw new Error("'options.filePath' must be a non-empty string or undefined");
}
/**
* Returns the formatter representing the given formatter name.
- * @param {string} [name] The name of the formattter to load.
+ * @param {string} [name] The name of the formatter to load.
* The following values are allowed:
* - `undefined` ... Load `stylish` builtin formatter.
* - A builtin formatter name ... Load the builtin formatter.
throw new Error("'name' must be a string");
}
- const { cliEngine } = privateMembersMap.get(this);
+ const { cliEngine, options } = privateMembersMap.get(this);
const formatter = cliEngine.getFormatter(name);
if (typeof formatter !== "function") {
/**
* The main formatter method.
* @param {LintResults[]} results The lint results to format.
- * @returns {string} The formatted lint results.
+ * @returns {string | Promise<string>} The formatted lint results.
*/
format(results) {
let rulesMeta = null;
results.sort(compareResultsByFilePath);
return formatter(results, {
+ get cwd() {
+ return options.cwd;
+ },
get rulesMeta() {
if (!rulesMeta) {
rulesMeta = createRulesMeta(cliEngine.getRules());