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