stylelint
Version:
A mighty, modern CSS linter.
309 lines (250 loc) • 11 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", {
value: true
});
exports.augmentConfigFull = exports.augmentConfigExtended = undefined;
var _lodash = require("lodash");
var _lodash2 = _interopRequireDefault(_lodash);
var _utils = require("./utils");
var _fs = require("fs");
var _fs2 = _interopRequireDefault(_fs);
var _globjoin = require("globjoin");
var _globjoin2 = _interopRequireDefault(_globjoin);
var _normalizeRuleSettings = require("./normalizeRuleSettings");
var _normalizeRuleSettings2 = _interopRequireDefault(_normalizeRuleSettings);
var _path = require("path");
var _path2 = _interopRequireDefault(_path);
var _resolveFrom = require("resolve-from");
var _resolveFrom2 = _interopRequireDefault(_resolveFrom);
var _rules = require("./rules");
var _rules2 = _interopRequireDefault(_rules);
function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
var DEFAULT_IGNORE_FILENAME = ".stylelintignore";
var FILE_NOT_FOUND_ERROR_CODE = "ENOENT";
// - Makes all paths absolute
// - Merges extends
function augmentConfigBasic(stylelint, config, configDir) {
return Promise.resolve().then(function () {
return absolutizePaths(config, configDir);
}).then(function (augmentedConfig) {
return extendConfig(stylelint, 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, cosmiconfigResult) {
if (!cosmiconfigResult) return Promise.resolve(null);
var configDir = _path2.default.dirname(cosmiconfigResult.filepath || "");
var cleanedConfig = _lodash2.default.omit(cosmiconfigResult.config, "ignoreFiles");
return augmentConfigBasic(stylelint, cleanedConfig, configDir).then(function (augmentedConfig) {
return {
config: augmentedConfig,
filepath: cosmiconfigResult.filepath
};
});
}
function augmentConfigFull(stylelint, cosmiconfigResult) {
if (!cosmiconfigResult) return Promise.resolve(null);
var config = cosmiconfigResult.config;
var filepath = cosmiconfigResult.filepath;
var configDir = stylelint._options.configBasedir || _path2.default.dirname(filepath || "");
return addIgnorePatterns(stylelint, config, configDir).then(function (augmentedConfig) {
return augmentConfigBasic(stylelint, augmentedConfig, configDir);
}).then(function (augmentedConfig) {
return addPluginFunctions(augmentedConfig);
}).then(function (augmentedConfig) {
return addProcessorFunctions(augmentedConfig);
}).then(function (augmentedConfig) {
var configWithOverrides = _lodash2.default.merge(augmentedConfig, stylelint._options.configOverrides);
if (!configWithOverrides.rules) {
throw (0, _utils.configurationError)("No rules found within configuration. Have you provided a \"rules\" property?");
}
return configWithOverrides;
}).then(function (augmentedConfig) {
return normalizeAllRuleSettings(augmentedConfig);
}).then(function (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, config) {
var ignoreFilePath = stylelint._options.ignorePath || DEFAULT_IGNORE_FILENAME;
var absoluteIgnoreFilePath = _path2.default.isAbsolute(ignoreFilePath) ? ignoreFilePath : _path2.default.resolve(process.cwd(), ignoreFilePath);
return new Promise(function (resolve, reject) {
_fs2.default.readFile(absoluteIgnoreFilePath, "utf8", function (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
config.ignorePatterns = data;
resolve(config);
});
});
}
// Make all paths in the config absolute:
// - ignoreFiles
// - plugins
// - processors
// (extends handled elsewhere)
function absolutizePaths(config, configDir) {
if (config.ignoreFiles) {
config.ignoreFiles = [].concat(config.ignoreFiles).map(function (glob) {
if (_path2.default.isAbsolute(glob.replace(/^!/, ""))) return glob;
return (0, _globjoin2.default)(configDir, glob);
});
}
if (config.plugins) {
config.plugins = [].concat(config.plugins).map(function (lookup) {
return getModulePath(configDir, lookup);
});
}
if (config.processors) {
config.processors = absolutizeProcessors(config.processors, configDir);
}
return config;
}
// First try to resolve from the provided directory,
// then try to resolve from process.cwd.
function getModulePath(basedir, lookup) {
var path = (0, _resolveFrom2.default)(basedir, lookup);
if (!path) {
path = (0, _resolveFrom2.default)(process.cwd(), lookup);
}
if (!path) {
throw (0, _utils.configurationError)("Could not find \"" + lookup + "\". Do you need a `configBasedir`?");
}
return path;
}
// Processors are absolutized in their own way because
// they can be and return a string or an array
function absolutizeProcessors(processors, configDir) {
return [].concat(processors).map(function (item) {
if (typeof item === "string") {
return getModulePath(configDir, item);
}
return [getModulePath(configDir, item[0]), item[1]];
});
}
function extendConfig(stylelint, config, configDir) {
if (!config.extends) return Promise.resolve(config);
var originalWithoutExtends = _lodash2.default.omit(config, "extends");
var loadExtends = [].concat(config.extends).reduce(function (resultPromise, extendLookup) {
return resultPromise.then(function (resultConfig) {
return loadExtendedConfig(stylelint, resultConfig, configDir, extendLookup).then(function (extendResult) {
return mergeConfigs(resultConfig, extendResult.config);
});
});
}, Promise.resolve(originalWithoutExtends));
return loadExtends.then(function (resultConfig) {
return mergeConfigs(resultConfig, originalWithoutExtends);
});
}
function loadExtendedConfig(stylelint, config, configDir, extendLookup) {
var 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, b) {
var pluginMerger = {};
if (a.plugins || b.plugins) {
pluginMerger.plugins = _lodash2.default.union(a.plugins, b.plugins);
}
var processorMerger = {};
if (a.processors || b.processors) {
processorMerger.processors = _lodash2.default.union(a.processors, b.processors);
}
var rulesMerger = {};
if (a.rules || b.rules) {
rulesMerger.rules = Object.assign({}, a.rules, b.rules);
}
return Object.assign({}, b, a, pluginMerger, rulesMerger);
}
function addPluginFunctions(config) {
if (!config.plugins) return config;
var pluginFunctions = config.plugins.reduce(function (result, pluginLookup) {
var pluginImport = require(pluginLookup);
// Handle either ES6 or CommonJS modules
pluginImport = pluginImport.default || pluginImport;[].concat(pluginImport).forEach(function (plugin) {
if (!plugin.ruleName) {
throw (0, _utils.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 (!_lodash2.default.includes(plugin.ruleName, "/")) {
throw (0, _utils.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 \"" + plugin.ruleName + "\" does not do this, so will not work. ") + "Please file an issue with the plugin.");
}
result[plugin.ruleName] = plugin.rule;
});
return result;
}, {});
config.pluginFunctions = pluginFunctions;
return config;
}
function normalizeAllRuleSettings(config) {
var normalizedRules = {};
Object.keys(config.rules).forEach(function (ruleName) {
var rawRuleSettings = config.rules[ruleName];
var rule = _rules2.default[ruleName] || _lodash2.default.get(config, ["pluginFunctions", ruleName]);
if (!rule) {
throw (0, _utils.configurationError)("Undefined rule " + ruleName);
}
normalizedRules[ruleName] = (0, _normalizeRuleSettings2.default)(rawRuleSettings, ruleName, _lodash2.default.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
var processorCache = new Map();
function addProcessorFunctions(config) {
if (!config.processors) return config;
var codeProcessors = [];
var resultProcessors = [];[].concat(config.processors).forEach(function (processorConfig) {
var processorKey = JSON.stringify(processorConfig);
var initializedProcessor = void 0;
if (processorCache.has(processorKey)) {
initializedProcessor = processorCache.get(processorKey);
} else {
processorConfig = [].concat(processorConfig);
var processorLookup = processorConfig[0];
var processorOptions = processorConfig[1];
var 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;
}
exports.augmentConfigExtended = augmentConfigExtended;
exports.augmentConfigFull = augmentConfigFull;