-
-
Notifications
You must be signed in to change notification settings - Fork 43
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
New: Implement DotCompat class (#20)
* New: DotCompat class * More test and functionality * Dot compat cleanup * Add tests for extends * Finish up * Unignore fixture files * Update README.md Co-authored-by: Brandon Mills <[email protected]> * Update lib/dot-compat.js Co-authored-by: Brandon Mills <[email protected]> * Rename 'dot config' to 'flat config' * Allow functions in ignores array * Fix overrides behavior * Fix incorrect translation of language options * Throw errors when parser/plugin is missing * Combine ignore patterns * Fix linting errors * Fix typo * Fix failing test * Pass in pluginEnvironments to translateESLintRC() * Pass in pluginProcessors to translateESLintRC() * Code cleanup * Update README * fix errors * Cleanup package.json conflict * Finish tests * fix lint errors Co-authored-by: Brandon Mills <[email protected]>
- Loading branch information
Showing
15 changed files
with
1,440 additions
and
52 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* @fileoverview Stub eslint:all config | ||
* @author Nicholas C. Zakas | ||
*/ | ||
|
||
"use strict"; | ||
|
||
module.exports = { | ||
settings: { | ||
"eslint:all": true | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
/** | ||
* @fileoverview Stub eslint:recommended config | ||
* @author Nicholas C. Zakas | ||
*/ | ||
|
||
"use strict"; | ||
|
||
module.exports = { | ||
settings: { | ||
"eslint:recommended": true | ||
} | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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<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 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; |
Oops, something went wrong.