1
0
mirror of https://github.com/thangisme/notes.git synced 2024-06-20 12:35:45 +00:00
notes/node_modules/stylelint/lib/augmentConfig.js
Patrick Marsceill b7b0d0d7bf
Initial commit
2017-03-09 13:16:08 -05:00

333 lines
12 KiB
JavaScript

/* @flow */
"use strict"
const configurationError = require("./utils/configurationError")
const getModulePath = require("./utils/getModulePath")
const _ = require("lodash")
const fs = require("fs")
const globjoin = require("globjoin")
const normalizeRuleSettings = require("./normalizeRuleSettings")
const path = require("path")
const rules = require("./rules")
const DEFAULT_IGNORE_FILENAME = ".stylelintignore"
const FILE_NOT_FOUND_ERROR_CODE = "ENOENT"
// - 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 addIgnorePatterns(stylelint, augmentedConfig)
}).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,
}
})
}
// Load a file ignore ignore patterns, if there is one;
// then add them to the config as an ignorePatterns property
function addIgnorePatterns(
stylelint/*: stylelint$internalApi*/,
config/*: stylelint$config*/
)/*: Promise<stylelint$config>*/ {
const ignoreFilePath = stylelint._options.ignorePath || DEFAULT_IGNORE_FILENAME
const absoluteIgnoreFilePath = path.isAbsolute(ignoreFilePath) ? ignoreFilePath : path.resolve(process.cwd(), ignoreFilePath)
return new Promise((resolve, reject) => {
fs.readFile(absoluteIgnoreFilePath, "utf8", (err, data) => {
if (err) {
// If the file's not found, fine, we'll just
// consider it an empty array of globs
if (err.code === FILE_NOT_FOUND_ERROR_CODE) {
return resolve(config)
}
return reject(err)
}
// Add an ignorePatterns property to the config, containing the
// .gitignore-patterned globs loaded from .stylelintignore
const augmentedConfig/*: stylelint$config*/ = Object.assign({}, config, {
ignorePatterns: data,
})
resolve(augmentedConfig)
})
})
}
// 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 = 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 (!_.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 = 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 }