.gitignore added
[dotfiles/.git] / .config / coc / extensions / node_modules / coc-prettier / node_modules / stylelint / lib / augmentConfig.js
1 /* @flow */
2 "use strict";
3 const _ = require("lodash");
4 const configurationError = require("./utils/configurationError");
5 const dynamicRequire = require("./dynamicRequire");
6 const getModulePath = require("./utils/getModulePath");
7 const globjoin = require("globjoin");
8 const normalizeRuleSettings = require("./normalizeRuleSettings");
9 const path = require("path");
10 const rules = require("./rules");
11
12 // - Merges config and configOverrides
13 // - Makes all paths absolute
14 // - Merges extends
15 function augmentConfigBasic(
16   stylelint /*: stylelint$internalApi*/,
17   config /*: stylelint$config*/,
18   configDir /*: string*/,
19   allowOverrides /*:: ?: boolean*/
20 ) /*: Promise<stylelint$config>*/ {
21   return Promise.resolve()
22     .then(() => {
23       if (!allowOverrides) return config;
24       return _.merge(config, stylelint._options.configOverrides);
25     })
26     .then(augmentedConfig => {
27       return extendConfig(stylelint, augmentedConfig, configDir);
28     })
29     .then(augmentedConfig => {
30       return absolutizePaths(augmentedConfig, configDir);
31     });
32 }
33
34 // Extended configs need to be run through augmentConfigBasic
35 // but do not need the full treatment. Things like pluginFunctions
36 // will be resolved and added by the parent config.
37 function augmentConfigExtended(
38   stylelint /*: stylelint$internalApi*/,
39   cosmiconfigResultArg /*: ?{
40      config: stylelint$config,
41      filepath: string,
42    }*/
43 ) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
44   const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow
45   if (!cosmiconfigResult) return Promise.resolve(null);
46
47   const configDir = path.dirname(cosmiconfigResult.filepath || "");
48   const cleanedConfig = _.omit(cosmiconfigResult.config, "ignoreFiles");
49   return augmentConfigBasic(stylelint, cleanedConfig, configDir).then(
50     augmentedConfig => {
51       return {
52         config: augmentedConfig,
53         filepath: cosmiconfigResult.filepath
54       };
55     }
56   );
57 }
58
59 function augmentConfigFull(
60   stylelint /*: stylelint$internalApi*/,
61   cosmiconfigResultArg /*: ?{
62    config: stylelint$config,
63    filepath: string,
64   }*/
65 ) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
66   const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow
67   if (!cosmiconfigResult) return Promise.resolve(null);
68
69   const config = cosmiconfigResult.config,
70     filepath = cosmiconfigResult.filepath;
71
72   const configDir =
73     stylelint._options.configBasedir || path.dirname(filepath || "");
74
75   return augmentConfigBasic(stylelint, config, configDir, true)
76     .then(augmentedConfig => {
77       return addPluginFunctions(augmentedConfig);
78     })
79     .then(augmentedConfig => {
80       return addProcessorFunctions(augmentedConfig);
81     })
82     .then(augmentedConfig => {
83       if (!augmentedConfig.rules) {
84         throw configurationError(
85           'No rules found within configuration. Have you provided a "rules" property?'
86         );
87       }
88
89       return normalizeAllRuleSettings(augmentedConfig);
90     })
91     .then(augmentedConfig => {
92       return {
93         config: augmentedConfig,
94         filepath: cosmiconfigResult.filepath
95       };
96     });
97 }
98
99 // Make all paths in the config absolute:
100 // - ignoreFiles
101 // - plugins
102 // - processors
103 // (extends handled elsewhere)
104 function absolutizePaths(
105   config /*: stylelint$config*/,
106   configDir /*: string*/
107 ) /*: stylelint$config*/ {
108   if (config.ignoreFiles) {
109     config.ignoreFiles = [].concat(config.ignoreFiles).map(glob => {
110       if (path.isAbsolute(glob.replace(/^!/, ""))) return glob;
111       return globjoin(configDir, glob);
112     });
113   }
114
115   if (config.plugins) {
116     config.plugins = [].concat(config.plugins).map(lookup => {
117       return getModulePath(configDir, lookup);
118     });
119   }
120
121   if (config.processors) {
122     config.processors = absolutizeProcessors(config.processors, configDir);
123   }
124
125   return config;
126 }
127
128 // Processors are absolutized in their own way because
129 // they can be and return a string or an array
130 function absolutizeProcessors(
131   processors /*: stylelint$configProcessors*/,
132   configDir /*: string*/
133 ) /*: stylelint$configProcessors*/ {
134   const normalizedProcessors = Array.isArray(processors)
135     ? processors
136     : [processors];
137
138   return normalizedProcessors.map(item => {
139     if (typeof item === "string") {
140       return getModulePath(configDir, item);
141     }
142
143     return [getModulePath(configDir, item[0]), item[1]];
144   });
145 }
146
147 function extendConfig(
148   stylelint /*: stylelint$internalApi*/,
149   config /*: stylelint$config*/,
150   configDir /*: string*/
151 ) /*: Promise<stylelint$config>*/ {
152   if (config.extends === undefined) return Promise.resolve(config);
153   const normalizedExtends = Array.isArray(config.extends)
154     ? config.extends
155     : [config.extends];
156
157   const originalWithoutExtends = _.omit(config, "extends");
158   const loadExtends = normalizedExtends.reduce(
159     (resultPromise, extendLookup) => {
160       return resultPromise.then(resultConfig => {
161         return loadExtendedConfig(
162           stylelint,
163           resultConfig,
164           configDir,
165           extendLookup
166         ).then(extendResult => {
167           if (!extendResult) return resultConfig;
168           return mergeConfigs(resultConfig, extendResult.config);
169         });
170       });
171     },
172     Promise.resolve(originalWithoutExtends)
173   );
174
175   return loadExtends.then(resultConfig => {
176     return mergeConfigs(resultConfig, originalWithoutExtends);
177   });
178 }
179
180 function loadExtendedConfig(
181   stylelint /*: stylelint$internalApi*/,
182   config /*: stylelint$config*/,
183   configDir /*: string*/,
184   extendLookup /*: string*/
185 ) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
186   const extendPath = getModulePath(configDir, extendLookup);
187   return stylelint._extendExplorer.load(null, extendPath);
188 }
189
190 // When merging configs (via extends)
191 // - plugin and processor arrays are joined
192 // - rules are merged via Object.assign, so there is no attempt made to
193 //   merge any given rule's settings. If b contains the same rule as a,
194 //   b's rule settings will override a's rule settings entirely.
195 // - Everything else is merged via Object.assign
196 function mergeConfigs(
197   a /*: stylelint$config*/,
198   b /*: stylelint$config*/
199 ) /*: stylelint$config*/ {
200   const pluginMerger = {};
201   if (a.plugins || b.plugins) {
202     pluginMerger.plugins = [];
203     if (a.plugins) {
204       pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
205     }
206     if (b.plugins) {
207       pluginMerger.plugins = _.uniq(pluginMerger.plugins.concat(b.plugins));
208     }
209   }
210
211   const processorMerger = {};
212   if (a.processors || b.processors) {
213     processorMerger.processors = [];
214     if (a.processors) {
215       processorMerger.processors = processorMerger.processors.concat(
216         a.processors
217       );
218     }
219     if (b.processors) {
220       processorMerger.processors = _.uniq(
221         processorMerger.processors.concat(b.processors)
222       );
223     }
224   }
225
226   const rulesMerger = {};
227   if (a.rules || b.rules) {
228     rulesMerger.rules = Object.assign({}, a.rules, b.rules);
229   }
230
231   const result = Object.assign(
232     {},
233     a,
234     b,
235     processorMerger,
236     pluginMerger,
237     rulesMerger
238   );
239   return result;
240 }
241
242 function addPluginFunctions(
243   config /*: stylelint$config*/
244 ) /*: stylelint$config*/ {
245   if (!config.plugins) return config;
246
247   const normalizedPlugins = Array.isArray(config.plugins)
248     ? config.plugins
249     : [config.plugins];
250
251   const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => {
252     let pluginImport = dynamicRequire(pluginLookup);
253     // Handle either ES6 or CommonJS modules
254     pluginImport = pluginImport.default || pluginImport;
255
256     // A plugin can export either a single rule definition
257     // or an array of them
258     const normalizedPluginImport = Array.isArray(pluginImport)
259       ? pluginImport
260       : [pluginImport];
261
262     normalizedPluginImport.forEach(pluginRuleDefinition => {
263       if (!pluginRuleDefinition.ruleName) {
264         throw configurationError(
265           "stylelint v3+ requires plugins to expose a ruleName. " +
266             `The plugin "${
267               pluginLookup
268             }" is not doing this, so will not work ` +
269             "with stylelint v3+. Please file an issue with the plugin."
270         );
271       }
272
273       if (!_.includes(pluginRuleDefinition.ruleName, "/")) {
274         throw configurationError(
275           "stylelint v7+ requires plugin rules to be namspaced, " +
276             "i.e. only `plugin-namespace/plugin-rule-name` plugin rule names are supported. " +
277             `The plugin rule "${
278               pluginRuleDefinition.ruleName
279             }" does not do this, so will not work. ` +
280             "Please file an issue with the plugin."
281         );
282       }
283
284       result[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
285     });
286
287     return result;
288   }, {});
289
290   config.pluginFunctions = pluginFunctions;
291   return config;
292 }
293
294 function normalizeAllRuleSettings(
295   config /*: stylelint$config*/
296 ) /*: stylelint$config*/ {
297   const normalizedRules = {};
298   if (!config.rules) return config;
299   Object.keys(config.rules).forEach(ruleName => {
300     const rawRuleSettings = _.get(config, ["rules", ruleName]);
301     const rule =
302       rules[ruleName] || _.get(config, ["pluginFunctions", ruleName]);
303     if (!rule) {
304       throw configurationError(`Undefined rule ${ruleName}`);
305     }
306     normalizedRules[ruleName] = normalizeRuleSettings(
307       rawRuleSettings,
308       ruleName,
309       _.get(rule, "primaryOptionArray")
310     );
311   });
312   config.rules = normalizedRules;
313   return config;
314 }
315
316 // Given an array of processors strings, we want to add two
317 // properties to the augmented config:
318 // - codeProcessors: functions that will run on code as it comes in
319 // - resultProcessors: functions that will run on results as they go out
320 //
321 // To create these properties, we need to:
322 // - Find the processor module
323 // - Intialize the processor module by calling its functions with any
324 //   provided options
325 // - Push the processor's code and result processors to their respective arrays
326 const processorCache = new Map();
327 function addProcessorFunctions(
328   config /*: stylelint$config*/
329 ) /*: stylelint$config*/ {
330   if (!config.processors) return config;
331
332   const codeProcessors = [];
333   const resultProcessors = [];
334   [].concat(config.processors).forEach(processorConfig => {
335     const processorKey = JSON.stringify(processorConfig);
336
337     let initializedProcessor;
338     if (processorCache.has(processorKey)) {
339       initializedProcessor = processorCache.get(processorKey);
340     } else {
341       processorConfig = [].concat(processorConfig);
342       const processorLookup = processorConfig[0];
343       const processorOptions = processorConfig[1];
344       let processor = dynamicRequire(processorLookup);
345       processor = processor.default || processor;
346       initializedProcessor = processor(processorOptions);
347       processorCache.set(processorKey, initializedProcessor);
348     }
349
350     if (initializedProcessor && initializedProcessor.code) {
351       codeProcessors.push(initializedProcessor.code);
352     }
353     if (initializedProcessor && initializedProcessor.result) {
354       resultProcessors.push(initializedProcessor.result);
355     }
356   });
357
358   config.codeProcessors = codeProcessors;
359   config.resultProcessors = resultProcessors;
360   return config;
361 }
362
363 module.exports = { augmentConfigExtended, augmentConfigFull };