2025-04-17 07:44:37 -04:00

841 lines
28 KiB
JavaScript

/**
* @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
//-----------------------------------------------------------------------------
/**
* @import { ConfigData, ConfigData as FlatConfigObject } from "../shared/types.js";
*/
/**
* @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} [hasUnstableNativeNodeJsTSConfigFlag] The flag to indicate whether the `unstable_native_nodejs_ts_config` flag is enabled.
*/
//------------------------------------------------------------------------------
// 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) {
// 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;
}
/**
* 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;
}
/**
* 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<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`.
* @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 = [],
} = 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) {
globalThis.process?.emitWarning?.(
`Running ESLint with an empty config (from ${configFilePath}). Please double-check that this is what you want. If you want to run ESLint with an empty config, export [{}] to remove this warning.`,
"ESLintEmptyConfigWarning",
);
}
}
// 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,
});
}
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 };