--- /dev/null
+/* @flow */
+"use strict";
+const _ = require("lodash");
+const configurationError = require("./utils/configurationError");
+const dynamicRequire = require("./dynamicRequire");
+const getModulePath = require("./utils/getModulePath");
+const globjoin = require("globjoin");
+const normalizeRuleSettings = require("./normalizeRuleSettings");
+const path = require("path");
+const rules = require("./rules");
+
+// - Merges config and configOverrides
+// - Makes all paths absolute
+// - Merges extends
+function augmentConfigBasic(
+ stylelint /*: stylelint$internalApi*/,
+ config /*: stylelint$config*/,
+ configDir /*: string*/,
+ allowOverrides /*:: ?: boolean*/
+) /*: Promise<stylelint$config>*/ {
+ return Promise.resolve()
+ .then(() => {
+ if (!allowOverrides) return config;
+ return _.merge(config, stylelint._options.configOverrides);
+ })
+ .then(augmentedConfig => {
+ return extendConfig(stylelint, augmentedConfig, configDir);
+ })
+ .then(augmentedConfig => {
+ return absolutizePaths(augmentedConfig, configDir);
+ });
+}
+
+// Extended configs need to be run through augmentConfigBasic
+// but do not need the full treatment. Things like pluginFunctions
+// will be resolved and added by the parent config.
+function augmentConfigExtended(
+ stylelint /*: stylelint$internalApi*/,
+ cosmiconfigResultArg /*: ?{
+ config: stylelint$config,
+ filepath: string,
+ }*/
+) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
+ const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow
+ if (!cosmiconfigResult) return Promise.resolve(null);
+
+ const configDir = path.dirname(cosmiconfigResult.filepath || "");
+ const cleanedConfig = _.omit(cosmiconfigResult.config, "ignoreFiles");
+ return augmentConfigBasic(stylelint, cleanedConfig, configDir).then(
+ augmentedConfig => {
+ return {
+ config: augmentedConfig,
+ filepath: cosmiconfigResult.filepath
+ };
+ }
+ );
+}
+
+function augmentConfigFull(
+ stylelint /*: stylelint$internalApi*/,
+ cosmiconfigResultArg /*: ?{
+ config: stylelint$config,
+ filepath: string,
+ }*/
+) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
+ const cosmiconfigResult = cosmiconfigResultArg; // Lock in for Flow
+ if (!cosmiconfigResult) return Promise.resolve(null);
+
+ const config = cosmiconfigResult.config,
+ filepath = cosmiconfigResult.filepath;
+
+ const configDir =
+ stylelint._options.configBasedir || path.dirname(filepath || "");
+
+ return augmentConfigBasic(stylelint, config, configDir, true)
+ .then(augmentedConfig => {
+ return addPluginFunctions(augmentedConfig);
+ })
+ .then(augmentedConfig => {
+ return addProcessorFunctions(augmentedConfig);
+ })
+ .then(augmentedConfig => {
+ if (!augmentedConfig.rules) {
+ throw configurationError(
+ 'No rules found within configuration. Have you provided a "rules" property?'
+ );
+ }
+
+ return normalizeAllRuleSettings(augmentedConfig);
+ })
+ .then(augmentedConfig => {
+ return {
+ config: augmentedConfig,
+ filepath: cosmiconfigResult.filepath
+ };
+ });
+}
+
+// Make all paths in the config absolute:
+// - ignoreFiles
+// - plugins
+// - processors
+// (extends handled elsewhere)
+function absolutizePaths(
+ config /*: stylelint$config*/,
+ configDir /*: string*/
+) /*: stylelint$config*/ {
+ if (config.ignoreFiles) {
+ config.ignoreFiles = [].concat(config.ignoreFiles).map(glob => {
+ if (path.isAbsolute(glob.replace(/^!/, ""))) return glob;
+ return globjoin(configDir, glob);
+ });
+ }
+
+ if (config.plugins) {
+ config.plugins = [].concat(config.plugins).map(lookup => {
+ return getModulePath(configDir, lookup);
+ });
+ }
+
+ if (config.processors) {
+ config.processors = absolutizeProcessors(config.processors, configDir);
+ }
+
+ return config;
+}
+
+// Processors are absolutized in their own way because
+// they can be and return a string or an array
+function absolutizeProcessors(
+ processors /*: stylelint$configProcessors*/,
+ configDir /*: string*/
+) /*: stylelint$configProcessors*/ {
+ const normalizedProcessors = Array.isArray(processors)
+ ? processors
+ : [processors];
+
+ return normalizedProcessors.map(item => {
+ if (typeof item === "string") {
+ return getModulePath(configDir, item);
+ }
+
+ return [getModulePath(configDir, item[0]), item[1]];
+ });
+}
+
+function extendConfig(
+ stylelint /*: stylelint$internalApi*/,
+ config /*: stylelint$config*/,
+ configDir /*: string*/
+) /*: Promise<stylelint$config>*/ {
+ if (config.extends === undefined) return Promise.resolve(config);
+ const normalizedExtends = Array.isArray(config.extends)
+ ? config.extends
+ : [config.extends];
+
+ const originalWithoutExtends = _.omit(config, "extends");
+ const loadExtends = normalizedExtends.reduce(
+ (resultPromise, extendLookup) => {
+ return resultPromise.then(resultConfig => {
+ return loadExtendedConfig(
+ stylelint,
+ resultConfig,
+ configDir,
+ extendLookup
+ ).then(extendResult => {
+ if (!extendResult) return resultConfig;
+ return mergeConfigs(resultConfig, extendResult.config);
+ });
+ });
+ },
+ Promise.resolve(originalWithoutExtends)
+ );
+
+ return loadExtends.then(resultConfig => {
+ return mergeConfigs(resultConfig, originalWithoutExtends);
+ });
+}
+
+function loadExtendedConfig(
+ stylelint /*: stylelint$internalApi*/,
+ config /*: stylelint$config*/,
+ configDir /*: string*/,
+ extendLookup /*: string*/
+) /*: Promise<?{ config: stylelint$config, filepath: string }>*/ {
+ const extendPath = getModulePath(configDir, extendLookup);
+ return stylelint._extendExplorer.load(null, extendPath);
+}
+
+// When merging configs (via extends)
+// - plugin and processor arrays are joined
+// - rules are merged via Object.assign, so there is no attempt made to
+// merge any given rule's settings. If b contains the same rule as a,
+// b's rule settings will override a's rule settings entirely.
+// - Everything else is merged via Object.assign
+function mergeConfigs(
+ a /*: stylelint$config*/,
+ b /*: stylelint$config*/
+) /*: stylelint$config*/ {
+ const pluginMerger = {};
+ if (a.plugins || b.plugins) {
+ pluginMerger.plugins = [];
+ if (a.plugins) {
+ pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
+ }
+ if (b.plugins) {
+ pluginMerger.plugins = _.uniq(pluginMerger.plugins.concat(b.plugins));
+ }
+ }
+
+ const processorMerger = {};
+ if (a.processors || b.processors) {
+ processorMerger.processors = [];
+ if (a.processors) {
+ processorMerger.processors = processorMerger.processors.concat(
+ a.processors
+ );
+ }
+ if (b.processors) {
+ processorMerger.processors = _.uniq(
+ processorMerger.processors.concat(b.processors)
+ );
+ }
+ }
+
+ const rulesMerger = {};
+ if (a.rules || b.rules) {
+ rulesMerger.rules = Object.assign({}, a.rules, b.rules);
+ }
+
+ const result = Object.assign(
+ {},
+ a,
+ b,
+ processorMerger,
+ pluginMerger,
+ rulesMerger
+ );
+ return result;
+}
+
+function addPluginFunctions(
+ config /*: stylelint$config*/
+) /*: stylelint$config*/ {
+ if (!config.plugins) return config;
+
+ const normalizedPlugins = Array.isArray(config.plugins)
+ ? config.plugins
+ : [config.plugins];
+
+ const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => {
+ let pluginImport = dynamicRequire(pluginLookup);
+ // Handle either ES6 or CommonJS modules
+ pluginImport = pluginImport.default || pluginImport;
+
+ // A plugin can export either a single rule definition
+ // or an array of them
+ const normalizedPluginImport = Array.isArray(pluginImport)
+ ? pluginImport
+ : [pluginImport];
+
+ normalizedPluginImport.forEach(pluginRuleDefinition => {
+ if (!pluginRuleDefinition.ruleName) {
+ throw configurationError(
+ "stylelint v3+ requires plugins to expose a ruleName. " +
+ `The plugin "${
+ pluginLookup
+ }" is not doing this, so will not work ` +
+ "with stylelint v3+. Please file an issue with the plugin."
+ );
+ }
+
+ if (!_.includes(pluginRuleDefinition.ruleName, "/")) {
+ throw configurationError(
+ "stylelint v7+ requires plugin rules to be namspaced, " +
+ "i.e. only `plugin-namespace/plugin-rule-name` plugin rule names are supported. " +
+ `The plugin rule "${
+ pluginRuleDefinition.ruleName
+ }" does not do this, so will not work. ` +
+ "Please file an issue with the plugin."
+ );
+ }
+
+ result[pluginRuleDefinition.ruleName] = pluginRuleDefinition.rule;
+ });
+
+ return result;
+ }, {});
+
+ config.pluginFunctions = pluginFunctions;
+ return config;
+}
+
+function normalizeAllRuleSettings(
+ config /*: stylelint$config*/
+) /*: stylelint$config*/ {
+ const normalizedRules = {};
+ if (!config.rules) return config;
+ Object.keys(config.rules).forEach(ruleName => {
+ const rawRuleSettings = _.get(config, ["rules", ruleName]);
+ const rule =
+ rules[ruleName] || _.get(config, ["pluginFunctions", ruleName]);
+ if (!rule) {
+ throw configurationError(`Undefined rule ${ruleName}`);
+ }
+ normalizedRules[ruleName] = normalizeRuleSettings(
+ rawRuleSettings,
+ ruleName,
+ _.get(rule, "primaryOptionArray")
+ );
+ });
+ config.rules = normalizedRules;
+ return config;
+}
+
+// Given an array of processors strings, we want to add two
+// properties to the augmented config:
+// - codeProcessors: functions that will run on code as it comes in
+// - resultProcessors: functions that will run on results as they go out
+//
+// To create these properties, we need to:
+// - Find the processor module
+// - Intialize the processor module by calling its functions with any
+// provided options
+// - Push the processor's code and result processors to their respective arrays
+const processorCache = new Map();
+function addProcessorFunctions(
+ config /*: stylelint$config*/
+) /*: stylelint$config*/ {
+ if (!config.processors) return config;
+
+ const codeProcessors = [];
+ const resultProcessors = [];
+ [].concat(config.processors).forEach(processorConfig => {
+ const processorKey = JSON.stringify(processorConfig);
+
+ let initializedProcessor;
+ if (processorCache.has(processorKey)) {
+ initializedProcessor = processorCache.get(processorKey);
+ } else {
+ processorConfig = [].concat(processorConfig);
+ const processorLookup = processorConfig[0];
+ const processorOptions = processorConfig[1];
+ let processor = dynamicRequire(processorLookup);
+ processor = processor.default || processor;
+ initializedProcessor = processor(processorOptions);
+ processorCache.set(processorKey, initializedProcessor);
+ }
+
+ if (initializedProcessor && initializedProcessor.code) {
+ codeProcessors.push(initializedProcessor.code);
+ }
+ if (initializedProcessor && initializedProcessor.result) {
+ resultProcessors.push(initializedProcessor.result);
+ }
+ });
+
+ config.codeProcessors = codeProcessors;
+ config.resultProcessors = resultProcessors;
+ return config;
+}
+
+module.exports = { augmentConfigExtended, augmentConfigFull };