1 | /**
|
---|
2 | * @fileoverview Compatibility class for flat config.
|
---|
3 | * @author Nicholas C. Zakas
|
---|
4 | */
|
---|
5 |
|
---|
6 | //-----------------------------------------------------------------------------
|
---|
7 | // Requirements
|
---|
8 | //-----------------------------------------------------------------------------
|
---|
9 |
|
---|
10 | import createDebug from "debug";
|
---|
11 | import path from "path";
|
---|
12 |
|
---|
13 | import environments from "../conf/environments.js";
|
---|
14 | import { ConfigArrayFactory } from "./config-array-factory.js";
|
---|
15 |
|
---|
16 | //-----------------------------------------------------------------------------
|
---|
17 | // Helpers
|
---|
18 | //-----------------------------------------------------------------------------
|
---|
19 |
|
---|
20 | /** @typedef {import("../../shared/types").Environment} Environment */
|
---|
21 | /** @typedef {import("../../shared/types").Processor} Processor */
|
---|
22 |
|
---|
23 | const debug = createDebug("eslintrc:flat-compat");
|
---|
24 | const cafactory = Symbol("cafactory");
|
---|
25 |
|
---|
26 | /**
|
---|
27 | * Translates an ESLintRC-style config object into a flag-config-style config
|
---|
28 | * object.
|
---|
29 | * @param {Object} eslintrcConfig An ESLintRC-style config object.
|
---|
30 | * @param {Object} options Options to help translate the config.
|
---|
31 | * @param {string} options.resolveConfigRelativeTo To the directory to resolve
|
---|
32 | * configs from.
|
---|
33 | * @param {string} options.resolvePluginsRelativeTo The directory to resolve
|
---|
34 | * plugins from.
|
---|
35 | * @param {ReadOnlyMap<string,Environment>} options.pluginEnvironments A map of plugin environment
|
---|
36 | * names to objects.
|
---|
37 | * @param {ReadOnlyMap<string,Processor>} options.pluginProcessors A map of plugin processor
|
---|
38 | * names to objects.
|
---|
39 | * @returns {Object} A flag-config-style config object.
|
---|
40 | */
|
---|
41 | function translateESLintRC(eslintrcConfig, {
|
---|
42 | resolveConfigRelativeTo,
|
---|
43 | resolvePluginsRelativeTo,
|
---|
44 | pluginEnvironments,
|
---|
45 | pluginProcessors
|
---|
46 | }) {
|
---|
47 |
|
---|
48 | const flatConfig = {};
|
---|
49 | const configs = [];
|
---|
50 | const languageOptions = {};
|
---|
51 | const linterOptions = {};
|
---|
52 | const keysToCopy = ["settings", "rules", "processor"];
|
---|
53 | const languageOptionsKeysToCopy = ["globals", "parser", "parserOptions"];
|
---|
54 | const linterOptionsKeysToCopy = ["noInlineConfig", "reportUnusedDisableDirectives"];
|
---|
55 |
|
---|
56 | // copy over simple translations
|
---|
57 | for (const key of keysToCopy) {
|
---|
58 | if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
|
---|
59 | flatConfig[key] = eslintrcConfig[key];
|
---|
60 | }
|
---|
61 | }
|
---|
62 |
|
---|
63 | // copy over languageOptions
|
---|
64 | for (const key of languageOptionsKeysToCopy) {
|
---|
65 | if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
|
---|
66 |
|
---|
67 | // create the languageOptions key in the flat config
|
---|
68 | flatConfig.languageOptions = languageOptions;
|
---|
69 |
|
---|
70 | if (key === "parser") {
|
---|
71 | debug(`Resolving parser '${languageOptions[key]}' relative to ${resolveConfigRelativeTo}`);
|
---|
72 |
|
---|
73 | if (eslintrcConfig[key].error) {
|
---|
74 | throw eslintrcConfig[key].error;
|
---|
75 | }
|
---|
76 |
|
---|
77 | languageOptions[key] = eslintrcConfig[key].definition;
|
---|
78 | continue;
|
---|
79 | }
|
---|
80 |
|
---|
81 | // clone any object values that are in the eslintrc config
|
---|
82 | if (eslintrcConfig[key] && typeof eslintrcConfig[key] === "object") {
|
---|
83 | languageOptions[key] = {
|
---|
84 | ...eslintrcConfig[key]
|
---|
85 | };
|
---|
86 | } else {
|
---|
87 | languageOptions[key] = eslintrcConfig[key];
|
---|
88 | }
|
---|
89 | }
|
---|
90 | }
|
---|
91 |
|
---|
92 | // copy over linterOptions
|
---|
93 | for (const key of linterOptionsKeysToCopy) {
|
---|
94 | if (key in eslintrcConfig && typeof eslintrcConfig[key] !== "undefined") {
|
---|
95 | flatConfig.linterOptions = linterOptions;
|
---|
96 | linterOptions[key] = eslintrcConfig[key];
|
---|
97 | }
|
---|
98 | }
|
---|
99 |
|
---|
100 | // move ecmaVersion a level up
|
---|
101 | if (languageOptions.parserOptions) {
|
---|
102 |
|
---|
103 | if ("ecmaVersion" in languageOptions.parserOptions) {
|
---|
104 | languageOptions.ecmaVersion = languageOptions.parserOptions.ecmaVersion;
|
---|
105 | delete languageOptions.parserOptions.ecmaVersion;
|
---|
106 | }
|
---|
107 |
|
---|
108 | if ("sourceType" in languageOptions.parserOptions) {
|
---|
109 | languageOptions.sourceType = languageOptions.parserOptions.sourceType;
|
---|
110 | delete languageOptions.parserOptions.sourceType;
|
---|
111 | }
|
---|
112 |
|
---|
113 | // check to see if we even need parserOptions anymore and remove it if not
|
---|
114 | if (Object.keys(languageOptions.parserOptions).length === 0) {
|
---|
115 | delete languageOptions.parserOptions;
|
---|
116 | }
|
---|
117 | }
|
---|
118 |
|
---|
119 | // overrides
|
---|
120 | if (eslintrcConfig.criteria) {
|
---|
121 | flatConfig.files = [absoluteFilePath => eslintrcConfig.criteria.test(absoluteFilePath)];
|
---|
122 | }
|
---|
123 |
|
---|
124 | // translate plugins
|
---|
125 | if (eslintrcConfig.plugins && typeof eslintrcConfig.plugins === "object") {
|
---|
126 | debug(`Translating plugins: ${eslintrcConfig.plugins}`);
|
---|
127 |
|
---|
128 | flatConfig.plugins = {};
|
---|
129 |
|
---|
130 | for (const pluginName of Object.keys(eslintrcConfig.plugins)) {
|
---|
131 |
|
---|
132 | debug(`Translating plugin: ${pluginName}`);
|
---|
133 | debug(`Resolving plugin '${pluginName} relative to ${resolvePluginsRelativeTo}`);
|
---|
134 |
|
---|
135 | const { original: plugin, error } = eslintrcConfig.plugins[pluginName];
|
---|
136 |
|
---|
137 | if (error) {
|
---|
138 | throw error;
|
---|
139 | }
|
---|
140 |
|
---|
141 | flatConfig.plugins[pluginName] = plugin;
|
---|
142 |
|
---|
143 | // create a config for any processors
|
---|
144 | if (plugin.processors) {
|
---|
145 | for (const processorName of Object.keys(plugin.processors)) {
|
---|
146 | if (processorName.startsWith(".")) {
|
---|
147 | debug(`Assigning processor: ${pluginName}/${processorName}`);
|
---|
148 |
|
---|
149 | configs.unshift({
|
---|
150 | files: [`**/*${processorName}`],
|
---|
151 | processor: pluginProcessors.get(`${pluginName}/${processorName}`)
|
---|
152 | });
|
---|
153 | }
|
---|
154 |
|
---|
155 | }
|
---|
156 | }
|
---|
157 | }
|
---|
158 | }
|
---|
159 |
|
---|
160 | // translate env - must come after plugins
|
---|
161 | if (eslintrcConfig.env && typeof eslintrcConfig.env === "object") {
|
---|
162 | for (const envName of Object.keys(eslintrcConfig.env)) {
|
---|
163 |
|
---|
164 | // only add environments that are true
|
---|
165 | if (eslintrcConfig.env[envName]) {
|
---|
166 | debug(`Translating environment: ${envName}`);
|
---|
167 |
|
---|
168 | if (environments.has(envName)) {
|
---|
169 |
|
---|
170 | // built-in environments should be defined first
|
---|
171 | configs.unshift(...translateESLintRC({
|
---|
172 | criteria: eslintrcConfig.criteria,
|
---|
173 | ...environments.get(envName)
|
---|
174 | }, {
|
---|
175 | resolveConfigRelativeTo,
|
---|
176 | resolvePluginsRelativeTo
|
---|
177 | }));
|
---|
178 | } else if (pluginEnvironments.has(envName)) {
|
---|
179 |
|
---|
180 | // if the environment comes from a plugin, it should come after the plugin config
|
---|
181 | configs.push(...translateESLintRC({
|
---|
182 | criteria: eslintrcConfig.criteria,
|
---|
183 | ...pluginEnvironments.get(envName)
|
---|
184 | }, {
|
---|
185 | resolveConfigRelativeTo,
|
---|
186 | resolvePluginsRelativeTo
|
---|
187 | }));
|
---|
188 | }
|
---|
189 | }
|
---|
190 | }
|
---|
191 | }
|
---|
192 |
|
---|
193 | // only add if there are actually keys in the config
|
---|
194 | if (Object.keys(flatConfig).length > 0) {
|
---|
195 | configs.push(flatConfig);
|
---|
196 | }
|
---|
197 |
|
---|
198 | return configs;
|
---|
199 | }
|
---|
200 |
|
---|
201 |
|
---|
202 | //-----------------------------------------------------------------------------
|
---|
203 | // Exports
|
---|
204 | //-----------------------------------------------------------------------------
|
---|
205 |
|
---|
206 | /**
|
---|
207 | * A compatibility class for working with configs.
|
---|
208 | */
|
---|
209 | class FlatCompat {
|
---|
210 |
|
---|
211 | constructor({
|
---|
212 | baseDirectory = process.cwd(),
|
---|
213 | resolvePluginsRelativeTo = baseDirectory,
|
---|
214 | recommendedConfig,
|
---|
215 | allConfig
|
---|
216 | } = {}) {
|
---|
217 | this.baseDirectory = baseDirectory;
|
---|
218 | this.resolvePluginsRelativeTo = resolvePluginsRelativeTo;
|
---|
219 | this[cafactory] = new ConfigArrayFactory({
|
---|
220 | cwd: baseDirectory,
|
---|
221 | resolvePluginsRelativeTo,
|
---|
222 | getEslintAllConfig: () => {
|
---|
223 |
|
---|
224 | if (!allConfig) {
|
---|
225 | throw new TypeError("Missing parameter 'allConfig' in FlatCompat constructor.");
|
---|
226 | }
|
---|
227 |
|
---|
228 | return allConfig;
|
---|
229 | },
|
---|
230 | getEslintRecommendedConfig: () => {
|
---|
231 |
|
---|
232 | if (!recommendedConfig) {
|
---|
233 | throw new TypeError("Missing parameter 'recommendedConfig' in FlatCompat constructor.");
|
---|
234 | }
|
---|
235 |
|
---|
236 | return recommendedConfig;
|
---|
237 | }
|
---|
238 | });
|
---|
239 | }
|
---|
240 |
|
---|
241 | /**
|
---|
242 | * Translates an ESLintRC-style config into a flag-config-style config.
|
---|
243 | * @param {Object} eslintrcConfig The ESLintRC-style config object.
|
---|
244 | * @returns {Object} A flag-config-style config object.
|
---|
245 | */
|
---|
246 | config(eslintrcConfig) {
|
---|
247 | const eslintrcArray = this[cafactory].create(eslintrcConfig, {
|
---|
248 | basePath: this.baseDirectory
|
---|
249 | });
|
---|
250 |
|
---|
251 | const flatArray = [];
|
---|
252 | let hasIgnorePatterns = false;
|
---|
253 |
|
---|
254 | eslintrcArray.forEach(configData => {
|
---|
255 | if (configData.type === "config") {
|
---|
256 | hasIgnorePatterns = hasIgnorePatterns || configData.ignorePattern;
|
---|
257 | flatArray.push(...translateESLintRC(configData, {
|
---|
258 | resolveConfigRelativeTo: path.join(this.baseDirectory, "__placeholder.js"),
|
---|
259 | resolvePluginsRelativeTo: path.join(this.resolvePluginsRelativeTo, "__placeholder.js"),
|
---|
260 | pluginEnvironments: eslintrcArray.pluginEnvironments,
|
---|
261 | pluginProcessors: eslintrcArray.pluginProcessors
|
---|
262 | }));
|
---|
263 | }
|
---|
264 | });
|
---|
265 |
|
---|
266 | // combine ignorePatterns to emulate ESLintRC behavior better
|
---|
267 | if (hasIgnorePatterns) {
|
---|
268 | flatArray.unshift({
|
---|
269 | ignores: [filePath => {
|
---|
270 |
|
---|
271 | // Compute the final config for this file.
|
---|
272 | // This filters config array elements by `files`/`excludedFiles` then merges the elements.
|
---|
273 | const finalConfig = eslintrcArray.extractConfig(filePath);
|
---|
274 |
|
---|
275 | // Test the `ignorePattern` properties of the final config.
|
---|
276 | return Boolean(finalConfig.ignores) && finalConfig.ignores(filePath);
|
---|
277 | }]
|
---|
278 | });
|
---|
279 | }
|
---|
280 |
|
---|
281 | return flatArray;
|
---|
282 | }
|
---|
283 |
|
---|
284 | /**
|
---|
285 | * Translates the `env` section of an ESLintRC-style config.
|
---|
286 | * @param {Object} envConfig The `env` section of an ESLintRC config.
|
---|
287 | * @returns {Object[]} An array of flag-config objects representing the environments.
|
---|
288 | */
|
---|
289 | env(envConfig) {
|
---|
290 | return this.config({
|
---|
291 | env: envConfig
|
---|
292 | });
|
---|
293 | }
|
---|
294 |
|
---|
295 | /**
|
---|
296 | * Translates the `extends` section of an ESLintRC-style config.
|
---|
297 | * @param {...string} configsToExtend The names of the configs to load.
|
---|
298 | * @returns {Object[]} An array of flag-config objects representing the config.
|
---|
299 | */
|
---|
300 | extends(...configsToExtend) {
|
---|
301 | return this.config({
|
---|
302 | extends: configsToExtend
|
---|
303 | });
|
---|
304 | }
|
---|
305 |
|
---|
306 | /**
|
---|
307 | * Translates the `plugins` section of an ESLintRC-style config.
|
---|
308 | * @param {...string} plugins The names of the plugins to load.
|
---|
309 | * @returns {Object[]} An array of flag-config objects representing the plugins.
|
---|
310 | */
|
---|
311 | plugins(...plugins) {
|
---|
312 | return this.config({
|
---|
313 | plugins
|
---|
314 | });
|
---|
315 | }
|
---|
316 | }
|
---|
317 |
|
---|
318 | export { FlatCompat };
|
---|