stylelint-scss
Version:
A collection of SCSS-specific rules for Stylelint
134 lines (113 loc) • 3.41 kB
JavaScript
;
const valueParser = require("postcss-value-parser");
const { rules, utils } = require("stylelint");
const { ALL_FUNCTIONS } = require("../../utils/functions");
const namespace = require("../../utils/namespace");
const { isRegExp, isString } = require("../../utils/validateTypes");
const ruleUrl = require("../../utils/ruleUrl");
const ruleToCheckAgainst = "function-no-unknown";
const ruleName = namespace(ruleToCheckAgainst);
const messages = utils.ruleMessages(ruleName, {
rejected: (...args) => {
return rules[ruleToCheckAgainst].messages
.rejected(...args)
.replace(` (${ruleToCheckAgainst})`, "");
}
});
const meta = {
url: ruleUrl(ruleName)
};
function isNamespacedFunction(fn) {
const namespacedFunc = /^\w+\.\w+$/;
return namespacedFunc.test(fn);
}
function isAtUseAsSyntax(nodes) {
const [first, second, third] = nodes.slice(-3);
return (
first.type === "word" &&
first.value === "as" &&
second.type === "space" &&
third.type === "word"
);
}
function getAtUseNamespace(nodes) {
if (isAtUseAsSyntax(nodes)) {
const [last] = nodes.slice(-1);
return last.value;
}
const [first] = nodes;
const parts = first.value.split("/");
const [last] = parts.slice(-1);
return last;
}
function rule(primaryOption, secondaryOptions) {
return (root, result) => {
const validOptions = utils.validateOptions(
result,
ruleName,
{
actual: primaryOption
},
{
actual: secondaryOptions,
possible: {
ignoreFunctions: [isString, isRegExp]
},
optional: true
}
);
if (!validOptions) {
return;
}
const optionsFunctions =
(secondaryOptions && secondaryOptions.ignoreFunctions) || [];
const ignoreFunctions = ALL_FUNCTIONS.concat(optionsFunctions);
const ignoreFunctionsAsSet = new Set(ignoreFunctions);
const newSecondaryOptions = Object.assign({}, secondaryOptions, {
ignoreFunctions
});
utils.checkAgainstRule(
{
ruleName: ruleToCheckAgainst,
ruleSettings: [primaryOption, newSecondaryOptions],
root
},
warning => {
const { node, index } = warning;
// NOTE: Using `valueParser` is necessary for extracting a function name. This may be a performance waste.
valueParser(node.value).walk(valueNode => {
const { type, value: funcName } = valueNode;
if (type !== "function" || funcName.trim() === "") {
return;
}
if (isNamespacedFunction(funcName)) {
const atUseNamespaces = [];
root.walkAtRules(/^use$/i, atRule => {
const { nodes } = valueParser(atRule.params);
atUseNamespaces.push(getAtUseNamespace(nodes));
});
if (atUseNamespaces.length) {
const [namespace] = funcName.split(".");
if (atUseNamespaces.includes(namespace)) {
return;
}
}
}
if (!ignoreFunctionsAsSet.has(funcName)) {
utils.report({
message: messages.rejected(funcName),
ruleName,
result,
node,
index
});
}
});
}
);
};
}
rule.ruleName = ruleName;
rule.messages = messages;
rule.meta = meta;
module.exports = rule;