|
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359 |
- 'use strict';
-
- const _ = require('lodash');
- const configurationError = require('./utils/configurationError');
- const getModulePath = require('./utils/getModulePath');
- const globjoin = require('globjoin');
- const normalizeAllRuleSettings = require('./normalizeAllRuleSettings');
- const path = require('path');
-
- /** @typedef {import('stylelint').StylelintConfigPlugins} StylelintConfigPlugins */
- /** @typedef {import('stylelint').StylelintConfigProcessor} StylelintConfigProcessor */
- /** @typedef {import('stylelint').StylelintConfigProcessors} StylelintConfigProcessors */
- /** @typedef {import('stylelint').StylelintConfigRules} StylelintConfigRules */
- /** @typedef {import('stylelint').StylelintInternalApi} StylelintInternalApi */
- /** @typedef {import('stylelint').StylelintConfig} StylelintConfig */
- /** @typedef {import('stylelint').CosmiconfigResult} CosmiconfigResult */
-
- /**
- * - Merges config and configOverrides
- * - Makes all paths absolute
- * - Merges extends
- * @param {StylelintInternalApi} stylelint
- * @param {StylelintConfig} config
- * @param {string} configDir
- * @param {boolean} [allowOverrides]
- * @returns {Promise<StylelintConfig>}
- */
- function augmentConfigBasic(stylelint, config, configDir, allowOverrides) {
- 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.
- * @param {StylelintInternalApi} stylelint
- * @param {CosmiconfigResult} [cosmiconfigResult]
- * @returns {Promise<CosmiconfigResult | null>}
- */
- function augmentConfigExtended(stylelint, cosmiconfigResult) {
- if (!cosmiconfigResult) return Promise.resolve(null);
-
- const configDir = path.dirname(cosmiconfigResult.filepath || '');
- const { ignoreFiles, ...cleanedConfig } = cosmiconfigResult.config;
-
- return augmentConfigBasic(stylelint, cleanedConfig, configDir).then((augmentedConfig) => {
- return {
- config: augmentedConfig,
- filepath: cosmiconfigResult.filepath,
- };
- });
- }
-
- /**
- * @param {StylelintInternalApi} stylelint
- * @param {CosmiconfigResult} [cosmiconfigResult]
- * @returns {Promise<CosmiconfigResult | null>}
- */
- function augmentConfigFull(stylelint, cosmiconfigResult) {
- if (!cosmiconfigResult) return Promise.resolve(null);
-
- const config = cosmiconfigResult.config;
- const 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)
- * @param {StylelintConfig} config
- * @param {string} configDir
- * @returns {StylelintConfig}
- */
- function absolutizePaths(config, configDir) {
- if (config.ignoreFiles) {
- config.ignoreFiles = /** @type {string[]} */ ([]).concat(config.ignoreFiles).map((glob) => {
- if (path.isAbsolute(glob.replace(/^!/, ''))) return glob;
-
- return globjoin(configDir, glob);
- });
- }
-
- if (config.plugins) {
- config.plugins = /** @type {string[]} */ ([]).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
- * @param {StylelintConfigProcessors} processors
- * @param {string} configDir
- * @return {StylelintConfigProcessors}
- */
- function absolutizeProcessors(processors, configDir) {
- 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]];
- });
- }
-
- /**
- * @param {StylelintInternalApi} stylelint
- * @param {StylelintConfig} config
- * @param {string} configDir
- * @return {Promise<StylelintConfig>}
- */
- function extendConfig(stylelint, config, configDir) {
- if (config.extends === undefined) return Promise.resolve(config);
-
- const normalizedExtends = Array.isArray(config.extends) ? config.extends : [config.extends];
- const { extends: configExtends, ...originalWithoutExtends } = config;
-
- 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);
- });
- }
-
- /**
- * @param {StylelintInternalApi} stylelint
- * @param {StylelintConfig} config
- * @param {string} configDir
- * @param {string} extendLookup
- * @return {Promise<CosmiconfigResult | null>}
- */
- function loadExtendedConfig(stylelint, config, configDir, extendLookup) {
- const extendPath = getModulePath(configDir, extendLookup);
-
- return stylelint._extendExplorer.load(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
- * @param {StylelintConfig} a
- * @param {StylelintConfig} b
- * @returns {StylelintConfig}
- */
- function mergeConfigs(a, b) {
- /** @type {{plugins: StylelintConfigPlugins}} */
- const pluginMerger = {};
-
- if (a.plugins || b.plugins) {
- pluginMerger.plugins = [];
-
- if (a.plugins) {
- pluginMerger.plugins = pluginMerger.plugins.concat(a.plugins);
- }
-
- if (b.plugins) {
- pluginMerger.plugins = [...new Set(pluginMerger.plugins.concat(b.plugins))];
- }
- }
-
- /** @type {{processors: StylelintConfigProcessors}} */
- const processorMerger = {};
-
- if (a.processors || b.processors) {
- processorMerger.processors = [];
-
- if (a.processors) {
- processorMerger.processors = processorMerger.processors.concat(a.processors);
- }
-
- if (b.processors) {
- processorMerger.processors = [...new Set(processorMerger.processors.concat(b.processors))];
- }
- }
-
- const rulesMerger = {};
-
- if (a.rules || b.rules) {
- rulesMerger.rules = { ...a.rules, ...b.rules };
- }
-
- const result = { ...a, ...b, ...processorMerger, ...pluginMerger, ...rulesMerger };
-
- return result;
- }
-
- /**
- * @param {StylelintConfig} config
- * @returns {StylelintConfig}
- */
- function addPluginFunctions(config) {
- if (!config.plugins) return config;
-
- const normalizedPlugins = Array.isArray(config.plugins) ? config.plugins : [config.plugins];
-
- const pluginFunctions = normalizedPlugins.reduce((result, pluginLookup) => {
- let pluginImport = require(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 (!pluginRuleDefinition.ruleName.includes('/')) {
- throw configurationError(
- 'stylelint v7+ requires plugin rules to be namespaced, ' +
- '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;
- }, /** @type {{[k: string]: Function}} */ ({}));
-
- config.pluginFunctions = pluginFunctions;
-
- 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
- * - Initialize the processor module by calling its functions with any
- * provided options
- * - Push the processor's code and result processors to their respective arrays
- * @type {Map<string, string | Object>}
- */
- const processorCache = new Map();
-
- /**
- * @param {StylelintConfig} config
- * @return {StylelintConfig}
- */
- function addProcessorFunctions(config) {
- if (!config.processors) return config;
-
- /** @type {Array<Function>} */
- const codeProcessors = [];
- /** @type {Array<Function>} */
- const resultProcessors = [];
-
- /** @type {Array<StylelintConfigProcessor>} */ ([])
- .concat(config.processors)
- .forEach((processorConfig) => {
- const processorKey = JSON.stringify(processorConfig);
-
- let initializedProcessor;
-
- if (processorCache.has(processorKey)) {
- initializedProcessor = processorCache.get(processorKey);
- } else {
- const processorLookup =
- typeof processorConfig === 'string' ? processorConfig : processorConfig[0];
- const processorOptions =
- typeof processorConfig === 'string' ? undefined : processorConfig[1];
- let processor = require(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 };
|