stylelint-scss
Version:
A collection of SCSS specific rules for stylelint
130 lines (102 loc) • 3.1 kB
JavaScript
import { includes } from "lodash"
import { utils } from "stylelint"
import { namespace } from "../../utils"
import valueParser from "postcss-value-parser"
export const ruleName = namespace("dollar-variable-no-missing-interpolation")
export const messages = utils.ruleMessages(ruleName, {
rejected: (n, v) => `Expected variable ${v} to be interpolated when using it with ${n}`,
})
// https://developer.mozilla.org/en/docs/Web/CSS/custom-ident#Lists_of_excluded_values
const customIdentProps = [
"animation",
"animation-name",
"counter-reset",
"counter-increment",
"list-style-type",
"will-change",
]
// https://developer.mozilla.org/en/docs/Web/CSS/At-rule
const customIdentAtRules = [
"counter-style",
"keyframes",
"supports",
]
function isAtRule(type) {
return type === "atrule"
}
function isCustomIdentAtRule(node) {
return isAtRule(node.type) && includes(customIdentAtRules, node.name)
}
function isCustomIdentProp(node) {
return includes(customIdentProps, node.prop)
}
function isAtSupports(node) {
return isAtRule(node.type) && node.name === "supports"
}
function isSassVar(value) {
return value[0] === "$"
}
function isStringVal(value) {
return (/^("|').*("|')$/).test(value)
}
function toRegex(arr) {
return new RegExp(`(${arr.join("|")})`)
}
export default function (actual) {
return function (root, result) {
const validOptions = utils.validateOptions(result, ruleName, { actual })
if (!validOptions) { return }
const stringVars = []
const vars = []
function findVars(node) {
node.walkDecls(decl => {
const { prop, value } = decl
if (!isSassVar(prop) || includes(vars, prop)) {
return
}
if (isStringVal(value)) {
stringVars.push(prop)
}
vars.push(prop)
})
}
findVars(root)
root.walkRules(findVars)
if (!vars.length) { return }
function shouldReport(node, value) {
if (isAtSupports(node) || isCustomIdentProp(node)) {
return includes(stringVars, value)
}
if (isCustomIdentAtRule(node)) {
return includes(vars, value)
}
return false
}
function report(node, value) {
const { name, prop, type } = node
const nodeName = isAtRule(type) ? "@" + name : prop
utils.report({
ruleName,
result,
node,
message: messages.rejected(nodeName, value),
})
}
function exitEarly(node) {
return node.type !== "word" || !node.value
}
function walkValues(node, value) {
valueParser(value).walk(valNode => {
const { value } = valNode
if (exitEarly(valNode) || !shouldReport(node, value)) { return }
report(node, value)
})
}
root.walkDecls(toRegex(customIdentProps), decl => {
walkValues(decl, decl.value)
})
root.walkAtRules(toRegex(customIdentAtRules), atRule => {
walkValues(atRule, atRule.params)
})
}
}