[d565449] | 1 | /**
|
---|
| 2 | * @fileoverview Flat Config Array
|
---|
| 3 | * @author Nicholas C. Zakas
|
---|
| 4 | */
|
---|
| 5 |
|
---|
| 6 | "use strict";
|
---|
| 7 |
|
---|
| 8 | //-----------------------------------------------------------------------------
|
---|
| 9 | // Requirements
|
---|
| 10 | //-----------------------------------------------------------------------------
|
---|
| 11 |
|
---|
| 12 | const { ConfigArray, ConfigArraySymbol } = require("@humanwhocodes/config-array");
|
---|
| 13 | const { flatConfigSchema } = require("./flat-config-schema");
|
---|
| 14 | const { RuleValidator } = require("./rule-validator");
|
---|
| 15 | const { defaultConfig } = require("./default-config");
|
---|
| 16 | const jsPlugin = require("@eslint/js");
|
---|
| 17 |
|
---|
| 18 | //-----------------------------------------------------------------------------
|
---|
| 19 | // Helpers
|
---|
| 20 | //-----------------------------------------------------------------------------
|
---|
| 21 |
|
---|
| 22 | const ruleValidator = new RuleValidator();
|
---|
| 23 |
|
---|
| 24 | /**
|
---|
| 25 | * Splits a plugin identifier in the form a/b/c into two parts: a/b and c.
|
---|
| 26 | * @param {string} identifier The identifier to parse.
|
---|
| 27 | * @returns {{objectName: string, pluginName: string}} The parts of the plugin
|
---|
| 28 | * name.
|
---|
| 29 | */
|
---|
| 30 | function splitPluginIdentifier(identifier) {
|
---|
| 31 | const parts = identifier.split("/");
|
---|
| 32 |
|
---|
| 33 | return {
|
---|
| 34 | objectName: parts.pop(),
|
---|
| 35 | pluginName: parts.join("/")
|
---|
| 36 | };
|
---|
| 37 | }
|
---|
| 38 |
|
---|
| 39 | /**
|
---|
| 40 | * Returns the name of an object in the config by reading its `meta` key.
|
---|
| 41 | * @param {Object} object The object to check.
|
---|
| 42 | * @returns {string?} The name of the object if found or `null` if there
|
---|
| 43 | * is no name.
|
---|
| 44 | */
|
---|
| 45 | function getObjectId(object) {
|
---|
| 46 |
|
---|
| 47 | // first check old-style name
|
---|
| 48 | let name = object.name;
|
---|
| 49 |
|
---|
| 50 | if (!name) {
|
---|
| 51 |
|
---|
| 52 | if (!object.meta) {
|
---|
| 53 | return null;
|
---|
| 54 | }
|
---|
| 55 |
|
---|
| 56 | name = object.meta.name;
|
---|
| 57 |
|
---|
| 58 | if (!name) {
|
---|
| 59 | return null;
|
---|
| 60 | }
|
---|
| 61 | }
|
---|
| 62 |
|
---|
| 63 | // now check for old-style version
|
---|
| 64 | let version = object.version;
|
---|
| 65 |
|
---|
| 66 | if (!version) {
|
---|
| 67 | version = object.meta && object.meta.version;
|
---|
| 68 | }
|
---|
| 69 |
|
---|
| 70 | // if there's a version then append that
|
---|
| 71 | if (version) {
|
---|
| 72 | return `${name}@${version}`;
|
---|
| 73 | }
|
---|
| 74 |
|
---|
| 75 | return name;
|
---|
| 76 | }
|
---|
| 77 |
|
---|
| 78 | const originalBaseConfig = Symbol("originalBaseConfig");
|
---|
| 79 |
|
---|
| 80 | //-----------------------------------------------------------------------------
|
---|
| 81 | // Exports
|
---|
| 82 | //-----------------------------------------------------------------------------
|
---|
| 83 |
|
---|
| 84 | /**
|
---|
| 85 | * Represents an array containing configuration information for ESLint.
|
---|
| 86 | */
|
---|
| 87 | class FlatConfigArray extends ConfigArray {
|
---|
| 88 |
|
---|
| 89 | /**
|
---|
| 90 | * Creates a new instance.
|
---|
| 91 | * @param {*[]} configs An array of configuration information.
|
---|
| 92 | * @param {{basePath: string, shouldIgnore: boolean, baseConfig: FlatConfig}} options The options
|
---|
| 93 | * to use for the config array instance.
|
---|
| 94 | */
|
---|
| 95 | constructor(configs, {
|
---|
| 96 | basePath,
|
---|
| 97 | shouldIgnore = true,
|
---|
| 98 | baseConfig = defaultConfig
|
---|
| 99 | } = {}) {
|
---|
| 100 | super(configs, {
|
---|
| 101 | basePath,
|
---|
| 102 | schema: flatConfigSchema
|
---|
| 103 | });
|
---|
| 104 |
|
---|
| 105 | if (baseConfig[Symbol.iterator]) {
|
---|
| 106 | this.unshift(...baseConfig);
|
---|
| 107 | } else {
|
---|
| 108 | this.unshift(baseConfig);
|
---|
| 109 | }
|
---|
| 110 |
|
---|
| 111 | /**
|
---|
| 112 | * The base config used to build the config array.
|
---|
| 113 | * @type {Array<FlatConfig>}
|
---|
| 114 | */
|
---|
| 115 | this[originalBaseConfig] = baseConfig;
|
---|
| 116 | Object.defineProperty(this, originalBaseConfig, { writable: false });
|
---|
| 117 |
|
---|
| 118 | /**
|
---|
| 119 | * Determines if `ignores` fields should be honored.
|
---|
| 120 | * If true, then all `ignores` fields are honored.
|
---|
| 121 | * if false, then only `ignores` fields in the baseConfig are honored.
|
---|
| 122 | * @type {boolean}
|
---|
| 123 | */
|
---|
| 124 | this.shouldIgnore = shouldIgnore;
|
---|
| 125 | Object.defineProperty(this, "shouldIgnore", { writable: false });
|
---|
| 126 | }
|
---|
| 127 |
|
---|
| 128 | /* eslint-disable class-methods-use-this -- Desired as instance method */
|
---|
| 129 | /**
|
---|
| 130 | * Replaces a config with another config to allow us to put strings
|
---|
| 131 | * in the config array that will be replaced by objects before
|
---|
| 132 | * normalization.
|
---|
| 133 | * @param {Object} config The config to preprocess.
|
---|
| 134 | * @returns {Object} The preprocessed config.
|
---|
| 135 | */
|
---|
| 136 | [ConfigArraySymbol.preprocessConfig](config) {
|
---|
| 137 | if (config === "eslint:recommended") {
|
---|
| 138 |
|
---|
| 139 | // if we are in a Node.js environment warn the user
|
---|
| 140 | if (typeof process !== "undefined" && process.emitWarning) {
|
---|
| 141 | process.emitWarning("The 'eslint:recommended' string configuration is deprecated and will be replaced by the @eslint/js package's 'recommended' config.");
|
---|
| 142 | }
|
---|
| 143 |
|
---|
| 144 | return jsPlugin.configs.recommended;
|
---|
| 145 | }
|
---|
| 146 |
|
---|
| 147 | if (config === "eslint:all") {
|
---|
| 148 |
|
---|
| 149 | // if we are in a Node.js environment warn the user
|
---|
| 150 | if (typeof process !== "undefined" && process.emitWarning) {
|
---|
| 151 | process.emitWarning("The 'eslint:all' string configuration is deprecated and will be replaced by the @eslint/js package's 'all' config.");
|
---|
| 152 | }
|
---|
| 153 |
|
---|
| 154 | return jsPlugin.configs.all;
|
---|
| 155 | }
|
---|
| 156 |
|
---|
| 157 | /*
|
---|
| 158 | * If `shouldIgnore` is false, we remove any ignore patterns specified
|
---|
| 159 | * in the config so long as it's not a default config and it doesn't
|
---|
| 160 | * have a `files` entry.
|
---|
| 161 | */
|
---|
| 162 | if (
|
---|
| 163 | !this.shouldIgnore &&
|
---|
| 164 | !this[originalBaseConfig].includes(config) &&
|
---|
| 165 | config.ignores &&
|
---|
| 166 | !config.files
|
---|
| 167 | ) {
|
---|
| 168 | /* eslint-disable-next-line no-unused-vars -- need to strip off other keys */
|
---|
| 169 | const { ignores, ...otherKeys } = config;
|
---|
| 170 |
|
---|
| 171 | return otherKeys;
|
---|
| 172 | }
|
---|
| 173 |
|
---|
| 174 | return config;
|
---|
| 175 | }
|
---|
| 176 |
|
---|
| 177 | /**
|
---|
| 178 | * Finalizes the config by replacing plugin references with their objects
|
---|
| 179 | * and validating rule option schemas.
|
---|
| 180 | * @param {Object} config The config to finalize.
|
---|
| 181 | * @returns {Object} The finalized config.
|
---|
| 182 | * @throws {TypeError} If the config is invalid.
|
---|
| 183 | */
|
---|
| 184 | [ConfigArraySymbol.finalizeConfig](config) {
|
---|
| 185 |
|
---|
| 186 | const { plugins, languageOptions, processor } = config;
|
---|
| 187 | let parserName, processorName;
|
---|
| 188 | let invalidParser = false,
|
---|
| 189 | invalidProcessor = false;
|
---|
| 190 |
|
---|
| 191 | // Check parser value
|
---|
| 192 | if (languageOptions && languageOptions.parser) {
|
---|
| 193 | const { parser } = languageOptions;
|
---|
| 194 |
|
---|
| 195 | if (typeof parser === "object") {
|
---|
| 196 | parserName = getObjectId(parser);
|
---|
| 197 |
|
---|
| 198 | if (!parserName) {
|
---|
| 199 | invalidParser = true;
|
---|
| 200 | }
|
---|
| 201 |
|
---|
| 202 | } else {
|
---|
| 203 | invalidParser = true;
|
---|
| 204 | }
|
---|
| 205 | }
|
---|
| 206 |
|
---|
| 207 | // Check processor value
|
---|
| 208 | if (processor) {
|
---|
| 209 | if (typeof processor === "string") {
|
---|
| 210 | const { pluginName, objectName: localProcessorName } = splitPluginIdentifier(processor);
|
---|
| 211 |
|
---|
| 212 | processorName = processor;
|
---|
| 213 |
|
---|
| 214 | if (!plugins || !plugins[pluginName] || !plugins[pluginName].processors || !plugins[pluginName].processors[localProcessorName]) {
|
---|
| 215 | throw new TypeError(`Key "processor": Could not find "${localProcessorName}" in plugin "${pluginName}".`);
|
---|
| 216 | }
|
---|
| 217 |
|
---|
| 218 | config.processor = plugins[pluginName].processors[localProcessorName];
|
---|
| 219 | } else if (typeof processor === "object") {
|
---|
| 220 | processorName = getObjectId(processor);
|
---|
| 221 |
|
---|
| 222 | if (!processorName) {
|
---|
| 223 | invalidProcessor = true;
|
---|
| 224 | }
|
---|
| 225 |
|
---|
| 226 | } else {
|
---|
| 227 | invalidProcessor = true;
|
---|
| 228 | }
|
---|
| 229 | }
|
---|
| 230 |
|
---|
| 231 | ruleValidator.validate(config);
|
---|
| 232 |
|
---|
| 233 | // apply special logic for serialization into JSON
|
---|
| 234 | /* eslint-disable object-shorthand -- shorthand would change "this" value */
|
---|
| 235 | Object.defineProperty(config, "toJSON", {
|
---|
| 236 | value: function() {
|
---|
| 237 |
|
---|
| 238 | if (invalidParser) {
|
---|
| 239 | throw new Error("Could not serialize parser object (missing 'meta' object).");
|
---|
| 240 | }
|
---|
| 241 |
|
---|
| 242 | if (invalidProcessor) {
|
---|
| 243 | throw new Error("Could not serialize processor object (missing 'meta' object).");
|
---|
| 244 | }
|
---|
| 245 |
|
---|
| 246 | return {
|
---|
| 247 | ...this,
|
---|
| 248 | plugins: Object.entries(plugins).map(([namespace, plugin]) => {
|
---|
| 249 |
|
---|
| 250 | const pluginId = getObjectId(plugin);
|
---|
| 251 |
|
---|
| 252 | if (!pluginId) {
|
---|
| 253 | return namespace;
|
---|
| 254 | }
|
---|
| 255 |
|
---|
| 256 | return `${namespace}:${pluginId}`;
|
---|
| 257 | }),
|
---|
| 258 | languageOptions: {
|
---|
| 259 | ...languageOptions,
|
---|
| 260 | parser: parserName
|
---|
| 261 | },
|
---|
| 262 | processor: processorName
|
---|
| 263 | };
|
---|
| 264 | }
|
---|
| 265 | });
|
---|
| 266 | /* eslint-enable object-shorthand -- ok to enable now */
|
---|
| 267 |
|
---|
| 268 | return config;
|
---|
| 269 | }
|
---|
| 270 | /* eslint-enable class-methods-use-this -- Desired as instance method */
|
---|
| 271 |
|
---|
| 272 | }
|
---|
| 273 |
|
---|
| 274 | exports.FlatConfigArray = FlatConfigArray;
|
---|