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");
12 // - Merges config and configOverrides
13 // - Makes all paths absolute
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()
23 if (!allowOverrides) return config;
24 return _.merge(config, stylelint._options.configOverrides);
26 .then(augmentedConfig => {
27 return extendConfig(stylelint, augmentedConfig, configDir);
29 .then(augmentedConfig => {
30 return absolutizePaths(augmentedConfig, configDir);
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,
43 ) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
44 const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow
45 if (!cosmiconfigResult) return Promise.resolve(null);
47 const configDir = path.dirname(cosmiconfigResult.filepath || "");
48 const cleanedConfig = _.omit(cosmiconfigResult.config, "ignoreFiles");
49 return augmentConfigBasic(stylelint, cleanedConfig, configDir).then(
52 config: augmentedConfig,
53 filepath: cosmiconfigResult.filepath
59 function augmentConfigFull(
60 stylelint /*: stylelint$internalApi*/,
61 cosmiconfigResultArg /*: ?{
62 config: stylelint$config,
65 ) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
66 const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow
67 if (!cosmiconfigResult) return Promise.resolve(null);
69 const config = cosmiconfigResult.config,
70 filepath = cosmiconfigResult.filepath;
73 stylelint._options.configBasedir || path.dirname(filepath || "");
75 return augmentConfigBasic(stylelint, config, configDir, true)
76 .then(augmentedConfig => {
77 return addPluginFunctions(augmentedConfig);
79 .then(augmentedConfig => {
80 return addProcessorFunctions(augmentedConfig);
82 .then(augmentedConfig => {
83 if (!augmentedConfig.rules) {
84 throw configurationError(
85 'No rules found within configuration. Have you provided a "rules" property?'
89 return normalizeAllRuleSettings(augmentedConfig);
91 .then(augmentedConfig => {
93 config: augmentedConfig,
94 filepath: cosmiconfigResult.filepath
99 // Make all paths in the config absolute:
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);
115 if (config.plugins) {
116 config.plugins = [].concat(config.plugins).map(lookup => {
117 return getModulePath(configDir, lookup);
121 if (config.processors) {
122 config.processors = absolutizeProcessors(config.processors, configDir);
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)
138 return normalizedProcessors.map(item => {
139 if (typeof item === "string") {
140 return getModulePath(configDir, item);
143 return [getModulePath(configDir, item[0]), item[1]];
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)
157 const originalWithoutExtends = _.omit(config, "extends");
158 const loadExtends = normalizedExtends.reduce(
159 (resultPromise, extendLookup) => {
160 return resultPromise.then(resultConfig => {
161 return loadExtendedConfig(
166 ).then(extendResult => {
167 if (!extendResult) return resultConfig;
168 return mergeConfigs(resultConfig, extendResult.config);
172 Promise.resolve(originalWithoutExtends)
175 return loadExtends.then(resultConfig => {
176 return mergeConfigs(resultConfig, originalWithoutExtends);
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);
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 = [];
204 pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
207 pluginMerger.plugins = _.uniq(pluginMerger.plugins.concat(b.plugins));
211 const processorMerger = {};
212 if (a.processors || b.processors) {
213 processorMerger.processors = [];
215 processorMerger.processors = processorMerger.processors.concat(
220 processorMerger.processors = _.uniq(
221 processorMerger.processors.concat(b.processors)
226 const rulesMerger = {};
227 if (a.rules || b.rules) {
228 rulesMerger.rules = Object.assign({}, a.rules, b.rules);
231 const result = Object.assign(
242 function addPluginFunctions(
243 config /*: stylelint$config*/
244 ) /*: stylelint$config*/ {
245 if (!config.plugins) return config;
247 const normalizedPlugins = Array.isArray(config.plugins)
251 const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => {
252 let pluginImport = dynamicRequire(pluginLookup);
253 // Handle either ES6 or CommonJS modules
254 pluginImport = pluginImport.default || pluginImport;
256 // A plugin can export either a single rule definition
257 // or an array of them
258 const normalizedPluginImport = Array.isArray(pluginImport)
262 normalizedPluginImport.forEach(pluginRuleDefinition => {
263 if (!pluginRuleDefinition.ruleName) {
264 throw configurationError(
265 "stylelint v3+ requires plugins to expose a ruleName. " +
268 }" is not doing this, so will not work ` +
269 "with stylelint v3+. Please file an issue with the plugin."
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. " +
278 pluginRuleDefinition.ruleName
279 }" does not do this, so will not work. ` +
280 "Please file an issue with the plugin."
284 result[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
290 config.pluginFunctions = pluginFunctions;
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]);
302 rules[ruleName] || _.get(config, ["pluginFunctions", ruleName]);
304 throw configurationError(`Undefined rule ${ruleName}`);
306 normalizedRules[ruleName] = normalizeRuleSettings(
309 _.get(rule, "primaryOptionArray")
312 config.rules = normalizedRules;
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
321 // To create these properties, we need to:
322 // - Find the processor module
323 // - Intialize the processor module by calling its functions with any
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;
332 const codeProcessors = [];
333 const resultProcessors = [];
334 [].concat(config.processors).forEach(processorConfig => {
335 const processorKey = JSON.stringify(processorConfig);
337 let initializedProcessor;
338 if (processorCache.has(processorKey)) {
339 initializedProcessor = processorCache.get(processorKey);
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);
350 if (initializedProcessor && initializedProcessor.code) {
351 codeProcessors.push(initializedProcessor.code);
353 if (initializedProcessor && initializedProcessor.result) {
354 resultProcessors.push(initializedProcessor.result);
358 config.codeProcessors = codeProcessors;
359 config.resultProcessors = resultProcessors;
363 module.exports = { augmentConfigExtended, augmentConfigFull };