Add components
This commit is contained in:
816
slider/node_modules/eslint/lib/config/config-loader.js
generated
vendored
Normal file
816
slider/node_modules/eslint/lib/config/config-loader.js
generated
vendored
Normal file
@@ -0,0 +1,816 @@
|
||||
/**
|
||||
* @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");
|
||||
const { WarningService } = require("../services/warning-service");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Types
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/** @typedef {import("../types").Linter.Config} Config */
|
||||
|
||||
/**
|
||||
* @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 {Config|Array<Config>} [baseConfig] The base config to use.
|
||||
* @property {Array<Config>} [defaultConfigs] The default configs to use.
|
||||
* @property {Array<string>} [ignorePatterns] The ignore patterns to use.
|
||||
* @property {Config|Array<Config>} [overrideConfig] The override config to use.
|
||||
* @property {boolean} [hasUnstableNativeNodeJsTSConfigFlag] The flag to indicate whether the `unstable_native_nodejs_ts_config` flag is enabled.
|
||||
* @property {WarningService} [warningService] The warning service to use.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const FLAT_CONFIG_FILENAMES = [
|
||||
"eslint.config.js",
|
||||
"eslint.config.mjs",
|
||||
"eslint.config.cjs",
|
||||
"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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if native TypeScript support is
|
||||
* enabled in the current Node.js process.
|
||||
*
|
||||
* This function determines if the
|
||||
* {@linkcode NodeJS.ProcessFeatures.typescript | typescript}
|
||||
* feature is present in the
|
||||
* {@linkcode process.features} object
|
||||
* and if its value is either "strip" or "transform".
|
||||
* @returns {boolean} `true` if native TypeScript support is enabled, otherwise `false`.
|
||||
* @since 9.24.0
|
||||
*/
|
||||
function isNativeTypeScriptSupportEnabled() {
|
||||
return (
|
||||
// eslint-disable-next-line n/no-unsupported-features/node-builtins -- it's still an experimental feature.
|
||||
["strip", "transform"].includes(process.features.typescript)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the TypeScript configuration file.
|
||||
* @param {string} filePath The absolute file path to load.
|
||||
* @param {URL} fileURL The file URL to load.
|
||||
* @param {number} mtime The last modified timestamp of the file.
|
||||
* @returns {Promise<any>} The configuration loaded from the file.
|
||||
* @since 9.24.0
|
||||
*/
|
||||
async function loadTypeScriptConfigFileWithJiti(filePath, fileURL, mtime) {
|
||||
const { createJiti, version: jitiVersion } =
|
||||
// eslint-disable-next-line no-use-before-define -- `ConfigLoader.loadJiti` can be overwritten for testing
|
||||
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 jitiOptions = {
|
||||
moduleCache: false,
|
||||
};
|
||||
|
||||
if (jitiVersion.startsWith("2.1.")) {
|
||||
jitiOptions.interopDefault = false;
|
||||
}
|
||||
|
||||
const jiti = createJiti(__filename, jitiOptions);
|
||||
const config = await jiti.import(fileURL.href);
|
||||
|
||||
importedConfigFileModificationTime.set(filePath, mtime);
|
||||
|
||||
return config?.default ?? config;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dynamically imports a module from the given file path.
|
||||
* @param {string} filePath The absolute file path of the module to import.
|
||||
* @param {URL} fileURL The file URL to load.
|
||||
* @param {number} mtime The last modified timestamp of the file.
|
||||
* @returns {Promise<any>} - A {@linkcode Promise | promise} that resolves to the imported ESLint config.
|
||||
* @since 9.24.0
|
||||
*/
|
||||
async function dynamicImportConfig(filePath, fileURL, mtime) {
|
||||
const module = await import(fileURL.href);
|
||||
|
||||
importedConfigFileModificationTime.set(filePath, mtime);
|
||||
|
||||
return module.default;
|
||||
}
|
||||
|
||||
/**
|
||||
* Load the config array from the given filename.
|
||||
* @param {string} filePath The filename to load from.
|
||||
* @param {boolean} hasUnstableNativeNodeJsTSConfigFlag The flag to indicate whether the `unstable_native_nodejs_ts_config` flag is enabled.
|
||||
* @returns {Promise<any>} The config loaded from the config file.
|
||||
*/
|
||||
async function loadConfigFile(filePath, hasUnstableNativeNodeJsTSConfigFlag) {
|
||||
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 (isTS) {
|
||||
if (hasUnstableNativeNodeJsTSConfigFlag) {
|
||||
if (isNativeTypeScriptSupportEnabled()) {
|
||||
return await dynamicImportConfig(filePath, fileURL, mtime);
|
||||
}
|
||||
|
||||
if (!("typescript" in process.features)) {
|
||||
throw new Error(
|
||||
"The unstable_native_nodejs_ts_config flag is not supported in older versions of Node.js.",
|
||||
);
|
||||
}
|
||||
|
||||
throw new Error(
|
||||
"The unstable_native_nodejs_ts_config flag is enabled, but native TypeScript support is not enabled in the current Node.js process. You need to either enable native TypeScript support by passing --experimental-strip-types or remove the unstable_native_nodejs_ts_config flag.",
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDeno && !isBun) {
|
||||
return await loadTypeScriptConfigFileWithJiti(
|
||||
filePath,
|
||||
fileURL,
|
||||
mtime,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// fallback to normal runtime behavior
|
||||
|
||||
return await dynamicImportConfig(filePath, fileURL, mtime);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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.warningService
|
||||
? options
|
||||
: { ...options, warningService: new WarningService() };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
|
||||
// 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<FlatConfigArray>} A configuration object 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<FlatConfigArray>} A configuration object 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 {FlatConfigArray} A configuration object 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 {FlatConfigArray} A configuration object 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<{createJiti: Function|undefined, version: string;}>} A promise that fulfills with an object containing the jiti module's createJiti function and version.
|
||||
*/
|
||||
static async loadJiti() {
|
||||
const { createJiti } = await import("jiti");
|
||||
const version = require("jiti/package.json").version;
|
||||
return { createJiti, version };
|
||||
}
|
||||
|
||||
/**
|
||||
* 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`.
|
||||
* @returns {Promise<{configFilePath:string|undefined,basePath:string}>} Location information for
|
||||
* the config file.
|
||||
*/
|
||||
static async locateConfigFileToUse({
|
||||
useConfigFile,
|
||||
cwd,
|
||||
fromDirectory = cwd,
|
||||
}) {
|
||||
// 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(FLAT_CONFIG_FILENAMES, {
|
||||
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,
|
||||
hasUnstableNativeNodeJsTSConfigFlag = false,
|
||||
defaultConfigs = [],
|
||||
warningService,
|
||||
} = 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,
|
||||
hasUnstableNativeNodeJsTSConfigFlag,
|
||||
);
|
||||
|
||||
/*
|
||||
* It's possible that a config file could be empty or else
|
||||
* have an empty object or array. In this case, we want to
|
||||
* warn the user that they have an empty config.
|
||||
*
|
||||
* An empty CommonJS file exports an empty object while
|
||||
* an empty ESM file exports undefined.
|
||||
*/
|
||||
|
||||
let emptyConfig = typeof fileConfig === "undefined";
|
||||
|
||||
debug(
|
||||
`Config file ${configFilePath} is ${emptyConfig ? "empty" : "not empty"}`,
|
||||
);
|
||||
|
||||
if (!emptyConfig) {
|
||||
if (Array.isArray(fileConfig)) {
|
||||
if (fileConfig.length === 0) {
|
||||
debug(
|
||||
`Config file ${configFilePath} is an empty array`,
|
||||
);
|
||||
emptyConfig = true;
|
||||
} else {
|
||||
configs.push(...fileConfig);
|
||||
}
|
||||
} else {
|
||||
if (
|
||||
typeof fileConfig === "object" &&
|
||||
fileConfig !== null &&
|
||||
Object.keys(fileConfig).length === 0
|
||||
) {
|
||||
debug(
|
||||
`Config file ${configFilePath} is an empty object`,
|
||||
);
|
||||
emptyConfig = true;
|
||||
} else {
|
||||
configs.push(fileConfig);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (emptyConfig) {
|
||||
warningService.emitEmptyConfigWarning(configFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
// add in any configured defaults
|
||||
configs.push(...defaultConfigs);
|
||||
|
||||
// append command line ignore patterns
|
||||
if (ignorePatterns && ignorePatterns.length > 0) {
|
||||
/*
|
||||
* Ignore patterns are added to the end of the config array
|
||||
* so they can override default ignores.
|
||||
*/
|
||||
configs.push({
|
||||
basePath: cwd,
|
||||
ignores: ignorePatterns,
|
||||
});
|
||||
}
|
||||
|
||||
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) {
|
||||
const normalizedOptions = options.warningService
|
||||
? options
|
||||
: { ...options, warningService: new WarningService() };
|
||||
super(normalizedOptions);
|
||||
this.#options = normalizedOptions;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
});
|
||||
}
|
||||
|
||||
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<FlatConfigArray>} A configuration object 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 {FlatConfigArray} A configuration object 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 };
|
674
slider/node_modules/eslint/lib/config/config.js
generated
vendored
Normal file
674
slider/node_modules/eslint/lib/config/config.js
generated
vendored
Normal file
@@ -0,0 +1,674 @@
|
||||
/**
|
||||
* @fileoverview The `Config` class
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const { deepMergeArrays } = require("../shared/deep-merge-arrays");
|
||||
const { flatConfigSchema, hasMethod } = require("./flat-config-schema");
|
||||
const { ObjectSchema } = require("@eslint/config-array");
|
||||
const ajvImport = require("../shared/ajv");
|
||||
const ajv = ajvImport();
|
||||
const ruleReplacements = require("../../conf/replacements.json");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* @import { RuleDefinition } from "@eslint/core";
|
||||
* @import { Linter } from "eslint";
|
||||
*/
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Private Members
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// JSON schema that disallows passing any options
|
||||
const noOptionsSchema = Object.freeze({
|
||||
type: "array",
|
||||
minItems: 0,
|
||||
maxItems: 0,
|
||||
});
|
||||
|
||||
const severities = new Map([
|
||||
[0, 0],
|
||||
[1, 1],
|
||||
[2, 2],
|
||||
["off", 0],
|
||||
["warn", 1],
|
||||
["error", 2],
|
||||
]);
|
||||
|
||||
/**
|
||||
* A collection of compiled validators for rules that have already
|
||||
* been validated.
|
||||
* @type {WeakMap}
|
||||
*/
|
||||
const validators = new WeakMap();
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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}" in configuration.`;
|
||||
|
||||
const missingPluginErrorMessage = errorMessage;
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
const error = new TypeError(errorMessage);
|
||||
|
||||
if (errorMessage === missingPluginErrorMessage) {
|
||||
error.messageTemplate = "config-plugin-missing";
|
||||
error.messageData = { pluginName, ruleId };
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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";
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {Linter.Config} config The config to search.
|
||||
* @returns {RuleDefinition|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 {RuleDefinition} 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that a value is not a function.
|
||||
* @param {any} value The value to check.
|
||||
* @param {string} key The key of the value in the object.
|
||||
* @param {string} objectKey The key of the object being checked.
|
||||
* @returns {void}
|
||||
* @throws {TypeError} If the value is a function.
|
||||
*/
|
||||
function assertNotFunction(value, key, objectKey) {
|
||||
if (typeof value === "function") {
|
||||
const error = new TypeError(
|
||||
`Cannot serialize key "${key}" in "${objectKey}": Function values are not supported.`,
|
||||
);
|
||||
|
||||
error.messageTemplate = "config-serialize-function";
|
||||
error.messageData = { key, objectKey };
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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") {
|
||||
if (typeof languageOptions.toJSON === "function") {
|
||||
const result = languageOptions.toJSON();
|
||||
|
||||
assertNotFunction(result, "toJSON", objectKey);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
const result = {};
|
||||
|
||||
for (const [key, value] of Object.entries(languageOptions)) {
|
||||
if (value) {
|
||||
if (typeof value === "object") {
|
||||
const name = getObjectId(value);
|
||||
|
||||
if (typeof value.toJSON === "function") {
|
||||
result[key] = value.toJSON();
|
||||
assertNotFunction(result[key], key, objectKey);
|
||||
} else if (name && hasMethod(value)) {
|
||||
result[key] = name;
|
||||
} else {
|
||||
result[key] = languageOptionsToJSON(value, key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
assertNotFunction(value, key, objectKey);
|
||||
}
|
||||
|
||||
result[key] = value;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets or creates a validator for a rule.
|
||||
* @param {Object} rule The rule to get a validator for.
|
||||
* @param {string} ruleId The ID of the rule (for error reporting).
|
||||
* @returns {Function|null} A validation function or null if no validation is needed.
|
||||
* @throws {InvalidRuleOptionsSchemaError} If a rule's `meta.schema` is invalid.
|
||||
*/
|
||||
function getOrCreateValidator(rule, ruleId) {
|
||||
if (!validators.has(rule)) {
|
||||
try {
|
||||
const schema = getRuleOptionsSchema(rule);
|
||||
|
||||
if (schema) {
|
||||
validators.set(rule, ajv.compile(schema));
|
||||
}
|
||||
} catch (err) {
|
||||
throw new InvalidRuleOptionsSchemaError(ruleId, err);
|
||||
}
|
||||
}
|
||||
|
||||
return validators.get(rule);
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// 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();
|
||||
this.validateRulesConfig(this.rules);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a rule configuration by its ID.
|
||||
* @param {string} ruleId The ID of the rule to get.
|
||||
* @returns {RuleDefinition|undefined} The rule definition from the plugin, or `undefined` if the rule is not found.
|
||||
*/
|
||||
getRuleDefinition(ruleId) {
|
||||
return getRuleFromConfig(ruleId, this);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates all of the rule configurations in the given rules config
|
||||
* against the plugins in this instance. This is used primarily to
|
||||
* validate inline configuration rules while inting.
|
||||
* @param {Object} rulesConfig The rules config to validate.
|
||||
* @returns {void}
|
||||
* @throws {Error} If a rule's configuration does not match its schema.
|
||||
* @throws {TypeError} If the rulesConfig is not provided or is invalid.
|
||||
* @throws {InvalidRuleOptionsSchemaError} If a rule's `meta.schema` is invalid.
|
||||
* @throws {TypeError} If a rule is not found in the plugins.
|
||||
*/
|
||||
validateRulesConfig(rulesConfig) {
|
||||
if (!rulesConfig) {
|
||||
throw new TypeError("Config is required for validation.");
|
||||
}
|
||||
|
||||
for (const [ruleId, ruleOptions] of Object.entries(rulesConfig)) {
|
||||
// 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, this);
|
||||
|
||||
if (!rule) {
|
||||
throwRuleNotFoundError(parseRuleId(ruleId), this);
|
||||
}
|
||||
|
||||
const validateRule = getOrCreateValidator(rule, ruleId);
|
||||
|
||||
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("")}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a complete options schema for a rule.
|
||||
* @param {RuleDefinition} ruleDefinition A rule definition 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`.
|
||||
*/
|
||||
static getRuleOptionsSchema(ruleDefinition) {
|
||||
return getRuleOptionsSchema(ruleDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes the severity value of a rule's configuration to a number
|
||||
* @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally
|
||||
* received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0),
|
||||
* the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array
|
||||
* whose first element is one of the above values. Strings are matched case-insensitively.
|
||||
* @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0.
|
||||
*/
|
||||
static getRuleNumericSeverity(ruleConfig) {
|
||||
const severityValue = Array.isArray(ruleConfig)
|
||||
? ruleConfig[0]
|
||||
: ruleConfig;
|
||||
|
||||
if (severities.has(severityValue)) {
|
||||
return severities.get(severityValue);
|
||||
}
|
||||
|
||||
if (typeof severityValue === "string") {
|
||||
return severities.get(severityValue.toLowerCase()) ?? 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { Config };
|
78
slider/node_modules/eslint/lib/config/default-config.js
generated
vendored
Normal file
78
slider/node_modules/eslint/lib/config/default-config.js
generated
vendored
Normal file
@@ -0,0 +1,78 @@
|
||||
/**
|
||||
* @fileoverview Default configuration
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const Rules = require("../rules");
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
const sharedDefaultConfig = [
|
||||
// intentionally empty config to ensure these files are globbed by default
|
||||
{
|
||||
files: ["**/*.js", "**/*.mjs"],
|
||||
},
|
||||
{
|
||||
files: ["**/*.cjs"],
|
||||
languageOptions: {
|
||||
sourceType: "commonjs",
|
||||
ecmaVersion: "latest",
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
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/"],
|
||||
},
|
||||
|
||||
...sharedDefaultConfig,
|
||||
]);
|
||||
|
||||
exports.defaultRuleTesterConfig = Object.freeze([
|
||||
{ files: ["**"] }, // Make sure the default config matches for all files
|
||||
|
||||
...sharedDefaultConfig,
|
||||
]);
|
217
slider/node_modules/eslint/lib/config/flat-config-array.js
generated
vendored
Normal file
217
slider/node_modules/eslint/lib/config/flat-config-array.js
generated
vendored
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* @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", "basePath"]);
|
||||
|
||||
/**
|
||||
* 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;
|
598
slider/node_modules/eslint/lib/config/flat-config-schema.js
generated
vendored
Normal file
598
slider/node_modules/eslint/lib/config/flat-config-schema.js
generated
vendored
Normal file
@@ -0,0 +1,598 @@
|
||||
/**
|
||||
* @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 (!/[\w\-@$]+(?:\/[\w\-$]+)+$/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 unusedInlineConfigsSeveritySchema = {
|
||||
merge(first, second) {
|
||||
const value = second === void 0 ? first : second;
|
||||
|
||||
return normalizeSeverityToNumber(value);
|
||||
},
|
||||
validate(value) {
|
||||
if (!ALLOWED_SEVERITIES.has(value)) {
|
||||
throw new TypeError(
|
||||
'Expected one of: "error", "warn", "off", 0, 1, or 2.',
|
||||
);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
/** @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,
|
||||
reportUnusedInlineConfigs: unusedInlineConfigsSeveritySchema,
|
||||
},
|
||||
},
|
||||
language: languageSchema,
|
||||
languageOptions: languageOptionsSchema,
|
||||
processor: processorSchema,
|
||||
plugins: pluginsSchema,
|
||||
rules: rulesSchema,
|
||||
};
|
||||
|
||||
//-----------------------------------------------------------------------------
|
||||
// Exports
|
||||
//-----------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
flatConfigSchema,
|
||||
hasMethod,
|
||||
assertIsRuleSeverity,
|
||||
};
|
Reference in New Issue
Block a user