Spaces:
Sleeping
Sleeping
/** | |
* @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("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<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"]; | |
// 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 { original: 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({ | |
criteria: eslintrcConfig.criteria, | |
...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({ | |
criteria: eslintrcConfig.criteria, | |
...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, | |
recommendedConfig, | |
allConfig | |
} = {}) { | |
this.baseDirectory = baseDirectory; | |
this.resolvePluginsRelativeTo = resolvePluginsRelativeTo; | |
this[cafactory] = new ConfigArrayFactory({ | |
cwd: baseDirectory, | |
resolvePluginsRelativeTo, | |
getEslintAllConfig: () => { | |
if (!allConfig) { | |
throw new TypeError("Missing parameter 'allConfig' in FlatCompat constructor."); | |
} | |
return allConfig; | |
}, | |
getEslintRecommendedConfig: () => { | |
if (!recommendedConfig) { | |
throw new TypeError("Missing parameter 'recommendedConfig' in FlatCompat constructor."); | |
} | |
return recommendedConfig; | |
} | |
}); | |
} | |
/** | |
* 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[]} An array of flag-config objects 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[]} An array of flag-config objects 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[]} An array of flag-config objects representing the plugins. | |
*/ | |
plugins(...plugins) { | |
return this.config({ | |
plugins | |
}); | |
} | |
} | |
export { FlatCompat }; | |