forked from public/fvtt-cthulhu-eternal
Initial import with skill sheet working
This commit is contained in:
719
node_modules/eslint/lib/config/config-loader.js
generated
vendored
Normal file
719
node_modules/eslint/lib/config/config-loader.js
generated
vendored
Normal file
@@ -0,0 +1,719 @@
|
||||
/**
|
||||
* @fileoverview Utility to load config files
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const path = require("node:path");
|
||||
const fs = require("node:fs/promises");
|
||||
const findUp = require("find-up");
|
||||
const { pathToFileURL } = require("node:url");
|
||||
const debug = require("debug")("eslint:config-loader");
|
||||
const { FlatConfigArray } = require("./flat-config-array");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef {import("../shared/types").FlatConfigObject} FlatConfigObject
|
||||
* @typedef {import("../shared/types").FlatConfigArray} FlatConfigArray
|
||||
* @typedef {Object} ConfigLoaderOptions
|
||||
* @property {string|false|undefined} configFile The path to the config file to use.
|
||||
* @property {string} cwd The current working directory.
|
||||
* @property {boolean} ignoreEnabled Indicates if ignore patterns should be honored.
|
||||
* @property {FlatConfigArray} [baseConfig] The base config to use.
|
||||
* @property {Array<FlatConfigObject>} [defaultConfigs] The default configs to use.
|
||||
* @property {Array<string>} [ignorePatterns] The ignore patterns to use.
|
||||
* @property {FlatConfigObject|Array<FlatConfigObject>} overrideConfig The override config to use.
|
||||
* @property {boolean} allowTS Indicates if TypeScript configuration files are allowed.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const FLAT_CONFIG_FILENAMES = [
|
||||
"eslint.config.js",
|
||||
"eslint.config.mjs",
|
||||
"eslint.config.cjs"
|
||||
];
|
||||
|
||||
const TS_FLAT_CONFIG_FILENAMES = [
|
||||
"eslint.config.ts",
|
||||
"eslint.config.mts",
|
||||
"eslint.config.cts"
|
||||
];
|
||||
|
||||
const importedConfigFileModificationTime = new Map();
|
||||
|
||||
/**
|
||||
* Asserts that the given file path is valid.
|
||||
* @param {string} filePath The file path to check.
|
||||
* @returns {void}
|
||||
* @throws {Error} If `filePath` is not a non-empty string.
|
||||
*/
|
||||
function assertValidFilePath(filePath) {
|
||||
if (!filePath || typeof filePath !== "string") {
|
||||
throw new Error("'filePath' must be a non-empty string");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a configuration exists. A configuration exists if any
|
||||
* of the following are true:
|
||||
* - `configFilePath` is defined.
|
||||
* - `useConfigFile` is `false`.
|
||||
* @param {string|undefined} configFilePath The path to the config file.
|
||||
* @param {ConfigLoaderOptions} loaderOptions The options to use when loading configuration files.
|
||||
* @returns {void}
|
||||
* @throws {Error} If no configuration exists.
|
||||
*/
|
||||
function assertConfigurationExists(configFilePath, loaderOptions) {
|
||||
|
||||
const {
|
||||
configFile: useConfigFile
|
||||
} = loaderOptions;
|
||||
|
||||
if (!configFilePath && useConfigFile !== false) {
|
||||
const error = new Error("Could not find config file.");
|
||||
|
||||
error.messageTemplate = "config-file-missing";
|
||||
throw error;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the file is a TypeScript file.
|
||||
* @param {string} filePath The file path to check.
|
||||
* @returns {boolean} `true` if the file is a TypeScript file, `false` if it's not.
|
||||
*/
|
||||
function isFileTS(filePath) {
|
||||
const fileExtension = path.extname(filePath);
|
||||
|
||||
return /^\.[mc]?ts$/u.test(fileExtension);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ESLint is running in Bun.
|
||||
* @returns {boolean} `true` if the ESLint is running Bun, `false` if it's not.
|
||||
*/
|
||||
function isRunningInBun() {
|
||||
return !!globalThis.Bun;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if ESLint is running in Deno.
|
||||
* @returns {boolean} `true` if the ESLint is running in Deno, `false` if it's not.
|
||||
*/
|
||||
function isRunningInDeno() {
|
||||
return !!globalThis.Deno;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the config array from the given filename.
|
||||
* @param {string} filePath The filename to load from.
|
||||
* @param {boolean} allowTS Indicates if TypeScript configuration files are allowed.
|
||||
* @returns {Promise<any>} The config loaded from the config file.
|
||||
*/
|
||||
async function loadConfigFile(filePath, allowTS) {
|
||||
|
||||
debug(`Loading config from ${filePath}`);
|
||||
|
||||
const fileURL = pathToFileURL(filePath);
|
||||
|
||||
debug(`Config file URL is ${fileURL}`);
|
||||
|
||||
const mtime = (await fs.stat(filePath)).mtime.getTime();
|
||||
|
||||
/*
|
||||
* Append a query with the config file's modification time (`mtime`) in order
|
||||
* to import the current version of the config file. Without the query, `import()` would
|
||||
* cache the config file module by the pathname only, and then always return
|
||||
* the same version (the one that was actual when the module was imported for the first time).
|
||||
*
|
||||
* This ensures that the config file module is loaded and executed again
|
||||
* if it has been changed since the last time it was imported.
|
||||
* If it hasn't been changed, `import()` will just return the cached version.
|
||||
*
|
||||
* Note that we should not overuse queries (e.g., by appending the current time
|
||||
* to always reload the config file module) as that could cause memory leaks
|
||||
* because entries are never removed from the import cache.
|
||||
*/
|
||||
fileURL.searchParams.append("mtime", mtime);
|
||||
|
||||
/*
|
||||
* With queries, we can bypass the import cache. However, when import-ing a CJS module,
|
||||
* Node.js uses the require infrastructure under the hood. That includes the require cache,
|
||||
* which caches the config file module by its file path (queries have no effect).
|
||||
* Therefore, we also need to clear the require cache before importing the config file module.
|
||||
* In order to get the same behavior with ESM and CJS config files, in particular - to reload
|
||||
* the config file only if it has been changed, we track file modification times and clear
|
||||
* the require cache only if the file has been changed.
|
||||
*/
|
||||
if (importedConfigFileModificationTime.get(filePath) !== mtime) {
|
||||
delete require.cache[filePath];
|
||||
}
|
||||
|
||||
const isTS = isFileTS(filePath);
|
||||
const isBun = isRunningInBun();
|
||||
const isDeno = isRunningInDeno();
|
||||
|
||||
/*
|
||||
* If we are dealing with a TypeScript file, then we need to use `jiti` to load it
|
||||
* in Node.js. Deno and Bun both allow native importing of TypeScript files.
|
||||
*
|
||||
* When Node.js supports native TypeScript imports, we can remove this check.
|
||||
*/
|
||||
if (allowTS && isTS && !isDeno && !isBun) {
|
||||
|
||||
// eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing
|
||||
const { createJiti } = await ConfigLoader.loadJiti().catch(() => {
|
||||
throw new Error("The 'jiti' library is required for loading TypeScript configuration files. Make sure to install it.");
|
||||
});
|
||||
|
||||
// `createJiti` was added in jiti v2.
|
||||
if (typeof createJiti !== "function") {
|
||||
throw new Error("You are using an outdated version of the 'jiti' library. Please update to the latest version of 'jiti' to ensure compatibility and access to the latest features.");
|
||||
}
|
||||
|
||||
/*
|
||||
* Disabling `moduleCache` allows us to reload a
|
||||
* config file when the last modified timestamp changes.
|
||||
*/
|
||||
|
||||
const jiti = createJiti(__filename, { moduleCache: false, interopDefault: false });
|
||||
const config = await jiti.import(fileURL.href);
|
||||
|
||||
importedConfigFileModificationTime.set(filePath, mtime);
|
||||
|
||||
return config?.default ?? config;
|
||||
}
|
||||
|
||||
|
||||
// fallback to normal runtime behavior
|
||||
|
||||
const config = (await import(fileURL)).default;
|
||||
|
||||
importedConfigFileModificationTime.set(filePath, mtime);
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Encapsulates the loading and caching of configuration files when looking up
|
||||
* from the file being linted.
|
||||
*/
|
||||
class ConfigLoader {
|
||||
|
||||
/**
|
||||
* Map of config file paths to the config arrays for those directories.
|
||||
* @type {Map<string, FlatConfigArray|Promise<FlatConfigArray>>}
|
||||
*/
|
||||
#configArrays = new Map();
|
||||
|
||||
/**
|
||||
* Map of absolute directory names to the config file paths for those directories.
|
||||
* @type {Map<string, {configFilePath:string,basePath:string}|Promise<{configFilePath:string,basePath:string}>>}
|
||||
*/
|
||||
#configFilePaths = new Map();
|
||||
|
||||
/**
|
||||
* The options to use when loading configuration files.
|
||||
* @type {ConfigLoaderOptions}
|
||||
*/
|
||||
#options;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
||||
*/
|
||||
constructor(options) {
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which config file to use. This is determined by seeing if an
|
||||
* override config file was specified, and if so, using it; otherwise, as long
|
||||
* as override config file is not explicitly set to `false`, it will search
|
||||
* upwards from `fromDirectory` for a file named `eslint.config.js`.
|
||||
* @param {string} fromDirectory The directory from which to start searching.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
async #locateConfigFileToUse(fromDirectory) {
|
||||
|
||||
// check cache first
|
||||
if (this.#configFilePaths.has(fromDirectory)) {
|
||||
return this.#configFilePaths.get(fromDirectory);
|
||||
}
|
||||
|
||||
const resultPromise = ConfigLoader.locateConfigFileToUse({
|
||||
useConfigFile: this.#options.configFile,
|
||||
cwd: this.#options.cwd,
|
||||
fromDirectory,
|
||||
allowTS: this.#options.allowTS
|
||||
});
|
||||
|
||||
// ensure `ConfigLoader.locateConfigFileToUse` is called only once for `fromDirectory`
|
||||
this.#configFilePaths.set(fromDirectory, resultPromise);
|
||||
|
||||
// Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
|
||||
const result = await resultPromise;
|
||||
|
||||
this.#configFilePaths.set(fromDirectory, result);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config array for this run based on inputs.
|
||||
* @param {string} configFilePath The absolute path to the config file to use if not overridden.
|
||||
* @param {string} basePath The base path to use for relative paths in the config file.
|
||||
* @returns {Promise<FlatConfigArray>} The config array for `eslint`.
|
||||
*/
|
||||
async #calculateConfigArray(configFilePath, basePath) {
|
||||
|
||||
// check for cached version first
|
||||
if (this.#configArrays.has(configFilePath)) {
|
||||
return this.#configArrays.get(configFilePath);
|
||||
}
|
||||
|
||||
const configsPromise = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options);
|
||||
|
||||
// ensure `ConfigLoader.calculateConfigArray` is called only once for `configFilePath`
|
||||
this.#configArrays.set(configFilePath, configsPromise);
|
||||
|
||||
// Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
|
||||
const configs = await configsPromise;
|
||||
|
||||
this.#configArrays.set(configFilePath, configs);
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the config file path for the given directory or file. This will either use
|
||||
* the override config file that was specified in the constructor options or
|
||||
* search for a config file from the directory.
|
||||
* @param {string} fileOrDirPath The file or directory path to get the config file path for.
|
||||
* @returns {Promise<string|undefined>} The config file path or `undefined` if not found.
|
||||
* @throws {Error} If `fileOrDirPath` is not a non-empty string.
|
||||
* @throws {Error} If `fileOrDirPath` is not an absolute path.
|
||||
*/
|
||||
async findConfigFileForPath(fileOrDirPath) {
|
||||
|
||||
assertValidFilePath(fileOrDirPath);
|
||||
|
||||
const absoluteDirPath = path.resolve(this.#options.cwd, path.dirname(fileOrDirPath));
|
||||
const { configFilePath } = await this.#locateConfigFileToUse(absoluteDirPath);
|
||||
|
||||
return configFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given file based on the CLI options.
|
||||
* 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 or directory to retrieve config for.
|
||||
* @returns {Promise<ConfigData|undefined>} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
* @throws {Error} If no configuration for `filePath` exists.
|
||||
*/
|
||||
async loadConfigArrayForFile(filePath) {
|
||||
|
||||
assertValidFilePath(filePath);
|
||||
|
||||
debug(`Calculating config for file ${filePath}`);
|
||||
|
||||
const configFilePath = await this.findConfigFileForPath(filePath);
|
||||
|
||||
assertConfigurationExists(configFilePath, this.#options);
|
||||
|
||||
return this.loadConfigArrayForDirectory(filePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given directory based on the CLI options.
|
||||
* This is the same logic used by the ESLint CLI executable to determine
|
||||
* configuration for each file it processes.
|
||||
* @param {string} dirPath The path of the directory to retrieve config for.
|
||||
* @returns {Promise<ConfigData|undefined>} A configuration object for the directory
|
||||
* or `undefined` if there is no configuration data for the directory.
|
||||
*/
|
||||
async loadConfigArrayForDirectory(dirPath) {
|
||||
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
debug(`Calculating config for directory ${dirPath}`);
|
||||
|
||||
const absoluteDirPath = path.resolve(this.#options.cwd, path.dirname(dirPath));
|
||||
const { configFilePath, basePath } = await this.#locateConfigFileToUse(absoluteDirPath);
|
||||
|
||||
debug(`Using config file ${configFilePath} and base path ${basePath}`);
|
||||
return this.#calculateConfigArray(configFilePath, basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration array for the given file based on the CLI options.
|
||||
* This is a synchronous operation and does not read any files from disk. It's
|
||||
* intended to be used in locations where we know the config file has already
|
||||
* been loaded and we just need to get the configuration for a file.
|
||||
* @param {string} filePath The path of the file to retrieve a config object for.
|
||||
* @returns {ConfigData|undefined} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
* @throws {Error} If `filePath` is not a non-empty string.
|
||||
* @throws {Error} If `filePath` is not an absolute path.
|
||||
* @throws {Error} If the config file was not already loaded.
|
||||
*/
|
||||
getCachedConfigArrayForFile(filePath) {
|
||||
assertValidFilePath(filePath);
|
||||
|
||||
debug(`Looking up cached config for ${filePath}`);
|
||||
|
||||
return this.getCachedConfigArrayForPath(path.dirname(filePath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration array for the given directory based on the CLI options.
|
||||
* This is a synchronous operation and does not read any files from disk. It's
|
||||
* intended to be used in locations where we know the config file has already
|
||||
* been loaded and we just need to get the configuration for a file.
|
||||
* @param {string} fileOrDirPath The path of the directory to retrieve a config object for.
|
||||
* @returns {ConfigData|undefined} A configuration object for the directory
|
||||
* or `undefined` if there is no configuration data for the directory.
|
||||
* @throws {Error} If `dirPath` is not a non-empty string.
|
||||
* @throws {Error} If `dirPath` is not an absolute path.
|
||||
* @throws {Error} If the config file was not already loaded.
|
||||
*/
|
||||
getCachedConfigArrayForPath(fileOrDirPath) {
|
||||
assertValidFilePath(fileOrDirPath);
|
||||
|
||||
debug(`Looking up cached config for ${fileOrDirPath}`);
|
||||
|
||||
const absoluteDirPath = path.resolve(this.#options.cwd, fileOrDirPath);
|
||||
|
||||
if (!this.#configFilePaths.has(absoluteDirPath)) {
|
||||
throw new Error(`Could not find config file for ${fileOrDirPath}`);
|
||||
}
|
||||
|
||||
const configFilePathInfo = this.#configFilePaths.get(absoluteDirPath);
|
||||
|
||||
if (typeof configFilePathInfo.then === "function") {
|
||||
throw new Error(`Config file path for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`);
|
||||
}
|
||||
|
||||
const { configFilePath } = configFilePathInfo;
|
||||
|
||||
const configArray = this.#configArrays.get(configFilePath);
|
||||
|
||||
if (!configArray || typeof configArray.then === "function") {
|
||||
throw new Error(`Config array for ${fileOrDirPath} has not yet been calculated or an error occurred during the calculation`);
|
||||
}
|
||||
|
||||
return configArray;
|
||||
}
|
||||
|
||||
/**
|
||||
* Used to import the jiti dependency. This method is exposed internally for testing purposes.
|
||||
* @returns {Promise<Record<string, unknown>>} A promise that fulfills with a module object
|
||||
* or rejects with an error if jiti is not found.
|
||||
*/
|
||||
static loadJiti() {
|
||||
return import("jiti");
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which config file to use. This is determined by seeing if an
|
||||
* override config file was specified, and if so, using it; otherwise, as long
|
||||
* as override config file is not explicitly set to `false`, it will search
|
||||
* upwards from `fromDirectory` for a file named `eslint.config.js`.
|
||||
* This method is exposed internally for testing purposes.
|
||||
* @param {Object} [options] the options object
|
||||
* @param {string|false|undefined} options.useConfigFile The path to the config file to use.
|
||||
* @param {string} options.cwd Path to a directory that should be considered as the current working directory.
|
||||
* @param {string} [options.fromDirectory] The directory from which to start searching. Defaults to `cwd`.
|
||||
* @param {boolean} options.allowTS Indicates if TypeScript configuration files are allowed.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
static async locateConfigFileToUse({ useConfigFile, cwd, fromDirectory = cwd, allowTS }) {
|
||||
|
||||
const configFilenames = allowTS
|
||||
? [...FLAT_CONFIG_FILENAMES, ...TS_FLAT_CONFIG_FILENAMES]
|
||||
: FLAT_CONFIG_FILENAMES;
|
||||
|
||||
// determine where to load config file from
|
||||
let configFilePath;
|
||||
let basePath = cwd;
|
||||
|
||||
if (typeof useConfigFile === "string") {
|
||||
debug(`Override config file path is ${useConfigFile}`);
|
||||
configFilePath = path.resolve(cwd, useConfigFile);
|
||||
basePath = cwd;
|
||||
} else if (useConfigFile !== false) {
|
||||
debug("Searching for eslint.config.js");
|
||||
configFilePath = await findUp(
|
||||
configFilenames,
|
||||
{ cwd: fromDirectory }
|
||||
);
|
||||
|
||||
if (configFilePath) {
|
||||
basePath = path.dirname(configFilePath);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return {
|
||||
configFilePath,
|
||||
basePath
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config array for this run based on inputs.
|
||||
* This method is exposed internally for testing purposes.
|
||||
* @param {string} configFilePath The absolute path to the config file to use if not overridden.
|
||||
* @param {string} basePath The base path to use for relative paths in the config file.
|
||||
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
||||
* @returns {Promise<FlatConfigArray>} The config array for `eslint`.
|
||||
*/
|
||||
static async calculateConfigArray(configFilePath, basePath, options) {
|
||||
|
||||
const {
|
||||
cwd,
|
||||
baseConfig,
|
||||
ignoreEnabled,
|
||||
ignorePatterns,
|
||||
overrideConfig,
|
||||
defaultConfigs = [],
|
||||
allowTS
|
||||
} = options;
|
||||
|
||||
debug(`Calculating config array from config file ${configFilePath} and base path ${basePath}`);
|
||||
|
||||
const configs = new FlatConfigArray(baseConfig || [], { basePath, shouldIgnore: ignoreEnabled });
|
||||
|
||||
// load config file
|
||||
if (configFilePath) {
|
||||
|
||||
debug(`Loading config file ${configFilePath}`);
|
||||
const fileConfig = await loadConfigFile(configFilePath, allowTS);
|
||||
|
||||
if (Array.isArray(fileConfig)) {
|
||||
configs.push(...fileConfig);
|
||||
} else {
|
||||
configs.push(fileConfig);
|
||||
}
|
||||
}
|
||||
|
||||
// add in any configured defaults
|
||||
configs.push(...defaultConfigs);
|
||||
|
||||
// append command line ignore patterns
|
||||
if (ignorePatterns && ignorePatterns.length > 0) {
|
||||
|
||||
let relativeIgnorePatterns;
|
||||
|
||||
/*
|
||||
* If the config file basePath is different than the cwd, then
|
||||
* the ignore patterns won't work correctly. Here, we adjust the
|
||||
* ignore pattern to include the correct relative path. Patterns
|
||||
* passed as `ignorePatterns` are relative to the cwd, whereas
|
||||
* the config file basePath can be an ancestor of the cwd.
|
||||
*/
|
||||
if (basePath === cwd) {
|
||||
relativeIgnorePatterns = ignorePatterns;
|
||||
} else {
|
||||
|
||||
// relative path must only have Unix-style separators
|
||||
const relativeIgnorePath = path.relative(basePath, cwd).replace(/\\/gu, "/");
|
||||
|
||||
relativeIgnorePatterns = ignorePatterns.map(pattern => {
|
||||
const negated = pattern.startsWith("!");
|
||||
const basePattern = negated ? pattern.slice(1) : pattern;
|
||||
|
||||
return (negated ? "!" : "") +
|
||||
path.posix.join(relativeIgnorePath, basePattern);
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* Ignore patterns are added to the end of the config array
|
||||
* so they can override default ignores.
|
||||
*/
|
||||
configs.push({
|
||||
ignores: relativeIgnorePatterns
|
||||
});
|
||||
}
|
||||
|
||||
if (overrideConfig) {
|
||||
if (Array.isArray(overrideConfig)) {
|
||||
configs.push(...overrideConfig);
|
||||
} else {
|
||||
configs.push(overrideConfig);
|
||||
}
|
||||
}
|
||||
|
||||
await configs.normalize();
|
||||
|
||||
return configs;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Encapsulates the loading and caching of configuration files when looking up
|
||||
* from the current working directory.
|
||||
*/
|
||||
class LegacyConfigLoader extends ConfigLoader {
|
||||
|
||||
/**
|
||||
* The options to use when loading configuration files.
|
||||
* @type {ConfigLoaderOptions}
|
||||
*/
|
||||
#options;
|
||||
|
||||
/**
|
||||
* The cached config file path for this instance.
|
||||
* @type {Promise<{configFilePath:string,basePath:string}|undefined>}
|
||||
*/
|
||||
#configFilePath;
|
||||
|
||||
/**
|
||||
* The cached config array for this instance.
|
||||
* @type {FlatConfigArray|Promise<FlatConfigArray>}
|
||||
*/
|
||||
#configArray;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {ConfigLoaderOptions} options The options to use when loading configuration files.
|
||||
*/
|
||||
constructor(options) {
|
||||
super(options);
|
||||
this.#options = options;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines which config file to use. This is determined by seeing if an
|
||||
* override config file was specified, and if so, using it; otherwise, as long
|
||||
* as override config file is not explicitly set to `false`, it will search
|
||||
* upwards from the cwd for a file named `eslint.config.js`.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
#locateConfigFileToUse() {
|
||||
if (!this.#configFilePath) {
|
||||
this.#configFilePath = ConfigLoader.locateConfigFileToUse({
|
||||
useConfigFile: this.#options.configFile,
|
||||
cwd: this.#options.cwd,
|
||||
allowTS: this.#options.allowTS
|
||||
});
|
||||
}
|
||||
|
||||
return this.#configFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the config array for this run based on inputs.
|
||||
* @param {string} configFilePath The absolute path to the config file to use if not overridden.
|
||||
* @param {string} basePath The base path to use for relative paths in the config file.
|
||||
* @returns {Promise<FlatConfigArray>} The config array for `eslint`.
|
||||
*/
|
||||
async #calculateConfigArray(configFilePath, basePath) {
|
||||
|
||||
// check for cached version first
|
||||
if (this.#configArray) {
|
||||
return this.#configArray;
|
||||
}
|
||||
|
||||
// ensure `ConfigLoader.calculateConfigArray` is called only once
|
||||
this.#configArray = ConfigLoader.calculateConfigArray(configFilePath, basePath, this.#options);
|
||||
|
||||
// Unwrap the promise. This is primarily for the sync `getCachedConfigArrayForPath` method.
|
||||
this.#configArray = await this.#configArray;
|
||||
|
||||
return this.#configArray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Returns the config file path for the given directory. This will either use
|
||||
* the override config file that was specified in the constructor options or
|
||||
* search for a config file from the directory of the file being linted.
|
||||
* @param {string} dirPath The directory path to get the config file path for.
|
||||
* @returns {Promise<string|undefined>} The config file path or `undefined` if not found.
|
||||
* @throws {Error} If `fileOrDirPath` is not a non-empty string.
|
||||
* @throws {Error} If `fileOrDirPath` is not an absolute path.
|
||||
*/
|
||||
async findConfigFileForPath(dirPath) {
|
||||
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
const { configFilePath } = await this.#locateConfigFileToUse();
|
||||
|
||||
return configFilePath;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration object for the given file based on the CLI options.
|
||||
* This is the same logic used by the ESLint CLI executable to determine
|
||||
* configuration for each file it processes.
|
||||
* @param {string} dirPath The path of the directory to retrieve config for.
|
||||
* @returns {Promise<ConfigData|undefined>} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
*/
|
||||
async loadConfigArrayForDirectory(dirPath) {
|
||||
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
debug(`[Legacy]: Calculating config for ${dirPath}`);
|
||||
|
||||
const { configFilePath, basePath } = await this.#locateConfigFileToUse();
|
||||
|
||||
debug(`[Legacy]: Using config file ${configFilePath} and base path ${basePath}`);
|
||||
return this.#calculateConfigArray(configFilePath, basePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a configuration array for the given directory based on the CLI options.
|
||||
* This is a synchronous operation and does not read any files from disk. It's
|
||||
* intended to be used in locations where we know the config file has already
|
||||
* been loaded and we just need to get the configuration for a file.
|
||||
* @param {string} dirPath The path of the directory to retrieve a config object for.
|
||||
* @returns {ConfigData|undefined} A configuration object for the file
|
||||
* or `undefined` if there is no configuration data for the file.
|
||||
* @throws {Error} If `dirPath` is not a non-empty string.
|
||||
* @throws {Error} If `dirPath` is not an absolute path.
|
||||
* @throws {Error} If the config file was not already loaded.
|
||||
*/
|
||||
getCachedConfigArrayForPath(dirPath) {
|
||||
assertValidFilePath(dirPath);
|
||||
|
||||
debug(`[Legacy]: Looking up cached config for ${dirPath}`);
|
||||
|
||||
if (!this.#configArray) {
|
||||
throw new Error(`Could not find config file for ${dirPath}`);
|
||||
}
|
||||
|
||||
if (typeof this.#configArray.then === "function") {
|
||||
throw new Error(`Config array for ${dirPath} has not yet been calculated or an error occurred during the calculation`);
|
||||
}
|
||||
|
||||
return this.#configArray;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { ConfigLoader, LegacyConfigLoader };
|
||||
297
node_modules/eslint/lib/config/config.js
generated
vendored
Normal file
297
node_modules/eslint/lib/config/config.js
generated
vendored
Normal file
@@ -0,0 +1,297 @@
|
||||
/**
|
||||
* @fileoverview The `Config` class
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { deepMergeArrays } = require("../shared/deep-merge-arrays");
|
||||
const { getRuleFromConfig } = require("./flat-config-helpers");
|
||||
const { flatConfigSchema, hasMethod } = require("./flat-config-schema");
|
||||
const { RuleValidator } = require("./rule-validator");
|
||||
const { ObjectSchema } = require("@eslint/config-array");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const ruleValidator = new RuleValidator();
|
||||
|
||||
const severities = new Map([
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
["off", 0],
|
||||
["warn", 1],
|
||||
["error", 2]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
|
||||
* @param {string} identifier The identifier to parse.
|
||||
* @returns {{objectName: string, pluginName: string}} The parts of the plugin
|
||||
* name.
|
||||
*/
|
||||
function splitPluginIdentifier(identifier) {
|
||||
const parts = identifier.split("/");
|
||||
|
||||
return {
|
||||
objectName: parts.pop(),
|
||||
pluginName: parts.join("/")
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of an object in the config by reading its `meta` key.
|
||||
* @param {Object} object The object to check.
|
||||
* @returns {string?} The name of the object if found or `null` if there
|
||||
* is no name.
|
||||
*/
|
||||
function getObjectId(object) {
|
||||
|
||||
// first check old-style name
|
||||
let name = object.name;
|
||||
|
||||
if (!name) {
|
||||
|
||||
if (!object.meta) {
|
||||
return null;
|
||||
}
|
||||
|
||||
name = object.meta.name;
|
||||
|
||||
if (!name) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// now check for old-style version
|
||||
let version = object.version;
|
||||
|
||||
if (!version) {
|
||||
version = object.meta && object.meta.version;
|
||||
}
|
||||
|
||||
// if there's a version then append that
|
||||
if (version) {
|
||||
return `${name}@${version}`;
|
||||
}
|
||||
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts a languageOptions object to a JSON representation.
|
||||
* @param {Record<string, any>} languageOptions The options to create a JSON
|
||||
* representation of.
|
||||
* @param {string} objectKey The key of the object being converted.
|
||||
* @returns {Record<string, any>} The JSON representation of the languageOptions.
|
||||
* @throws {TypeError} If a function is found in the languageOptions.
|
||||
*/
|
||||
function languageOptionsToJSON(languageOptions, objectKey = "languageOptions") {
|
||||
|
||||
const result = {};
|
||||
|
||||
for (const [key, value] of Object.entries(languageOptions)) {
|
||||
if (value) {
|
||||
if (typeof value === "object") {
|
||||
const name = getObjectId(value);
|
||||
|
||||
if (name && hasMethod(value)) {
|
||||
result[key] = name;
|
||||
} else {
|
||||
result[key] = languageOptionsToJSON(value, key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
if (typeof value === "function") {
|
||||
throw new TypeError(`Cannot serialize key "${key}" in ${objectKey}: Function values are not supported.`);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Represents a normalized configuration object.
|
||||
*/
|
||||
class Config {
|
||||
|
||||
/**
|
||||
* The name to use for the language when serializing to JSON.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
#languageName;
|
||||
|
||||
/**
|
||||
* The name to use for the processor when serializing to JSON.
|
||||
* @type {string|undefined}
|
||||
*/
|
||||
#processorName;
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Object} config The configuration object.
|
||||
*/
|
||||
constructor(config) {
|
||||
|
||||
const { plugins, language, languageOptions, processor, ...otherKeys } = config;
|
||||
|
||||
// Validate config object
|
||||
const schema = new ObjectSchema(flatConfigSchema);
|
||||
|
||||
schema.validate(config);
|
||||
|
||||
// first, copy all the other keys over
|
||||
Object.assign(this, otherKeys);
|
||||
|
||||
// ensure that a language is specified
|
||||
if (!language) {
|
||||
throw new TypeError("Key 'language' is required.");
|
||||
}
|
||||
|
||||
// copy the rest over
|
||||
this.plugins = plugins;
|
||||
this.language = language;
|
||||
|
||||
// Check language value
|
||||
const { pluginName: languagePluginName, objectName: localLanguageName } = splitPluginIdentifier(language);
|
||||
|
||||
this.#languageName = language;
|
||||
|
||||
if (!plugins || !plugins[languagePluginName] || !plugins[languagePluginName].languages || !plugins[languagePluginName].languages[localLanguageName]) {
|
||||
throw new TypeError(`Key "language": Could not find "${localLanguageName}" in plugin "${languagePluginName}".`);
|
||||
}
|
||||
|
||||
this.language = plugins[languagePluginName].languages[localLanguageName];
|
||||
|
||||
if (this.language.defaultLanguageOptions ?? languageOptions) {
|
||||
this.languageOptions = flatConfigSchema.languageOptions.merge(
|
||||
this.language.defaultLanguageOptions,
|
||||
languageOptions
|
||||
);
|
||||
} else {
|
||||
this.languageOptions = {};
|
||||
}
|
||||
|
||||
// Validate language options
|
||||
try {
|
||||
this.language.validateLanguageOptions(this.languageOptions);
|
||||
} catch (error) {
|
||||
throw new TypeError(`Key "languageOptions": ${error.message}`, { cause: error });
|
||||
}
|
||||
|
||||
// Normalize language options if necessary
|
||||
if (this.language.normalizeLanguageOptions) {
|
||||
this.languageOptions = this.language.normalizeLanguageOptions(this.languageOptions);
|
||||
}
|
||||
|
||||
// Check processor value
|
||||
if (processor) {
|
||||
this.processor = processor;
|
||||
|
||||
if (typeof processor === "string") {
|
||||
const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
|
||||
|
||||
this.#processorName = processor;
|
||||
|
||||
if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
|
||||
throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
|
||||
}
|
||||
|
||||
this.processor = plugins[pluginName].processors[localProcessorName];
|
||||
} else if (typeof processor === "object") {
|
||||
this.#processorName = getObjectId(processor);
|
||||
this.processor = processor;
|
||||
} else {
|
||||
throw new TypeError("Key 'processor' must be a string or an object.");
|
||||
}
|
||||
}
|
||||
|
||||
// Process the rules
|
||||
if (this.rules) {
|
||||
this.#normalizeRulesConfig();
|
||||
ruleValidator.validate(this);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the configuration to a JSON representation.
|
||||
* @returns {Record<string, any>} The JSON representation of the configuration.
|
||||
* @throws {Error} If the configuration cannot be serialized.
|
||||
*/
|
||||
toJSON() {
|
||||
|
||||
if (this.processor && !this.#processorName) {
|
||||
throw new Error("Could not serialize processor object (missing 'meta' object).");
|
||||
}
|
||||
|
||||
if (!this.#languageName) {
|
||||
throw new Error("Could not serialize language object (missing 'meta' object).");
|
||||
}
|
||||
|
||||
return {
|
||||
...this,
|
||||
plugins: Object.entries(this.plugins).map(([namespace, plugin]) => {
|
||||
|
||||
const pluginId = getObjectId(plugin);
|
||||
|
||||
if (!pluginId) {
|
||||
return namespace;
|
||||
}
|
||||
|
||||
return `${namespace}:${pluginId}`;
|
||||
}),
|
||||
language: this.#languageName,
|
||||
languageOptions: languageOptionsToJSON(this.languageOptions),
|
||||
processor: this.#processorName
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the rules configuration. Ensures that each rule config is
|
||||
* an array and that the severity is a number. Applies meta.defaultOptions.
|
||||
* This function modifies `this.rules`.
|
||||
* @returns {void}
|
||||
*/
|
||||
#normalizeRulesConfig() {
|
||||
for (const [ruleId, originalConfig] of Object.entries(this.rules)) {
|
||||
|
||||
// ensure rule config is an array
|
||||
let ruleConfig = Array.isArray(originalConfig)
|
||||
? originalConfig
|
||||
: [originalConfig];
|
||||
|
||||
// normalize severity
|
||||
ruleConfig[0] = severities.get(ruleConfig[0]);
|
||||
|
||||
const rule = getRuleFromConfig(ruleId, this);
|
||||
|
||||
// apply meta.defaultOptions
|
||||
const slicedOptions = ruleConfig.slice(1);
|
||||
const mergedOptions = deepMergeArrays(rule?.meta?.defaultOptions, slicedOptions);
|
||||
|
||||
if (mergedOptions.length) {
|
||||
ruleConfig = [ruleConfig[0], ...mergedOptions];
|
||||
}
|
||||
|
||||
this.rules[ruleId] = ruleConfig;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Config };
|
||||
69
node_modules/eslint/lib/config/default-config.js
generated
vendored
Normal file
69
node_modules/eslint/lib/config/default-config.js
generated
vendored
Normal file
@@ -0,0 +1,69 @@
|
||||
/**
|
||||
* @fileoverview Default configuration
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const Rules = require("../rules");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
exports.defaultConfig = Object.freeze([
|
||||
{
|
||||
plugins: {
|
||||
"@": {
|
||||
|
||||
languages: {
|
||||
js: require("../languages/js")
|
||||
},
|
||||
|
||||
/*
|
||||
* Because we try to delay loading rules until absolutely
|
||||
* necessary, a proxy allows us to hook into the lazy-loading
|
||||
* aspect of the rules map while still keeping all of the
|
||||
* relevant configuration inside of the config array.
|
||||
*/
|
||||
rules: new Proxy({}, {
|
||||
get(target, property) {
|
||||
return Rules.get(property);
|
||||
},
|
||||
|
||||
has(target, property) {
|
||||
return Rules.has(property);
|
||||
}
|
||||
})
|
||||
}
|
||||
},
|
||||
language: "@/js",
|
||||
linterOptions: {
|
||||
reportUnusedDisableDirectives: 1
|
||||
}
|
||||
},
|
||||
|
||||
// default ignores are listed here
|
||||
{
|
||||
ignores: [
|
||||
"**/node_modules/",
|
||||
".git/"
|
||||
]
|
||||
},
|
||||
|
||||
// intentionally empty config to ensure these files are globbed by default
|
||||
{
|
||||
files: ["**/*.js", "**/*.mjs"]
|
||||
},
|
||||
{
|
||||
files: ["**/*.cjs"],
|
||||
languageOptions: {
|
||||
sourceType: "commonjs",
|
||||
ecmaVersion: "latest"
|
||||
}
|
||||
}
|
||||
]);
|
||||
222
node_modules/eslint/lib/config/flat-config-array.js
generated
vendored
Normal file
222
node_modules/eslint/lib/config/flat-config-array.js
generated
vendored
Normal file
@@ -0,0 +1,222 @@
|
||||
/**
|
||||
* @fileoverview Flat Config Array
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
|
||||
const { flatConfigSchema } = require("./flat-config-schema");
|
||||
const { defaultConfig } = require("./default-config");
|
||||
const { Config } = require("./config");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Fields that are considered metadata and not part of the config object.
|
||||
*/
|
||||
const META_FIELDS = new Set(["name"]);
|
||||
|
||||
/**
|
||||
* Wraps a config error with details about where the error occurred.
|
||||
* @param {Error} error The original error.
|
||||
* @param {number} originalLength The original length of the config array.
|
||||
* @param {number} baseLength The length of the base config.
|
||||
* @returns {TypeError} The new error with details.
|
||||
*/
|
||||
function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
|
||||
|
||||
let location = "user-defined";
|
||||
let configIndex = error.index;
|
||||
|
||||
/*
|
||||
* A config array is set up in this order:
|
||||
* 1. Base config
|
||||
* 2. Original configs
|
||||
* 3. User-defined configs
|
||||
* 4. CLI-defined configs
|
||||
*
|
||||
* So we need to adjust the index to account for the base config.
|
||||
*
|
||||
* - If the index is less than the base length, it's in the base config
|
||||
* (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
|
||||
* - If the index is greater than the base length but less than the original
|
||||
* length + base length, it's in the original config. The original config
|
||||
* is passed to the `FlatConfigArray` constructor as the first argument.
|
||||
* - Otherwise, it's in the user-defined config, which is loaded from the
|
||||
* config file and merged with any command-line options.
|
||||
*/
|
||||
if (error.index < baseLength) {
|
||||
location = "base";
|
||||
} else if (error.index < originalLength + baseLength) {
|
||||
location = "original";
|
||||
configIndex = error.index - baseLength;
|
||||
} else {
|
||||
configIndex = error.index - originalLength - baseLength;
|
||||
}
|
||||
|
||||
return new TypeError(
|
||||
`${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
|
||||
{ cause: error }
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
const originalBaseConfig = Symbol("originalBaseConfig");
|
||||
const originalLength = Symbol("originalLength");
|
||||
const baseLength = Symbol("baseLength");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Represents an array containing configuration information for ESLint.
|
||||
*/
|
||||
class FlatConfigArray extends ConfigArray {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {*[]} configs An array of configuration information.
|
||||
* @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
|
||||
* to use for the config array instance.
|
||||
*/
|
||||
constructor(configs, {
|
||||
basePath,
|
||||
shouldIgnore = true,
|
||||
baseConfig = defaultConfig
|
||||
} = {}) {
|
||||
super(configs, {
|
||||
basePath,
|
||||
schema: flatConfigSchema
|
||||
});
|
||||
|
||||
/**
|
||||
* The original length of the array before any modifications.
|
||||
* @type {number}
|
||||
*/
|
||||
this[originalLength] = this.length;
|
||||
|
||||
if (baseConfig[Symbol.iterator]) {
|
||||
this.unshift(...baseConfig);
|
||||
} else {
|
||||
this.unshift(baseConfig);
|
||||
}
|
||||
|
||||
/**
|
||||
* The length of the array after applying the base config.
|
||||
* @type {number}
|
||||
*/
|
||||
this[baseLength] = this.length - this[originalLength];
|
||||
|
||||
/**
|
||||
* The base config used to build the config array.
|
||||
* @type {Array<FlatConfig>}
|
||||
*/
|
||||
this[originalBaseConfig] = baseConfig;
|
||||
Object.defineProperty(this, originalBaseConfig, { writable: false });
|
||||
|
||||
/**
|
||||
* Determines if `ignores` fields should be honored.
|
||||
* If true, then all `ignores` fields are honored.
|
||||
* if false, then only `ignores` fields in the baseConfig are honored.
|
||||
* @type {boolean}
|
||||
*/
|
||||
this.shouldIgnore = shouldIgnore;
|
||||
Object.defineProperty(this, "shouldIgnore", { writable: false });
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the array by calling the superclass method and catching/rethrowing
|
||||
* any ConfigError exceptions with additional details.
|
||||
* @param {any} [context] The context to use to normalize the array.
|
||||
* @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
|
||||
*/
|
||||
normalize(context) {
|
||||
return super.normalize(context)
|
||||
.catch(error => {
|
||||
if (error.name === "ConfigError") {
|
||||
throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the array by calling the superclass method and catching/rethrowing
|
||||
* any ConfigError exceptions with additional details.
|
||||
* @param {any} [context] The context to use to normalize the array.
|
||||
* @returns {FlatConfigArray} The current instance.
|
||||
* @throws {TypeError} If the config is invalid.
|
||||
*/
|
||||
normalizeSync(context) {
|
||||
|
||||
try {
|
||||
|
||||
return super.normalizeSync(context);
|
||||
|
||||
} catch (error) {
|
||||
|
||||
if (error.name === "ConfigError") {
|
||||
throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
|
||||
}
|
||||
|
||||
throw error;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* eslint-disable class-methods-use-this -- Desired as instance method */
|
||||
/**
|
||||
* Replaces a config with another config to allow us to put strings
|
||||
* in the config array that will be replaced by objects before
|
||||
* normalization.
|
||||
* @param {Object} config The config to preprocess.
|
||||
* @returns {Object} The preprocessed config.
|
||||
*/
|
||||
[ConfigArraySymbol.preprocessConfig](config) {
|
||||
|
||||
/*
|
||||
* If a config object has `ignores` and no other non-meta fields, then it's an object
|
||||
* for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
|
||||
* so we'll remove its `ignores`.
|
||||
*/
|
||||
if (
|
||||
!this.shouldIgnore &&
|
||||
!this[originalBaseConfig].includes(config) &&
|
||||
config.ignores &&
|
||||
Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
|
||||
) {
|
||||
/* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
|
||||
const { ignores, ...otherKeys } = config;
|
||||
|
||||
return otherKeys;
|
||||
}
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Finalizes the config by replacing plugin references with their objects
|
||||
* and validating rule option schemas.
|
||||
* @param {Object} config The config to finalize.
|
||||
* @returns {Object} The finalized config.
|
||||
* @throws {TypeError} If the config is invalid.
|
||||
*/
|
||||
[ConfigArraySymbol.finalizeConfig](config) {
|
||||
return new Config(config);
|
||||
}
|
||||
/* eslint-enable class-methods-use-this -- Desired as instance method */
|
||||
|
||||
}
|
||||
|
||||
exports.FlatConfigArray = FlatConfigArray;
|
||||
128
node_modules/eslint/lib/config/flat-config-helpers.js
generated
vendored
Normal file
128
node_modules/eslint/lib/config/flat-config-helpers.js
generated
vendored
Normal file
@@ -0,0 +1,128 @@
|
||||
/**
|
||||
* @fileoverview Shared functions to work with configs.
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../shared/types").Rule} Rule */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Private Members
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// JSON schema that disallows passing any options
|
||||
const noOptionsSchema = Object.freeze({
|
||||
type: "array",
|
||||
minItems: 0,
|
||||
maxItems: 0
|
||||
});
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Functions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parses a ruleId into its plugin and rule parts.
|
||||
* @param {string} ruleId The rule ID to parse.
|
||||
* @returns {{pluginName:string,ruleName:string}} The plugin and rule
|
||||
* parts of the ruleId;
|
||||
*/
|
||||
function parseRuleId(ruleId) {
|
||||
let pluginName, ruleName;
|
||||
|
||||
// distinguish between core rules and plugin rules
|
||||
if (ruleId.includes("/")) {
|
||||
|
||||
// mimic scoped npm packages
|
||||
if (ruleId.startsWith("@")) {
|
||||
pluginName = ruleId.slice(0, ruleId.lastIndexOf("/"));
|
||||
} else {
|
||||
pluginName = ruleId.slice(0, ruleId.indexOf("/"));
|
||||
}
|
||||
|
||||
ruleName = ruleId.slice(pluginName.length + 1);
|
||||
} else {
|
||||
pluginName = "@";
|
||||
ruleName = ruleId;
|
||||
}
|
||||
|
||||
return {
|
||||
pluginName,
|
||||
ruleName
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves a rule instance from a given config based on the ruleId.
|
||||
* @param {string} ruleId The rule ID to look for.
|
||||
* @param {FlatConfig} config The config to search.
|
||||
* @returns {import("../shared/types").Rule|undefined} The rule if found
|
||||
* or undefined if not.
|
||||
*/
|
||||
function getRuleFromConfig(ruleId, config) {
|
||||
const { pluginName, ruleName } = parseRuleId(ruleId);
|
||||
|
||||
return config.plugins?.[pluginName]?.rules?.[ruleName];
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a complete options schema for a rule.
|
||||
* @param {Rule} rule A rule object
|
||||
* @throws {TypeError} If `meta.schema` is specified but is not an array, object or `false`.
|
||||
* @returns {Object|null} JSON Schema for the rule's options. `null` if `meta.schema` is `false`.
|
||||
*/
|
||||
function getRuleOptionsSchema(rule) {
|
||||
|
||||
if (!rule.meta) {
|
||||
return { ...noOptionsSchema }; // default if `meta.schema` is not specified
|
||||
}
|
||||
|
||||
const schema = rule.meta.schema;
|
||||
|
||||
if (typeof schema === "undefined") {
|
||||
return { ...noOptionsSchema }; // default if `meta.schema` is not specified
|
||||
}
|
||||
|
||||
// `schema:false` is an allowed explicit opt-out of options validation for the rule
|
||||
if (schema === false) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (typeof schema !== "object" || schema === null) {
|
||||
throw new TypeError("Rule's `meta.schema` must be an array or object");
|
||||
}
|
||||
|
||||
// ESLint-specific array form needs to be converted into a valid JSON Schema definition
|
||||
if (Array.isArray(schema)) {
|
||||
if (schema.length) {
|
||||
return {
|
||||
type: "array",
|
||||
items: schema,
|
||||
minItems: 0,
|
||||
maxItems: schema.length
|
||||
};
|
||||
}
|
||||
|
||||
// `schema:[]` is an explicit way to specify that the rule does not accept any options
|
||||
return { ...noOptionsSchema };
|
||||
}
|
||||
|
||||
// `schema:<object>` is assumed to be a valid JSON Schema definition
|
||||
return schema;
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
parseRuleId,
|
||||
getRuleFromConfig,
|
||||
getRuleOptionsSchema
|
||||
};
|
||||
576
node_modules/eslint/lib/config/flat-config-schema.js
generated
vendored
Normal file
576
node_modules/eslint/lib/config/flat-config-schema.js
generated
vendored
Normal file
@@ -0,0 +1,576 @@
|
||||
/**
|
||||
* @fileoverview Flat config schema
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { normalizeSeverityToNumber } = require("../shared/severity");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Type Definitions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @typedef ObjectPropertySchema
|
||||
* @property {Function|string} merge The function or name of the function to call
|
||||
* to merge multiple objects with this property.
|
||||
* @property {Function|string} validate The function or name of the function to call
|
||||
* to validate the value of this property.
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const ruleSeverities = new Map([
|
||||
[0, 0], ["off", 0],
|
||||
[1, 1], ["warn", 1],
|
||||
[2, 2], ["error", 2]
|
||||
]);
|
||||
|
||||
/**
|
||||
* Check if a value is a non-null object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is a non-null object.
|
||||
*/
|
||||
function isNonNullObject(value) {
|
||||
return typeof value === "object" && value !== null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is a non-null non-array object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is a non-null non-array object.
|
||||
*/
|
||||
function isNonArrayObject(value) {
|
||||
return isNonNullObject(value) && !Array.isArray(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a value is undefined.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {boolean} `true` if the value is undefined.
|
||||
*/
|
||||
function isUndefined(value) {
|
||||
return typeof value === "undefined";
|
||||
}
|
||||
|
||||
/**
|
||||
* Deeply merges two non-array objects.
|
||||
* @param {Object} first The base object.
|
||||
* @param {Object} second The overrides object.
|
||||
* @param {Map<string, Map<string, Object>>} [mergeMap] Maps the combination of first and second arguments to a merged result.
|
||||
* @returns {Object} An object with properties from both first and second.
|
||||
*/
|
||||
function deepMerge(first, second, mergeMap = new Map()) {
|
||||
|
||||
let secondMergeMap = mergeMap.get(first);
|
||||
|
||||
if (secondMergeMap) {
|
||||
const result = secondMergeMap.get(second);
|
||||
|
||||
if (result) {
|
||||
|
||||
// If this combination of first and second arguments has been already visited, return the previously created result.
|
||||
return result;
|
||||
}
|
||||
} else {
|
||||
secondMergeMap = new Map();
|
||||
mergeMap.set(first, secondMergeMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* First create a result object where properties from the second object
|
||||
* overwrite properties from the first. This sets up a baseline to use
|
||||
* later rather than needing to inspect and change every property
|
||||
* individually.
|
||||
*/
|
||||
const result = {
|
||||
...first,
|
||||
...second
|
||||
};
|
||||
|
||||
delete result.__proto__; // eslint-disable-line no-proto -- don't merge own property "__proto__"
|
||||
|
||||
// Store the pending result for this combination of first and second arguments.
|
||||
secondMergeMap.set(second, result);
|
||||
|
||||
for (const key of Object.keys(second)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__" || !Object.prototype.propertyIsEnumerable.call(first, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstValue = first[key];
|
||||
const secondValue = second[key];
|
||||
|
||||
if (isNonArrayObject(firstValue) && isNonArrayObject(secondValue)) {
|
||||
result[key] = deepMerge(firstValue, secondValue, mergeMap);
|
||||
} else if (isUndefined(secondValue)) {
|
||||
result[key] = firstValue;
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the rule options config for a given rule by ensuring that
|
||||
* it is an array and that the first item is 0, 1, or 2.
|
||||
* @param {Array|string|number} ruleOptions The rule options config.
|
||||
* @returns {Array} An array of rule options.
|
||||
*/
|
||||
function normalizeRuleOptions(ruleOptions) {
|
||||
|
||||
const finalOptions = Array.isArray(ruleOptions)
|
||||
? ruleOptions.slice(0)
|
||||
: [ruleOptions];
|
||||
|
||||
finalOptions[0] = ruleSeverities.get(finalOptions[0]);
|
||||
return structuredClone(finalOptions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if an object has any methods.
|
||||
* @param {Object} object The object to check.
|
||||
* @returns {boolean} `true` if the object has any methods.
|
||||
*/
|
||||
function hasMethod(object) {
|
||||
|
||||
for (const key of Object.keys(object)) {
|
||||
|
||||
if (typeof object[key] === "function") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Assertions
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* The error type when a rule's options are configured with an invalid type.
|
||||
*/
|
||||
class InvalidRuleOptionsError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The invalid value.
|
||||
*/
|
||||
constructor(ruleId, value) {
|
||||
super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`);
|
||||
this.messageTemplate = "invalid-rule-options";
|
||||
this.messageData = { ruleId, value };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is a valid rule options entry.
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {void}
|
||||
* @throws {InvalidRuleOptionsError} If the value isn't a valid rule options.
|
||||
*/
|
||||
function assertIsRuleOptions(ruleId, value) {
|
||||
if (typeof value !== "string" && typeof value !== "number" && !Array.isArray(value)) {
|
||||
throw new InvalidRuleOptionsError(ruleId, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when a rule's severity is invalid.
|
||||
*/
|
||||
class InvalidRuleSeverityError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The invalid value.
|
||||
*/
|
||||
constructor(ruleId, value) {
|
||||
super(`Key "${ruleId}": Expected severity of "off", 0, "warn", 1, "error", or 2.`);
|
||||
this.messageTemplate = "invalid-rule-severity";
|
||||
this.messageData = { ruleId, value };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is valid rule severity.
|
||||
* @param {string} ruleId Rule name being configured.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {void}
|
||||
* @throws {InvalidRuleSeverityError} If the value isn't a valid rule severity.
|
||||
*/
|
||||
function assertIsRuleSeverity(ruleId, value) {
|
||||
const severity = ruleSeverities.get(value);
|
||||
|
||||
if (typeof severity === "undefined") {
|
||||
throw new InvalidRuleSeverityError(ruleId, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a given string is the form pluginName/objectName.
|
||||
* @param {string} value The string to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the string isn't in the correct format.
|
||||
*/
|
||||
function assertIsPluginMemberName(value) {
|
||||
if (!/[@a-z0-9-_$]+(?:\/(?:[a-z0-9-_$]+))+$/iu.test(value)) {
|
||||
throw new TypeError(`Expected string in the form "pluginName/objectName" but found "${value}".`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that a value is an object.
|
||||
* @param {any} value The value to check.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value isn't an object.
|
||||
*/
|
||||
function assertIsObject(value) {
|
||||
if (!isNonNullObject(value)) {
|
||||
throw new TypeError("Expected an object.");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when there's an eslintrc-style options in a flat config.
|
||||
*/
|
||||
class IncompatibleKeyError extends Error {
|
||||
|
||||
/**
|
||||
* @param {string} key The invalid key.
|
||||
*/
|
||||
constructor(key) {
|
||||
super("This appears to be in eslintrc format rather than flat config format.");
|
||||
this.messageTemplate = "eslintrc-incompat";
|
||||
this.messageData = { key };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when there's an eslintrc-style plugins array found.
|
||||
*/
|
||||
class IncompatiblePluginsError extends Error {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {Array<string>} plugins The plugins array.
|
||||
*/
|
||||
constructor(plugins) {
|
||||
super("This appears to be in eslintrc format (array of strings) rather than flat config format (object).");
|
||||
this.messageTemplate = "eslintrc-plugins";
|
||||
this.messageData = { plugins };
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Low-Level Schemas
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const booleanSchema = {
|
||||
merge: "replace",
|
||||
validate: "boolean"
|
||||
};
|
||||
|
||||
const ALLOWED_SEVERITIES = new Set(["error", "warn", "off", 2, 1, 0]);
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const disableDirectiveSeveritySchema = {
|
||||
merge(first, second) {
|
||||
const value = second === void 0 ? first : second;
|
||||
|
||||
if (typeof value === "boolean") {
|
||||
return value ? "warn" : "off";
|
||||
}
|
||||
|
||||
return normalizeSeverityToNumber(value);
|
||||
},
|
||||
validate(value) {
|
||||
if (!(ALLOWED_SEVERITIES.has(value) || typeof value === "boolean")) {
|
||||
throw new TypeError("Expected one of: \"error\", \"warn\", \"off\", 0, 1, 2, or a boolean.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const deepObjectAssignSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
return deepMerge(first, second);
|
||||
},
|
||||
validate: "object"
|
||||
};
|
||||
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// High-Level Schemas
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const languageOptionsSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
|
||||
const result = deepMerge(first, second);
|
||||
|
||||
for (const [key, value] of Object.entries(result)) {
|
||||
|
||||
/*
|
||||
* Special case: Because the `parser` property is an object, it should
|
||||
* not be deep merged. Instead, it should be replaced if it exists in
|
||||
* the second object. To make this more generic, we just check for
|
||||
* objects with methods and replace them if they exist in the second
|
||||
* object.
|
||||
*/
|
||||
if (isNonArrayObject(value)) {
|
||||
if (hasMethod(value)) {
|
||||
result[key] = second[key] ?? first[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
// for other objects, make sure we aren't reusing the same object
|
||||
result[key] = { ...result[key] };
|
||||
continue;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
validate: "object"
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const languageSchema = {
|
||||
merge: "replace",
|
||||
validate: assertIsPluginMemberName
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const pluginsSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
const keys = new Set([...Object.keys(first), ...Object.keys(second)]);
|
||||
const result = {};
|
||||
|
||||
// manually validate that plugins are not redefined
|
||||
for (const key of keys) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (key in first && key in second && first[key] !== second[key]) {
|
||||
throw new TypeError(`Cannot redefine plugin "${key}".`);
|
||||
}
|
||||
|
||||
result[key] = second[key] || first[key];
|
||||
}
|
||||
|
||||
return result;
|
||||
},
|
||||
validate(value) {
|
||||
|
||||
// first check the value to be sure it's an object
|
||||
if (value === null || typeof value !== "object") {
|
||||
throw new TypeError("Expected an object.");
|
||||
}
|
||||
|
||||
// make sure it's not an array, which would mean eslintrc-style is used
|
||||
if (Array.isArray(value)) {
|
||||
throw new IncompatiblePluginsError(value);
|
||||
}
|
||||
|
||||
// second check the keys to make sure they are objects
|
||||
for (const key of Object.keys(value)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (key === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (value[key] === null || typeof value[key] !== "object") {
|
||||
throw new TypeError(`Key "${key}": Expected an object.`);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const processorSchema = {
|
||||
merge: "replace",
|
||||
validate(value) {
|
||||
if (typeof value === "string") {
|
||||
assertIsPluginMemberName(value);
|
||||
} else if (value && typeof value === "object") {
|
||||
if (typeof value.preprocess !== "function" || typeof value.postprocess !== "function") {
|
||||
throw new TypeError("Object must have a preprocess() and a postprocess() method.");
|
||||
}
|
||||
} else {
|
||||
throw new TypeError("Expected an object or a string.");
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/** @type {ObjectPropertySchema} */
|
||||
const rulesSchema = {
|
||||
merge(first = {}, second = {}) {
|
||||
|
||||
const result = {
|
||||
...first,
|
||||
...second
|
||||
};
|
||||
|
||||
|
||||
for (const ruleId of Object.keys(result)) {
|
||||
|
||||
try {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (ruleId === "__proto__") {
|
||||
|
||||
/* eslint-disable-next-line no-proto -- Though deprecated, may still be present */
|
||||
delete result.__proto__;
|
||||
continue;
|
||||
}
|
||||
|
||||
result[ruleId] = normalizeRuleOptions(result[ruleId]);
|
||||
|
||||
/*
|
||||
* If either rule config is missing, then the correct
|
||||
* config is already present and we just need to normalize
|
||||
* the severity.
|
||||
*/
|
||||
if (!(ruleId in first) || !(ruleId in second)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const firstRuleOptions = normalizeRuleOptions(first[ruleId]);
|
||||
const secondRuleOptions = normalizeRuleOptions(second[ruleId]);
|
||||
|
||||
/*
|
||||
* If the second rule config only has a severity (length of 1),
|
||||
* then use that severity and keep the rest of the options from
|
||||
* the first rule config.
|
||||
*/
|
||||
if (secondRuleOptions.length === 1) {
|
||||
result[ruleId] = [secondRuleOptions[0], ...firstRuleOptions.slice(1)];
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* In any other situation, then the second rule config takes
|
||||
* precedence. That means the value at `result[ruleId]` is
|
||||
* already correct and no further work is necessary.
|
||||
*/
|
||||
} catch (ex) {
|
||||
throw new Error(`Key "${ruleId}": ${ex.message}`, { cause: ex });
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
|
||||
},
|
||||
|
||||
validate(value) {
|
||||
assertIsObject(value);
|
||||
|
||||
/*
|
||||
* We are not checking the rule schema here because there is no
|
||||
* guarantee that the rule definition is present at this point. Instead
|
||||
* we wait and check the rule schema during the finalization step
|
||||
* of calculating a config.
|
||||
*/
|
||||
for (const ruleId of Object.keys(value)) {
|
||||
|
||||
// avoid hairy edge case
|
||||
if (ruleId === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
const ruleOptions = value[ruleId];
|
||||
|
||||
assertIsRuleOptions(ruleId, ruleOptions);
|
||||
|
||||
if (Array.isArray(ruleOptions)) {
|
||||
assertIsRuleSeverity(ruleId, ruleOptions[0]);
|
||||
} else {
|
||||
assertIsRuleSeverity(ruleId, ruleOptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a schema that always throws an error. Useful for warning
|
||||
* about eslintrc-style keys.
|
||||
* @param {string} key The eslintrc key to create a schema for.
|
||||
* @returns {ObjectPropertySchema} The schema.
|
||||
*/
|
||||
function createEslintrcErrorSchema(key) {
|
||||
return {
|
||||
merge: "replace",
|
||||
validate() {
|
||||
throw new IncompatibleKeyError(key);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
const eslintrcKeys = [
|
||||
"env",
|
||||
"extends",
|
||||
"globals",
|
||||
"ignorePatterns",
|
||||
"noInlineConfig",
|
||||
"overrides",
|
||||
"parser",
|
||||
"parserOptions",
|
||||
"reportUnusedDisableDirectives",
|
||||
"root"
|
||||
];
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Full schema
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const flatConfigSchema = {
|
||||
|
||||
// eslintrc-style keys that should always error
|
||||
...Object.fromEntries(eslintrcKeys.map(key => [key, createEslintrcErrorSchema(key)])),
|
||||
|
||||
// flat config keys
|
||||
settings: deepObjectAssignSchema,
|
||||
linterOptions: {
|
||||
schema: {
|
||||
noInlineConfig: booleanSchema,
|
||||
reportUnusedDisableDirectives: disableDirectiveSeveritySchema
|
||||
}
|
||||
},
|
||||
language: languageSchema,
|
||||
languageOptions: languageOptionsSchema,
|
||||
processor: processorSchema,
|
||||
plugins: pluginsSchema,
|
||||
rules: rulesSchema
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
flatConfigSchema,
|
||||
hasMethod,
|
||||
assertIsRuleSeverity
|
||||
};
|
||||
194
node_modules/eslint/lib/config/rule-validator.js
generated
vendored
Normal file
194
node_modules/eslint/lib/config/rule-validator.js
generated
vendored
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* @fileoverview Rule Validator
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const ajvImport = require("../shared/ajv");
|
||||
const ajv = ajvImport();
|
||||
const {
|
||||
parseRuleId,
|
||||
getRuleFromConfig,
|
||||
getRuleOptionsSchema
|
||||
} = require("./flat-config-helpers");
|
||||
const ruleReplacements = require("../../conf/replacements.json");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Throws a helpful error when a rule cannot be found.
|
||||
* @param {Object} ruleId The rule identifier.
|
||||
* @param {string} ruleId.pluginName The ID of the rule to find.
|
||||
* @param {string} ruleId.ruleName The ID of the rule to find.
|
||||
* @param {Object} config The config to search in.
|
||||
* @throws {TypeError} For missing plugin or rule.
|
||||
* @returns {void}
|
||||
*/
|
||||
function throwRuleNotFoundError({ pluginName, ruleName }, config) {
|
||||
|
||||
const ruleId = pluginName === "@" ? ruleName : `${pluginName}/${ruleName}`;
|
||||
|
||||
const errorMessageHeader = `Key "rules": Key "${ruleId}"`;
|
||||
let errorMessage = `${errorMessageHeader}: Could not find plugin "${pluginName}".`;
|
||||
|
||||
// if the plugin exists then we need to check if the rule exists
|
||||
if (config.plugins && config.plugins[pluginName]) {
|
||||
const replacementRuleName = ruleReplacements.rules[ruleName];
|
||||
|
||||
if (pluginName === "@" && replacementRuleName) {
|
||||
|
||||
errorMessage = `${errorMessageHeader}: Rule "${ruleName}" was removed and replaced by "${replacementRuleName}".`;
|
||||
|
||||
} else {
|
||||
|
||||
errorMessage = `${errorMessageHeader}: Could not find "${ruleName}" in plugin "${pluginName}".`;
|
||||
|
||||
// otherwise, let's see if we can find the rule name elsewhere
|
||||
for (const [otherPluginName, otherPlugin] of Object.entries(config.plugins)) {
|
||||
if (otherPlugin.rules && otherPlugin.rules[ruleName]) {
|
||||
errorMessage += ` Did you mean "${otherPluginName}/${ruleName}"?`;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// falls through to throw error
|
||||
}
|
||||
|
||||
throw new TypeError(errorMessage);
|
||||
}
|
||||
|
||||
/**
|
||||
* The error type when a rule has an invalid `meta.schema`.
|
||||
*/
|
||||
class InvalidRuleOptionsSchemaError extends Error {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
* @param {string} ruleId Id of the rule that has an invalid `meta.schema`.
|
||||
* @param {Error} processingError Error caught while processing the `meta.schema`.
|
||||
*/
|
||||
constructor(ruleId, processingError) {
|
||||
super(
|
||||
`Error while processing options validation schema of rule '${ruleId}': ${processingError.message}`,
|
||||
{ cause: processingError }
|
||||
);
|
||||
this.code = "ESLINT_INVALID_RULE_OPTIONS_SCHEMA";
|
||||
}
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Implements validation functionality for the rules portion of a config.
|
||||
*/
|
||||
class RuleValidator {
|
||||
|
||||
/**
|
||||
* Creates a new instance.
|
||||
*/
|
||||
constructor() {
|
||||
|
||||
/**
|
||||
* A collection of compiled validators for rules that have already
|
||||
* been validated.
|
||||
* @type {WeakMap}
|
||||
*/
|
||||
this.validators = new WeakMap();
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates all of the rule configurations in a config against each
|
||||
* rule's schema.
|
||||
* @param {Object} config The full config to validate. This object must
|
||||
* contain both the rules section and the plugins section.
|
||||
* @returns {void}
|
||||
* @throws {Error} If a rule's configuration does not match its schema.
|
||||
*/
|
||||
validate(config) {
|
||||
|
||||
if (!config.rules) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const [ruleId, ruleOptions] of Object.entries(config.rules)) {
|
||||
|
||||
// check for edge case
|
||||
if (ruleId === "__proto__") {
|
||||
continue;
|
||||
}
|
||||
|
||||
/*
|
||||
* If a rule is disabled, we don't do any validation. This allows
|
||||
* users to safely set any value to 0 or "off" without worrying
|
||||
* that it will cause a validation error.
|
||||
*
|
||||
* Note: ruleOptions is always an array at this point because
|
||||
* this validation occurs after FlatConfigArray has merged and
|
||||
* normalized values.
|
||||
*/
|
||||
if (ruleOptions[0] === 0) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const rule = getRuleFromConfig(ruleId, config);
|
||||
|
||||
if (!rule) {
|
||||
throwRuleNotFoundError(parseRuleId(ruleId), config);
|
||||
}
|
||||
|
||||
// Precompile and cache validator the first time
|
||||
if (!this.validators.has(rule)) {
|
||||
try {
|
||||
const schema = getRuleOptionsSchema(rule);
|
||||
|
||||
if (schema) {
|
||||
this.validators.set(rule, ajv.compile(schema));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new InvalidRuleOptionsSchemaError(ruleId, err);
|
||||
}
|
||||
}
|
||||
|
||||
const validateRule = this.validators.get(rule);
|
||||
|
||||
if (validateRule) {
|
||||
|
||||
validateRule(ruleOptions.slice(1));
|
||||
|
||||
if (validateRule.errors) {
|
||||
throw new Error(`Key "rules": Key "${ruleId}":\n${
|
||||
validateRule.errors.map(
|
||||
error => {
|
||||
if (
|
||||
error.keyword === "additionalProperties" &&
|
||||
error.schema === false &&
|
||||
typeof error.parentSchema?.properties === "object" &&
|
||||
typeof error.params?.additionalProperty === "string"
|
||||
) {
|
||||
const expectedProperties = Object.keys(error.parentSchema.properties).map(property => `"${property}"`);
|
||||
|
||||
return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n\t\tUnexpected property "${error.params.additionalProperty}". Expected properties: ${expectedProperties.join(", ")}.\n`;
|
||||
}
|
||||
|
||||
return `\tValue ${JSON.stringify(error.data)} ${error.message}.\n`;
|
||||
}
|
||||
).join("")
|
||||
}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
exports.RuleValidator = RuleValidator;
|
||||
Reference in New Issue
Block a user