|
|
- /**
- * @fileoverview Flat Config Array
- * @author Nicholas C. Zakas
- */
-
- "use strict";
-
- //-----------------------------------------------------------------------------
- // Requirements
- //-----------------------------------------------------------------------------
-
- const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
- const { flatConfigSchema } = require("./flat-config-schema");
- const { RuleValidator } = require("./rule-validator");
- const { defaultConfig } = require("./default-config");
- const jsPlugin = require("@eslint/js");
-
- //-----------------------------------------------------------------------------
- // Helpers
- //-----------------------------------------------------------------------------
-
- /**
- * Fields that are considered metadata and not part of the config object.
- */
- const META_FIELDS = new Set(["name"]);
-
- const ruleValidator = new RuleValidator();
-
- /**
- * 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;
- }
-
- /**
- * 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 (config === "eslint:recommended") {
-
- // if we are in a Node.js environment warn the user
- if (typeof process !== "undefined" && process.emitWarning) {
- process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config.");
- }
-
- return jsPlugin.configs.recommended;
- }
-
- if (config === "eslint:all") {
-
- // if we are in a Node.js environment warn the user
- if (typeof process !== "undefined" && process.emitWarning) {
- process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config.");
- }
-
- return jsPlugin.configs.all;
- }
-
- /*
- * 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) {
-
- const { plugins, languageOptions, processor } = config;
- let parserName, processorName;
- let invalidParser = false,
- invalidProcessor = false;
-
- // Check parser value
- if (languageOptions && languageOptions.parser) {
- const { parser } = languageOptions;
-
- if (typeof parser === "object") {
- parserName = getObjectId(parser);
-
- if (!parserName) {
- invalidParser = true;
- }
-
- } else {
- invalidParser = true;
- }
- }
-
- // Check processor value
- if (processor) {
- if (typeof processor === "string") {
- const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
-
- 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}".`);
- }
-
- config.processor = plugins[pluginName].processors[localProcessorName];
- } else if (typeof processor === "object") {
- processorName = getObjectId(processor);
-
- if (!processorName) {
- invalidProcessor = true;
- }
-
- } else {
- invalidProcessor = true;
- }
- }
-
- ruleValidator.validate(config);
-
- // apply special logic for serialization into JSON
- /* eslint-disable object-shorthand -- shorthand would change "this" value */
- Object.defineProperty(config, "toJSON", {
- value: function() {
-
- if (invalidParser) {
- throw new Error("Could not serialize parser object (missing 'meta' object).");
- }
-
- if (invalidProcessor) {
- throw new Error("Could not serialize processor object (missing 'meta' object).");
- }
-
- return {
- ...this,
- plugins: Object.entries(plugins).map(([namespace, plugin]) => {
-
- const pluginId = getObjectId(plugin);
-
- if (!pluginId) {
- return namespace;
- }
-
- return `${namespace}:${pluginId}`;
- }),
- languageOptions: {
- ...languageOptions,
- parser: parserName
- },
- processor: processorName
- };
- }
- });
- /* eslint-enable object-shorthand -- ok to enable now */
-
- return config;
- }
- /* eslint-enable class-methods-use-this -- Desired as instance method */
-
- }
-
- exports.FlatConfigArray = FlatConfigArray;
|