/** * @fileoverview Compatibility class for flat config. * @author Nicholas C. Zakas */ "use strict"; //----------------------------------------------------------------------------- // Requirements //----------------------------------------------------------------------------- const path = require("path"); const environments = require("../conf/environments"); const createDebug = require("debug"); const { ConfigArrayFactory } = require("./config-array-factory"); //----------------------------------------------------------------------------- // Helpers //----------------------------------------------------------------------------- /** @typedef {import("../../shared/types").Environment} Environment */ /** @typedef {import("../../shared/types").Processor} Processor */ const debug = createDebug("eslintrc:flat-compat"); const cafactory = Symbol("cafactory"); /** * Translates an ESLintRC-style config object into a flag-config-style config * object. * @param {Object} eslintrcConfig An ESLintRC-style config object. * @param {Object} options Options to help translate the config. * @param {string} options.resolveConfigRelativeTo To the directory to resolve * configs from. * @param {string} options.resolvePluginsRelativeTo The directory to resolve * plugins from. * @param {ReadOnlyMap} options.pluginEnvironments A map of plugin environment * names to objects. * @param {ReadOnlyMap} options.pluginProcessors A map of plugin processor * names to objects. * @returns {Object} A flag-config-style config object. */ function translateESLintRC(eslintrcConfig, { resolveConfigRelativeTo, resolvePluginsRelativeTo, pluginEnvironments, pluginProcessors }) { const flatConfig = {}; const configs = []; const languageOptions = {}; const linterOptions = {}; const keysToCopy = ["settings", "rules", "processor"]; const languageOptionsKeysToCopy = ["globals", "parser", "parserOptions"]; const linterOptionsKeysToCopy = ["noInlineConfig", "reportUnusedDisableDirectives"]; // check for special settings for eslint:all and eslint:recommended: if (eslintrcConfig.settings) { if (eslintrcConfig.settings["eslint:all"] === true) { return ["eslint:all"]; } if (eslintrcConfig.settings["eslint:recommended"] === true) { return ["eslint:recommended"]; } } // copy over simple translations for (const key of keysToCopy) { if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") { flatConfig[key] = eslintrcConfig[key]; } } // copy over languageOptions for (const key of languageOptionsKeysToCopy) { if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") { // create the languageOptions key in the flat config flatConfig.languageOptions = languageOptions; if (key === "parser") { debug(`Resolving parser '${languageOptions[key]}' relative to ${resolveConfigRelativeTo}`); if (eslintrcConfig[key].error) { throw eslintrcConfig[key].error; } languageOptions[key] = eslintrcConfig[key].definition; continue; } // clone any object values that are in the eslintrc config if (eslintrcConfig[key] && typeof eslintrcConfig[key] === "object") { languageOptions[key] = { ...eslintrcConfig[key] }; } else { languageOptions[key] = eslintrcConfig[key]; } } } // copy over linterOptions for (const key of linterOptionsKeysToCopy) { if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") { flatConfig.linterOptions = linterOptions; linterOptions[key] = eslintrcConfig[key]; } } // move ecmaVersion a level up if (languageOptions.parserOptions) { if ("ecmaVersion" in languageOptions.parserOptions) { languageOptions.ecmaVersion = languageOptions.parserOptions.ecmaVersion; delete languageOptions.parserOptions.ecmaVersion; } if ("sourceType" in languageOptions.parserOptions) { languageOptions.sourceType = languageOptions.parserOptions.sourceType; delete languageOptions.parserOptions.sourceType; } // check to see if we even need parserOptions anymore and remove it if not if (Object.keys(languageOptions.parserOptions).length === 0) { delete languageOptions.parserOptions; } } // overrides if (eslintrcConfig.criteria) { flatConfig.files = [absoluteFilePath => eslintrcConfig.criteria.test(absoluteFilePath)]; } // translate plugins if (eslintrcConfig.plugins && typeof eslintrcConfig.plugins === "object") { debug(`Translating plugins: ${eslintrcConfig.plugins}`); flatConfig.plugins = {}; for (const pluginName of Object.keys(eslintrcConfig.plugins)) { debug(`Translating plugin: ${pluginName}`); debug(`Resolving plugin '${pluginName} relative to ${resolvePluginsRelativeTo}`); const { definition: plugin, error } = eslintrcConfig.plugins[pluginName]; if (error) { throw error; } flatConfig.plugins[pluginName] = plugin; // create a config for any processors if (plugin.processors) { for (const processorName of Object.keys(plugin.processors)) { if (processorName.startsWith(".")) { debug(`Assigning processor: ${pluginName}/${processorName}`); configs.unshift({ files: [`**/*${processorName}`], processor: pluginProcessors.get(`${pluginName}/${processorName}`) }); } } } } } // translate env - must come after plugins if (eslintrcConfig.env && typeof eslintrcConfig.env === "object") { for (const envName of Object.keys(eslintrcConfig.env)) { // only add environments that are true if (eslintrcConfig.env[envName]) { debug(`Translating environment: ${envName}`); if (environments.has(envName)) { // built-in environments should be defined first configs.unshift(...translateESLintRC(environments.get(envName), { resolveConfigRelativeTo, resolvePluginsRelativeTo })); } else if (pluginEnvironments.has(envName)) { // if the environment comes from a plugin, it should come after the plugin config configs.push(...translateESLintRC(pluginEnvironments.get(envName), { resolveConfigRelativeTo, resolvePluginsRelativeTo })); } } } } // only add if there are actually keys in the config if (Object.keys(flatConfig).length > 0) { configs.push(flatConfig); } return configs; } //----------------------------------------------------------------------------- // Exports //----------------------------------------------------------------------------- /** * A compatibility class for working with configs. */ class FlatCompat { constructor({ baseDirectory = process.cwd(), resolvePluginsRelativeTo = baseDirectory } = {}) { this.baseDirectory = baseDirectory; this.resolvePluginsRelativeTo = resolvePluginsRelativeTo; this[cafactory] = new ConfigArrayFactory({ cwd: baseDirectory, resolvePluginsRelativeTo, eslintAllPath: path.resolve(__dirname, "../conf/eslint-all.js"), eslintRecommendedPath: path.resolve(__dirname, "../conf/eslint-recommended.js") }); } /** * Translates an ESLintRC-style config into a flag-config-style config. * @param {Object} eslintrcConfig The ESLintRC-style config object. * @returns {Object} A flag-config-style config object. */ config(eslintrcConfig) { const eslintrcArray = this[cafactory].create(eslintrcConfig, { basePath: this.baseDirectory }); const flatArray = []; let hasIgnorePatterns = false; eslintrcArray.forEach(configData => { if (configData.type === "config") { hasIgnorePatterns = hasIgnorePatterns || configData.ignorePattern; flatArray.push(...translateESLintRC(configData, { resolveConfigRelativeTo: path.join(this.baseDirectory, "__placeholder.js"), resolvePluginsRelativeTo: path.join(this.resolvePluginsRelativeTo, "__placeholder.js"), pluginEnvironments: eslintrcArray.pluginEnvironments, pluginProcessors: eslintrcArray.pluginProcessors })); } }); // combine ignorePatterns to emulate ESLintRC behavior better if (hasIgnorePatterns) { flatArray.unshift({ ignores: [filePath => { // Compute the final config for this file. // This filters config array elements by `files`/`excludedFiles` then merges the elements. const finalConfig = eslintrcArray.extractConfig(filePath); // Test the `ignorePattern` properties of the final config. return Boolean(finalConfig.ignores) && finalConfig.ignores(filePath); }] }); } return flatArray; } /** * Translates the `env` section of an ESLintRC-style config. * @param {Object} envConfig The `env` section of an ESLintRC config. * @returns {Object} A flag-config object representing the environments. */ env(envConfig) { return this.config({ env: envConfig }); } /** * Translates the `extends` section of an ESLintRC-style config. * @param {...string} configsToExtend The names of the configs to load. * @returns {Object} A flag-config object representing the config. */ extends(...configsToExtend) { return this.config({ extends: configsToExtend }); } /** * Translates the `plugins` section of an ESLintRC-style config. * @param {...string} plugins The names of the plugins to load. * @returns {Object} A flag-config object representing the plugins. */ plugins(...plugins) { return this.config({ plugins }); } } exports.FlatCompat = FlatCompat;