@ec0lint/ec0lintrc
Version:
ec0lint configuration
350 lines (305 loc) • 12 kB
JavaScript
/**
* @fileoverview Compatibility class for flat config.
* @author Nicholas C. Zakas
*/
//-----------------------------------------------------------------------------
// Requirements
//-----------------------------------------------------------------------------
import createDebug from "debug";
import path from "path";
import environments from "../conf/environments.js";
import { ConfigArrayFactory } from "./config-array-factory.js";
//-----------------------------------------------------------------------------
// Helpers
//-----------------------------------------------------------------------------
/** @typedef {import("../../shared/types").Environment} Environment */
/** @typedef {import("../../shared/types").Processor} Processor */
const debug = createDebug("ec0lintrc: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<string,Environment>} options.pluginEnvironments A map of plugin environment
* names to objects.
* @param {ReadOnlyMap<string,Processor>} 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 ec0lint:all and ec0lint:recommended:
if (eslintrcConfig.settings) {
if (eslintrcConfig.settings["ec0lint:all"] === true) {
return ["ec0lint:all"];
}
if (eslintrcConfig.settings["ec0lint:recommended"] === true) {
return ["ec0lint: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,
getEslintAllConfig: () => ({ settings: { "ec0lint:all": true } }),
getEslintRecommendedConfig: () => ({
settings: { "ec0lint:recommended": true },
}),
});
}
/**
* 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,
});
}
}
export { FlatCompat };