mirror of
https://github.com/thangisme/notes.git
synced 2024-12-22 19:26:34 -05:00
333 lines
12 KiB
JavaScript
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 }
|