| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816 |
- /**
- * @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 };
|