diff --git a/README.md b/README.md index cd65564e..11ff46ec 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ This repository contains the legacy ESLintRC configuration file format for ESLin **Note:** This package is not intended for use outside of the ESLint ecosystem. It is ESLint-specific and not intended for use in other programs. -## Usage +## Installation You can install the package as follows: @@ -16,6 +16,46 @@ npm install @eslint/eslintrc --save-dev yarn add @eslint/eslintrc -D ``` +## Future Usage + +**Note:** This package is not intended for public use at this time. The following is an example of how it will be used in the future. + +The primary class in this package is `FlatCompat`, which is a utility to translate ESLintRC-style configs into flat configs. Here's how you use it inside of your `eslint.config.js` file: + +```js +import { FlatCompat } from "@eslint/eslintrc"; + +const compat = new FlatCompat(); + +export default [ + + // mimic ESLintRC-style extends + compat.extends("standard", "example"), + + // mimic environments + compat.env({ + es2020: true, + node: true + }), + + // mimic plugins + compat.plugins("airbnb", "react"), + + // translate an entire config + compat.config({ + plugins: ["airbnb", "react"], + extends: "standard", + env: { + es2020: true, + node: true + }, + rules: { + semi: "error" + } + }) +]; +``` + ## License MIT License diff --git a/conf/eslint-all.js b/conf/eslint-all.js new file mode 100644 index 00000000..859811c8 --- /dev/null +++ b/conf/eslint-all.js @@ -0,0 +1,12 @@ +/** + * @fileoverview Stub eslint:all config + * @author Nicholas C. Zakas + */ + +"use strict"; + +module.exports = { + settings: { + "eslint:all": true + } +}; diff --git a/conf/eslint-recommended.js b/conf/eslint-recommended.js new file mode 100644 index 00000000..96300919 --- /dev/null +++ b/conf/eslint-recommended.js @@ -0,0 +1,12 @@ +/** + * @fileoverview Stub eslint:recommended config + * @author Nicholas C. Zakas + */ + +"use strict"; + +module.exports = { + settings: { + "eslint:recommended": true + } +}; diff --git a/lib/flat-compat.js b/lib/flat-compat.js new file mode 100644 index 00000000..c0d0ea22 --- /dev/null +++ b/lib/flat-compat.js @@ -0,0 +1,308 @@ +/** + * @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; diff --git a/lib/index.js b/lib/index.js index 90959c16..3e8dd90e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -23,6 +23,7 @@ const { OverrideTester } = require("./config-array/override-tester"); const ConfigOps = require("./shared/config-ops"); const ConfigValidator = require("./shared/config-validator"); const naming = require("./shared/naming"); +const { FlatCompat } = require("./flat-compat"); //----------------------------------------------------------------------------- // Exports @@ -46,6 +47,8 @@ module.exports = { ConfigValidator, ModuleResolver, naming - } + }, + + FlatCompat }; diff --git a/package.json b/package.json index f71c5e81..a2220e6e 100644 --- a/package.json +++ b/package.json @@ -41,6 +41,7 @@ "eslint-release": "^3.1.2", "fs-teardown": "^0.1.0", "mocha": "^8.1.1", + "shelljs": "^0.8.4", "sinon": "^9.2.0", "temp-dir": "^2.0.0" }, diff --git a/tests/fixtures/flat-compat/config/node_modules/eslint-config-fixture1.js b/tests/fixtures/flat-compat/config/node_modules/eslint-config-fixture1.js new file mode 100644 index 00000000..dac6d2aa --- /dev/null +++ b/tests/fixtures/flat-compat/config/node_modules/eslint-config-fixture1.js @@ -0,0 +1,5 @@ +module.exports = { + globals: { + foobar: true + } +}; diff --git a/tests/fixtures/flat-compat/config/node_modules/eslint-config-fixture2.js b/tests/fixtures/flat-compat/config/node_modules/eslint-config-fixture2.js new file mode 100644 index 00000000..1209e420 --- /dev/null +++ b/tests/fixtures/flat-compat/config/node_modules/eslint-config-fixture2.js @@ -0,0 +1,8 @@ +module.exports = { + globals: { + foobar: false + }, + rules: { + foobar: "error" + } +}; diff --git a/tests/fixtures/flat-compat/config/node_modules/eslint-config-ignores-foo.js b/tests/fixtures/flat-compat/config/node_modules/eslint-config-ignores-foo.js new file mode 100644 index 00000000..112d2ada --- /dev/null +++ b/tests/fixtures/flat-compat/config/node_modules/eslint-config-ignores-foo.js @@ -0,0 +1,3 @@ +module.exports = { + ignorePatterns: ["foo/*"] +}; diff --git a/tests/fixtures/dot-compat/config/node_modules/eslint-plugin-fixture1.js b/tests/fixtures/flat-compat/config/node_modules/eslint-plugin-fixture1.js similarity index 100% rename from tests/fixtures/dot-compat/config/node_modules/eslint-plugin-fixture1.js rename to tests/fixtures/flat-compat/config/node_modules/eslint-plugin-fixture1.js diff --git a/tests/fixtures/dot-compat/config/node_modules/eslint-plugin-fixture2.js b/tests/fixtures/flat-compat/config/node_modules/eslint-plugin-fixture2.js similarity index 100% rename from tests/fixtures/dot-compat/config/node_modules/eslint-plugin-fixture2.js rename to tests/fixtures/flat-compat/config/node_modules/eslint-plugin-fixture2.js diff --git a/tests/fixtures/dot-compat/config/node_modules/eslint-plugin-fixture3.js b/tests/fixtures/flat-compat/config/node_modules/eslint-plugin-fixture3.js similarity index 100% rename from tests/fixtures/dot-compat/config/node_modules/eslint-plugin-fixture3.js rename to tests/fixtures/flat-compat/config/node_modules/eslint-plugin-fixture3.js diff --git a/tests/fixtures/dot-compat/config/node_modules/my-parser.js b/tests/fixtures/flat-compat/config/node_modules/my-parser.js similarity index 100% rename from tests/fixtures/dot-compat/config/node_modules/my-parser.js rename to tests/fixtures/flat-compat/config/node_modules/my-parser.js diff --git a/tests/lib/config-array-factory.js b/tests/lib/config-array-factory.js index 4fa1ea8c..5f6dc5af 100644 --- a/tests/lib/config-array-factory.js +++ b/tests/lib/config-array-factory.js @@ -8,7 +8,6 @@ // Requirements //----------------------------------------------------------------------------- -const os = require("os"); const path = require("path"); const fs = require("fs"); const { assert } = require("chai"); @@ -106,11 +105,6 @@ function assertPluginDefinition(actual, providedExpected) { describe("ConfigArrayFactory", () => { - // Need extra time for all the file i/o operations - before(function () { - this.timeout = 60 * 1000; - }); - describe("'create(configData, options)' method should normalize the config data.", () => { const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: tempDir @@ -119,7 +113,7 @@ describe("ConfigArrayFactory", () => { /** @type {ConfigArrayFactory} */ let factory; - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); @@ -189,7 +183,7 @@ describe("ConfigArrayFactory", () => { /** @type {ConfigArrayFactory} */ let factory; - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); @@ -282,7 +276,7 @@ describe("ConfigArrayFactory", () => { /** @type {ConfigArrayFactory} */ let factory; - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); @@ -537,7 +531,7 @@ describe("ConfigArrayFactory", () => { }); describe("'parser' details", () => { - + const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: tempDir, files: { @@ -546,8 +540,8 @@ describe("ConfigArrayFactory", () => { "parser.js": "exports.name = './parser.js';" } }); - - before(async () => { + + before(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); @@ -839,7 +833,7 @@ describe("ConfigArrayFactory", () => { "base.js": "module.exports = { rules: { semi: [2, 'always'] } };" } })); - + factory = new ConfigArrayFactory({ cwd: getPath(), eslintAllPath, @@ -1407,7 +1401,7 @@ describe("ConfigArrayFactory", () => { cwd: tempDir }); - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath(), @@ -1478,8 +1472,8 @@ describe("ConfigArrayFactory", () => { }; const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: tempDir, files }); let factory; - - beforeEach(async () => { + + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath(), @@ -1714,12 +1708,13 @@ describe("ConfigArrayFactory", () => { .toCompatibleObjectAsConfigFileContent(); } - it("should throw error if file doesn't exist", async () => { + it("should throw error if file doesn't exist", async() => { const teardown = createCustomTeardown({ cwd: tempDir }); + cleanup = teardown.cleanup; await teardown.prepare(); - const factory = new ConfigArrayFactory({ cwd: teardown.getPath()}); + const factory = new ConfigArrayFactory({ cwd: teardown.getPath() }); assert.throws(() => { @@ -1731,13 +1726,14 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from a legacy file", async () => { + it("should load information from a legacy file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { "legacy/.eslintrc": "{ rules: { eqeqeq: 2 } }" } }); + cleanup = teardown.cleanup; await teardown.prepare(); @@ -1752,7 +1748,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from a JavaScript file", async () => { + it("should load information from a JavaScript file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1775,7 +1771,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from a JavaScript file with a .cjs extension", async () => { + it("should load information from a JavaScript file with a .cjs extension", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1798,7 +1794,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should throw error when loading invalid JavaScript file", async () => { + it("should throw error when loading invalid JavaScript file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1816,7 +1812,7 @@ describe("ConfigArrayFactory", () => { }, /Cannot read config file/u); }); - it("should interpret parser module name when present in a JavaScript file", async () => { + it("should interpret parser module name when present in a JavaScript file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1844,7 +1840,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should interpret parser path when present in a JavaScript file", async () => { + it("should interpret parser path when present in a JavaScript file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1871,7 +1867,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should interpret parser module name or path when parser is set to default parser in a JavaScript file", async () => { + it("should interpret parser module name or path when parser is set to default parser in a JavaScript file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1897,7 +1893,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from a JSON file", async () => { + it("should load information from a JSON file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1920,7 +1916,7 @@ describe("ConfigArrayFactory", () => { }); it("should load fresh information from a JSON file", () => { - const factory = new ConfigArrayFactory({cwd: tempDir }); + const factory = new ConfigArrayFactory({ cwd: tempDir }); const filename = "fresh-test.json"; const filePath = path.resolve(tempDir, filename); const initialConfig = { @@ -1948,7 +1944,7 @@ describe("ConfigArrayFactory", () => { fs.rmdirSync(tempDir); }); - it("should load information from a package.json file", async () => { + it("should load information from a package.json file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -1968,7 +1964,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should throw error when loading invalid package.json file", async () => { + it("should throw error when loading invalid package.json file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2054,7 +2050,7 @@ describe("ConfigArrayFactory", () => { fs.rmdirSync(tempDir); }); - it("should load information from a YAML file", async () => { + it("should load information from a YAML file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2074,7 +2070,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from an empty YAML file", async () => { + it("should load information from an empty YAML file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2092,7 +2088,7 @@ describe("ConfigArrayFactory", () => { assertConfig(config, {}); }); - it("should load information from a YML file", async () => { + it("should load information from a YML file", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2112,7 +2108,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from a YML file and apply extensions", async () => { + it("should load information from a YML file and apply extensions", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2161,7 +2157,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from `extends` chain with relative path.", async () => { + it("should load information from `extends` chain with relative path.", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2186,13 +2182,13 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from `extends` chain in .eslintrc with relative path.", async () => { + it("should load information from `extends` chain in .eslintrc with relative path.", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { - "extends-chain-2/node_modules/eslint-config-a/index.js": "module.exports = { extends: './relative.js', rules: { a: 2 } };", - "extends-chain-2/node_modules/eslint-config-a/relative.js": "module.exports = { rules: { relative: 2 } };", - "extends-chain-2/relative.eslintrc.json": "{ \"extends\": \"./node_modules/eslint-config-a/index.js\" }" + "extends-chain-2/node_modules/eslint-config-a/index.js": "module.exports = { extends: './relative.js', rules: { a: 2 } };", + "extends-chain-2/node_modules/eslint-config-a/relative.js": "module.exports = { rules: { relative: 2 } };", + "extends-chain-2/relative.eslintrc.json": "{ \"extends\": \"./node_modules/eslint-config-a/index.js\" }" } }); @@ -2211,7 +2207,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load information from `parser` in .eslintrc with relative path.", async () => { + it("should load information from `parser` in .eslintrc with relative path.", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2233,7 +2229,7 @@ describe("ConfigArrayFactory", () => { }); describe("Plugins", () => { - it("should load information from a YML file and load plugins", async () => { + it("should load information from a YML file and load plugins", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2254,6 +2250,7 @@ describe("ConfigArrayFactory", () => { ` } }); + cleanup = teardown.cleanup; await teardown.prepare(); @@ -2270,7 +2267,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should load two separate configs from a plugin", async () => { + it("should load two separate configs from a plugin", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2289,6 +2286,7 @@ describe("ConfigArrayFactory", () => { ` } }); + cleanup = teardown.cleanup; await teardown.prepare(); @@ -2306,13 +2304,14 @@ describe("ConfigArrayFactory", () => { }); describe("even if config files have Unicode BOM,", () => { - it("should read the JSON config file correctly.", async () => { + it("should read the JSON config file correctly.", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { "bom/.eslintrc.json": "\uFEFF{ \"rules\": { \"semi\": \"error\" } }" } }); + cleanup = teardown.cleanup; await teardown.prepare(); @@ -2327,7 +2326,7 @@ describe("ConfigArrayFactory", () => { }); }); - it("should read the YAML config file correctly.", async () => { + it("should read the YAML config file correctly.", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2349,13 +2348,14 @@ describe("ConfigArrayFactory", () => { }); }); - it("should read the config in package.json correctly.", async () => { + it("should read the config in package.json correctly.", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { "bom/package.json": "\uFEFF{ \"eslintConfig\": { \"rules\": { \"semi\": \"error\" } } }" } }); + cleanup = teardown.cleanup; await teardown.prepare(); @@ -2371,13 +2371,14 @@ describe("ConfigArrayFactory", () => { }); }); - it("throws an error including the config file name if the config file is invalid", async () => { + it("throws an error including the config file name if the config file is invalid", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { "invalid/invalid-top-level-property.yml": "invalidProperty: 3" } }); + cleanup = teardown.cleanup; await teardown.prepare(); @@ -2394,7 +2395,7 @@ describe("ConfigArrayFactory", () => { }); // This group moved from 'tests/lib/config/config-file.js' when refactoring to keep the cumulated test cases. - describe("'extends' property should resolve the location of configs properly.", async () => { + describe("'extends' property should resolve the location of configs properly.", async() => { const { prepare, cleanup, getPath } = createCustomTeardown({ cwd: tempDir, files: { @@ -2413,7 +2414,7 @@ describe("ConfigArrayFactory", () => { let factory; - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); @@ -2495,7 +2496,7 @@ describe("ConfigArrayFactory", () => { let factory; - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); @@ -2532,7 +2533,7 @@ describe("ConfigArrayFactory", () => { ); }); - it("should load a plugin when referenced by short name, even when using a custom loadPluginsRelativeTo value", async () => { + it("should load a plugin when referenced by short name, even when using a custom loadPluginsRelativeTo value", async() => { const teardown = createCustomTeardown({ cwd: tempDir, files: { @@ -2637,7 +2638,7 @@ describe("ConfigArrayFactory", () => { let factory; - beforeEach(async () => { + beforeEach(async() => { await prepare(); factory = new ConfigArrayFactory({ cwd: getPath() }); }); diff --git a/tests/lib/flat-compat.js b/tests/lib/flat-compat.js new file mode 100644 index 00000000..4552d363 --- /dev/null +++ b/tests/lib/flat-compat.js @@ -0,0 +1,995 @@ +/** + * @fileoverview Tests for FlatCompat class. + * @author Nicholas C. Zakas + */ +"use strict"; + +//----------------------------------------------------------------------------- +// Requirements +//----------------------------------------------------------------------------- + +const path = require("path"); +const { assert } = require("chai"); +const { FlatCompat } = require("../../lib"); +const environments = require("../../conf/environments"); + +//----------------------------------------------------------------------------- +// Helpers +//----------------------------------------------------------------------------- + +const FIXTURES_BASE_PATH = path.resolve(__dirname, "../fixtures/flat-compat/"); + +/** + * Normalizes a plugin object to have all available keys. This matches what + * ConfigArrayFactory does. + * @param {Object} plugin The plugin object to normalize. + * @returns {Object} The normalized plugin object. + */ +function normalizePlugin(plugin) { + return { + configs: {}, + rules: {}, + environments: {}, + processors: {}, + ...plugin + }; +} + +/** + * Returns the full directory path for a fixture directory. + * @param {string} dirName The directory name to resolve. + * @returns {string} The full directory path to the fixture. + */ +function getFixturePath(dirName) { + return path.join(FIXTURES_BASE_PATH, dirName); +} + +//----------------------------------------------------------------------------- +// Tests +//----------------------------------------------------------------------------- + +describe("FlatCompat", () => { + + describe("config()", () => { + + let compat; + const baseDirectory = getFixturePath("config"); + const pluginFixture1 = normalizePlugin(require(path.join(baseDirectory, "node_modules/eslint-plugin-fixture1"))); + const pluginFixture2 = normalizePlugin(require(path.join(baseDirectory, "node_modules/eslint-plugin-fixture2"))); + const pluginFixture3 = normalizePlugin(require(path.join(baseDirectory, "node_modules/eslint-plugin-fixture3"))); + + beforeEach(() => { + compat = new FlatCompat({ + baseDirectory + }); + }); + + describe("top-level", () => { + + describe("ignorePatterns", () => { + it("should translate ignorePatterns string into ignores array", () => { + const result = compat.config({ + ignorePatterns: "*.jsx" + }); + + assert.strictEqual(result.length, 1); + assert.typeOf(result[0].ignores[0], "function"); + assert.isTrue(result[0].ignores[0](path.join(baseDirectory, "foo.jsx"))); + assert.isFalse(result[0].ignores[0](path.join(baseDirectory, "foo.js"))); + }); + + it("should translate ignorePatterns array into ignores array", () => { + const result = compat.config({ + ignorePatterns: ["*.jsx"] + }); + + assert.strictEqual(result.length, 1); + assert.typeOf(result[0].ignores[0], "function"); + assert.isTrue(result[0].ignores[0](path.join(baseDirectory, "foo.jsx"))); + assert.isFalse(result[0].ignores[0](path.join(baseDirectory, "foo.js"))); + }); + + it("should ignore second argument of ignore function from ignorePatterns", () => { + const result = compat.config({ + ignorePatterns: ["*.jsx"] + }); + + assert.strictEqual(result.length, 1); + assert.typeOf(result[0].ignores[0], "function"); + assert.isTrue(result[0].ignores[0](path.join(baseDirectory, "foo.jsx"), {})); + assert.isFalse(result[0].ignores[0](path.join(baseDirectory, "foo.js"), "")); + }); + + it("should combine ignorePatterns from extended configs", () => { + const result = compat.config({ + ignorePatterns: ["!foo/bar"], + extends: "ignores-foo" + }); + + assert.strictEqual(result.length, 1); + assert.typeOf(result[0].ignores[0], "function"); + assert.isTrue(result[0].ignores[0](path.join(baseDirectory, "foo/baz.js"))); + assert.isFalse(result[0].ignores[0](path.join(baseDirectory, "foo/bar/baz.js"))); + }); + + + }); + + it("should translate settings", () => { + const result = compat.config({ + settings: { + foo: true, + bar: false + } + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + settings: { + foo: true, + bar: false + } + }); + }); + + it("should translate plugins without processors", () => { + const result = compat.config({ + plugins: ["fixture1"] + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + plugins: { + fixture1: pluginFixture1 + } + }); + }); + + it("should translate plugins with processors", () => { + const result = compat.config({ + plugins: ["fixture2"] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + files: ["**/*.md"], + processor: pluginFixture2.processors[".md"] + }); + assert.deepStrictEqual(result[1], { + plugins: { + fixture2: pluginFixture2 + } + }); + }); + + it("should translate multiple plugins", () => { + const result = compat.config({ + plugins: ["fixture1", "fixture2"] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + files: ["**/*.md"], + processor: pluginFixture2.processors[".md"] + }); + assert.deepStrictEqual(result[1], { + plugins: { + fixture1: pluginFixture1, + fixture2: pluginFixture2 + } + }); + }); + + it("should translate plugins with environments", () => { + const result = compat.config({ + plugins: ["fixture3"], + env: { + "fixture3/a": true, + "fixture3/b": true + } + }); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foo: true + } + } + }); + assert.deepStrictEqual(result[1], { + languageOptions: { + globals: { + bar: false + } + } + }); + assert.deepStrictEqual(result[2], { + plugins: { + fixture3: pluginFixture3 + } + }); + }); + + }); + + describe("extends", () => { + it("should translate extends string into a config object", () => { + const result = compat.config({ + extends: "fixture1", + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foobar: true + } + } + }); + assert.deepStrictEqual(result[1], { + rules: { + foo: "warn" + } + }); + }); + + it("should translate extends eslint:all into a string", () => { + const result = compat.config({ + extends: "eslint:all", + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], "eslint:all"); + assert.deepStrictEqual(result[1], { + rules: { + foo: "warn" + } + }); + }); + + it("should translate extends [eslint:all] into a string", () => { + const result = compat.config({ + extends: ["eslint:all"], + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], "eslint:all"); + assert.deepStrictEqual(result[1], { + rules: { + foo: "warn" + } + }); + }); + + it("should translate extends eslint:recommended into a string", () => { + const result = compat.config({ + extends: "eslint:recommended", + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], "eslint:recommended"); + assert.deepStrictEqual(result[1], { + rules: { + foo: "warn" + } + }); + }); + + it("should translate extends [eslint:recommended] into a string", () => { + const result = compat.config({ + extends: ["eslint:recommended"], + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], "eslint:recommended"); + assert.deepStrictEqual(result[1], { + rules: { + foo: "warn" + } + }); + }); + + it("should translate extends array into a config object", () => { + const result = compat.config({ + extends: ["fixture1"], + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foobar: true + } + } + }); + assert.deepStrictEqual(result[1], { + rules: { + foo: "warn" + } + }); + }); + + it("should translate extends array with multiple configs into config objects", () => { + const result = compat.config({ + extends: ["fixture1", "eslint:all", "fixture2"], + rules: { + foo: "warn" + } + }); + + assert.strictEqual(result.length, 4); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foobar: true + } + } + }); + assert.deepStrictEqual(result[1], "eslint:all"); + assert.deepStrictEqual(result[2], { + languageOptions: { + globals: { + foobar: false + } + }, + rules: { + foobar: "error" + } + }); + assert.deepStrictEqual(result[3], { + rules: { + foo: "warn" + } + }); + }); + + }); + + describe("overrides", () => { + it("should translate files string into files array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: "*.jsx", + rules: { + foo: "warn" + } + } + ] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + assert.typeOf(result[1].files[0], "function"); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.js")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + }); + + it("should translate files array into files array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: ["*.jsx"], + rules: { + foo: "warn" + } + } + ] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + assert.typeOf(result[1].files[0], "function"); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.js")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + }); + + it("should translate files array with multiple patterns into files array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: ["*.jsx", "*.js"], + rules: { + foo: "warn" + } + } + ] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + assert.typeOf(result[1].files[0], "function"); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.js")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.jsm")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + }); + + it("should translate files/excludedFiles strings into files/ignores array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: "*", + excludedFiles: "*.jsx", + rules: { + foo: "warn" + } + } + ] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + assert.typeOf(result[1].files[0], "function"); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.js")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + }); + + it("should translate files/excludedFiles arrays into files/ignores array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: ["*"], + excludedFiles: ["*.jsx"], + rules: { + foo: "warn" + } + } + ] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + assert.typeOf(result[1].files[0], "function"); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.js")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + }); + + it("should translate files/excludedFiles arrays with multiple items into files/ignores array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: ["*.js", "*.jsx"], + excludedFiles: ["*.test.js", "*test.jsx"], + rules: { + foo: "warn" + } + } + ] + }); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + assert.typeOf(result[1].files[0], "function"); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.js")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.test.jsx")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.test.js")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + }); + + it("should translate multiple files/excludedFiles arrays with multiple items into files/ignores array", () => { + const result = compat.config({ + rules: { + foo: "error" + }, + overrides: [ + { + files: ["*.js", "*.jsx"], + excludedFiles: ["*.test.js", "*test.jsx"], + rules: { + foo: "warn" + } + }, + { + files: ["*.md", "*.mdx"], + excludedFiles: ["*.test.md", "*test.mdx"], + rules: { + bar: "error" + } + } + + ] + }); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { + rules: { + foo: "error" + } + }); + + assert.typeOf(result[1].files[0], "function"); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.jsx")); + assert.isTrue(result[1].files[0]("/usr/eslint/foo.js")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.test.jsx")); + assert.isFalse(result[1].files[0]("/usr/eslint/foo.test.js")); + assert.deepStrictEqual(result[1].rules, { + foo: "warn" + }); + + + assert.typeOf(result[2].files[0], "function"); + assert.isTrue(result[2].files[0]("/usr/eslint/foo.mdx")); + assert.isTrue(result[2].files[0]("/usr/eslint/foo.md")); + assert.isFalse(result[2].files[0]("/usr/eslint/foo.test.mdx")); + assert.isFalse(result[2].files[0]("/usr/eslint/foo.test.md")); + assert.deepStrictEqual(result[2].rules, { + bar: "error" + }); + }); + + }); + + describe("linterOptions", () => { + + ["noInlineConfig", "reportUnusedDisableDirectives"].forEach(propertyName => { + it(`should translate ${propertyName} into linterOptions.${propertyName}`, () => { + const result = compat.config({ + [propertyName]: true + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + linterOptions: { + [propertyName]: true + } + }); + }); + }); + + + it("should translate multiple linteroptions", () => { + const result = compat.config({ + noInlineConfig: true, + reportUnusedDisableDirectives: false + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + linterOptions: { + noInlineConfig: true, + reportUnusedDisableDirectives: false + } + }); + }); + + }); + + describe("languageOptions", () => { + + it("should translate globals", () => { + const result = compat.config({ + globals: { + foo: true, + bar: false + } + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foo: true, + bar: false + } + } + }); + }); + + it("should translate env into globals", () => { + const result = compat.config({ + env: { + amd: true + } + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + ...environments.get("amd") + } + }); + }); + + it("should translate parserOptions", () => { + const parserOptions = { + foo: true, + bar: false + }; + + const result = compat.config({ + parserOptions + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + parserOptions: { + foo: true, + bar: false + } + } + }); + + // the object should be a clone, not the original + assert.notEqual(result[0].languageOptions.parserOptions, parserOptions); + }); + + it("should translate parser string into an object", () => { + const result = compat.config({ + parser: "my-parser" + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + parser: require(getFixturePath("config/node_modules/my-parser")) + } + }); + }); + + it("should throw an error when the parser can't be found", () => { + + assert.throws(() => { + compat.config({ + parser: "missing-parser" + }); + }, /Failed to load parser 'missing-parser'/); + }); + + it("should translate sourceType", () => { + const result = compat.config({ + parserOptions: { + sourceType: "module" + } + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + sourceType: "module" + } + }); + }); + + it("should translate multiple options", () => { + const result = compat.config({ + parserOptions: { + sourceType: "module" + }, + globals: { + foo: true, + bar: false + } + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + sourceType: "module", + globals: { + foo: true, + bar: false + } + } + }); + }); + + }); + + }); + + describe("env()", () => { + + let compat; + + beforeEach(() => { + compat = new FlatCompat(); + }); + + it("should translate env into globals", () => { + const result = compat.env({ + amd: true + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + ...environments.get("amd") + } + }); + }); + + it("should not translate env into globals when env is false", () => { + const result = compat.env({ + amd: true, + node: false + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + ...environments.get("amd") + } + }); + }); + + it("should translate env with parserOptions.ecmaVersion into globals and languageOptions.ecmaVersion", () => { + const result = compat.env({ + es6: true + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + ecmaVersion: 6, + globals: { + ...environments.get("es6").globals + } + } + }); + }); + + it("should translate env with parserOptions.ecmaFeatures.globalReturn into globals and languageOptions.parserOptions", () => { + const result = compat.env({ + node: true + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + parserOptions: environments.get("node").parserOptions, + globals: { + ...environments.get("node").globals + } + } + }); + }); + + it("should translate env with parserOptions.ecmaFeatures.globalReturn into globals and languageOptions.parserOptions", () => { + const result = compat.env({ + es2021: true + }); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + ecmaVersion: 12, + globals: { + ...environments.get("es2021").globals + } + } + }); + }); + + }); + + describe("extends()", () => { + + let compat; + + beforeEach(() => { + compat = new FlatCompat({ + baseDirectory: getFixturePath("config") + }); + }); + + it("should translate extends string into a config object", () => { + const result = compat.extends("fixture1"); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foobar: true + } + } + }); + }); + + it("should translate extends eslint:all into a string", () => { + const result = compat.extends("eslint:all"); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], "eslint:all"); + }); + + it("should translate extends eslint:recommended into a string", () => { + const result = compat.extends("eslint:recommended"); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], "eslint:recommended"); + }); + + it("should translate extends array with multiple configs into config objects", () => { + const result = compat.extends("fixture1", "eslint:all", "fixture2"); + + assert.strictEqual(result.length, 3); + assert.deepStrictEqual(result[0], { + languageOptions: { + globals: { + foobar: true + } + } + }); + assert.deepStrictEqual(result[1], "eslint:all"); + assert.deepStrictEqual(result[2], { + languageOptions: { + globals: { + foobar: false + }, + }, + rules: { + foobar: "error" + } + }); + }); + + }); + + + describe("plugins()", () => { + + let compat; + + beforeEach(() => { + compat = new FlatCompat({ + baseDirectory: getFixturePath("config") + }); + }); + + it("should translate plugins without processors", () => { + const result = compat.plugins("fixture1"); + + assert.strictEqual(result.length, 1); + assert.deepStrictEqual(result[0], { + plugins: { + fixture1: { + configs: {}, + rules: {}, + environments: {}, + processors: {}, + ...require(path.join(compat.baseDirectory, "node_modules/eslint-plugin-fixture1")) + } + } + }); + }); + + it("should throw an error when a plugin is missing", () => { + assert.throws(() => { + compat.plugins("missing"); + }, /Failed to load plugin 'missing'/); + }); + + it("should translate plugins with processors", () => { + const result = compat.plugins("fixture2"); + const plugin = require(path.join(compat.baseDirectory, "node_modules/eslint-plugin-fixture2")); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + files: ["**/*.md"], + processor: plugin.processors[".md"] + }); + assert.deepStrictEqual(result[1], { + plugins: { + fixture2: { + configs: {}, + rules: {}, + environments: {}, + processors: {}, + ...plugin + } + } + }); + }); + + it("should translate multiple plugins", () => { + const result = compat.plugins("fixture1", "fixture2"); + const plugin = require(path.join(compat.baseDirectory, "node_modules/eslint-plugin-fixture2")); + + assert.strictEqual(result.length, 2); + assert.deepStrictEqual(result[0], { + files: ["**/*.md"], + processor: plugin.processors[".md"] + }); + assert.deepStrictEqual(result[1], { + plugins: { + fixture1: { + configs: {}, + rules: {}, + environments: {}, + processors: {}, + ...require(path.join(compat.baseDirectory, "node_modules/eslint-plugin-fixture1")) + }, + fixture2: { + configs: {}, + rules: {}, + environments: {}, + processors: {}, + ...plugin + } + } + }); + }); + + }); + +});