223 lines
7.3 KiB
JavaScript
223 lines
7.3 KiB
JavaScript
/**
|
|
* @fileoverview Flat Config Array
|
|
* @author Nicholas C. Zakas
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Requirements
|
|
//-----------------------------------------------------------------------------
|
|
|
|
const { ConfigArray, ConfigArraySymbol } = require("@eslint/config-array");
|
|
const { flatConfigSchema } = require("./flat-config-schema");
|
|
const { defaultConfig } = require("./default-config");
|
|
const { Config } = require("./config");
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Helpers
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Fields that are considered metadata and not part of the config object.
|
|
*/
|
|
const META_FIELDS = new Set(["name"]);
|
|
|
|
/**
|
|
* Wraps a config error with details about where the error occurred.
|
|
* @param {Error} error The original error.
|
|
* @param {number} originalLength The original length of the config array.
|
|
* @param {number} baseLength The length of the base config.
|
|
* @returns {TypeError} The new error with details.
|
|
*/
|
|
function wrapConfigErrorWithDetails(error, originalLength, baseLength) {
|
|
|
|
let location = "user-defined";
|
|
let configIndex = error.index;
|
|
|
|
/*
|
|
* A config array is set up in this order:
|
|
* 1. Base config
|
|
* 2. Original configs
|
|
* 3. User-defined configs
|
|
* 4. CLI-defined configs
|
|
*
|
|
* So we need to adjust the index to account for the base config.
|
|
*
|
|
* - If the index is less than the base length, it's in the base config
|
|
* (as specified by `baseConfig` argument to `FlatConfigArray` constructor).
|
|
* - If the index is greater than the base length but less than the original
|
|
* length + base length, it's in the original config. The original config
|
|
* is passed to the `FlatConfigArray` constructor as the first argument.
|
|
* - Otherwise, it's in the user-defined config, which is loaded from the
|
|
* config file and merged with any command-line options.
|
|
*/
|
|
if (error.index < baseLength) {
|
|
location = "base";
|
|
} else if (error.index < originalLength + baseLength) {
|
|
location = "original";
|
|
configIndex = error.index - baseLength;
|
|
} else {
|
|
configIndex = error.index - originalLength - baseLength;
|
|
}
|
|
|
|
return new TypeError(
|
|
`${error.message.slice(0, -1)} at ${location} index ${configIndex}.`,
|
|
{ cause: error }
|
|
);
|
|
}
|
|
|
|
|
|
const originalBaseConfig = Symbol("originalBaseConfig");
|
|
const originalLength = Symbol("originalLength");
|
|
const baseLength = Symbol("baseLength");
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Exports
|
|
//-----------------------------------------------------------------------------
|
|
|
|
/**
|
|
* Represents an array containing configuration information for ESLint.
|
|
*/
|
|
class FlatConfigArray extends ConfigArray {
|
|
|
|
/**
|
|
* Creates a new instance.
|
|
* @param {*[]} configs An array of configuration information.
|
|
* @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
|
|
* to use for the config array instance.
|
|
*/
|
|
constructor(configs, {
|
|
basePath,
|
|
shouldIgnore = true,
|
|
baseConfig = defaultConfig
|
|
} = {}) {
|
|
super(configs, {
|
|
basePath,
|
|
schema: flatConfigSchema
|
|
});
|
|
|
|
/**
|
|
* The original length of the array before any modifications.
|
|
* @type {number}
|
|
*/
|
|
this[originalLength] = this.length;
|
|
|
|
if (baseConfig[Symbol.iterator]) {
|
|
this.unshift(...baseConfig);
|
|
} else {
|
|
this.unshift(baseConfig);
|
|
}
|
|
|
|
/**
|
|
* The length of the array after applying the base config.
|
|
* @type {number}
|
|
*/
|
|
this[baseLength] = this.length - this[originalLength];
|
|
|
|
/**
|
|
* The base config used to build the config array.
|
|
* @type {Array<FlatConfig>}
|
|
*/
|
|
this[originalBaseConfig] = baseConfig;
|
|
Object.defineProperty(this, originalBaseConfig, { writable: false });
|
|
|
|
/**
|
|
* Determines if `ignores` fields should be honored.
|
|
* If true, then all `ignores` fields are honored.
|
|
* if false, then only `ignores` fields in the baseConfig are honored.
|
|
* @type {boolean}
|
|
*/
|
|
this.shouldIgnore = shouldIgnore;
|
|
Object.defineProperty(this, "shouldIgnore", { writable: false });
|
|
}
|
|
|
|
/**
|
|
* Normalizes the array by calling the superclass method and catching/rethrowing
|
|
* any ConfigError exceptions with additional details.
|
|
* @param {any} [context] The context to use to normalize the array.
|
|
* @returns {Promise<FlatConfigArray>} A promise that resolves when the array is normalized.
|
|
*/
|
|
normalize(context) {
|
|
return super.normalize(context)
|
|
.catch(error => {
|
|
if (error.name === "ConfigError") {
|
|
throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
|
|
}
|
|
|
|
throw error;
|
|
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Normalizes the array by calling the superclass method and catching/rethrowing
|
|
* any ConfigError exceptions with additional details.
|
|
* @param {any} [context] The context to use to normalize the array.
|
|
* @returns {FlatConfigArray} The current instance.
|
|
* @throws {TypeError} If the config is invalid.
|
|
*/
|
|
normalizeSync(context) {
|
|
|
|
try {
|
|
|
|
return super.normalizeSync(context);
|
|
|
|
} catch (error) {
|
|
|
|
if (error.name === "ConfigError") {
|
|
throw wrapConfigErrorWithDetails(error, this[originalLength], this[baseLength]);
|
|
}
|
|
|
|
throw error;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
/* eslint-disable class-methods-use-this -- Desired as instance method */
|
|
/**
|
|
* Replaces a config with another config to allow us to put strings
|
|
* in the config array that will be replaced by objects before
|
|
* normalization.
|
|
* @param {Object} config The config to preprocess.
|
|
* @returns {Object} The preprocessed config.
|
|
*/
|
|
[ConfigArraySymbol.preprocessConfig](config) {
|
|
|
|
/*
|
|
* If a config object has `ignores` and no other non-meta fields, then it's an object
|
|
* for global ignores. If `shouldIgnore` is false, that object shouldn't apply,
|
|
* so we'll remove its `ignores`.
|
|
*/
|
|
if (
|
|
!this.shouldIgnore &&
|
|
!this[originalBaseConfig].includes(config) &&
|
|
config.ignores &&
|
|
Object.keys(config).filter(key => !META_FIELDS.has(key)).length === 1
|
|
) {
|
|
/* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
|
|
const { ignores, ...otherKeys } = config;
|
|
|
|
return otherKeys;
|
|
}
|
|
|
|
return config;
|
|
}
|
|
|
|
/**
|
|
* Finalizes the config by replacing plugin references with their objects
|
|
* and validating rule option schemas.
|
|
* @param {Object} config The config to finalize.
|
|
* @returns {Object} The finalized config.
|
|
* @throws {TypeError} If the config is invalid.
|
|
*/
|
|
[ConfigArraySymbol.finalizeConfig](config) {
|
|
return new Config(config);
|
|
}
|
|
/* eslint-enable class-methods-use-this -- Desired as instance method */
|
|
|
|
}
|
|
|
|
exports.FlatConfigArray = FlatConfigArray;
|