1
0
mirror of https://github.com/thangisme/notes.git synced 2024-11-01 04:17:33 -04:00
notes/node_modules/stylelint/lib/rules/no-descending-specificity/index.js
Patrick Marsceill b7b0d0d7bf
Initial commit
2017-03-09 13:16:08 -05:00

99 lines
3.3 KiB
JavaScript

"use strict"
const specificity = require("specificity")
const findAtRuleContext = require("../../utils/findAtRuleContext")
const isCustomPropertySet = require("../../utils/isCustomPropertySet")
const nodeContextLookup = require("../../utils/nodeContextLookup")
const parseSelector = require("../../utils/parseSelector")
const report = require("../../utils/report")
const ruleMessages = require("../../utils/ruleMessages")
const validateOptions = require("../../utils/validateOptions")
const _ = require("lodash")
const keywordSets = require("../../reference/keywordSets")
const resolvedNestedSelector = require("postcss-resolve-nested-selector")
const ruleName = "no-descending-specificity"
const messages = ruleMessages(ruleName, {
rejected: (b, a) => `Expected selector "${b}" to come before selector "${a}"`,
})
const rule = function (actual) {
return (root, result) => {
const validOptions = validateOptions(result, ruleName, { actual })
if (!validOptions) {
return
}
const selectorContextLookup = nodeContextLookup()
root.walkRules(rule => {
// Ignore custom property set `--foo: {};`
if (isCustomPropertySet(rule)) {
return
}
const comparisonContext = selectorContextLookup.getContext(rule, findAtRuleContext(rule))
rule.selectors.forEach(selector => {
const trimSelector = selector.trim()
// Ignore `.selector, { }`
if (trimSelector === "") {
return
}
// The edge-case of duplicate selectors will act acceptably
const index = rule.selector.indexOf(trimSelector)
// Resolve any nested selectors before checking
resolvedNestedSelector(selector, rule).forEach(resolvedSelector => {
parseSelector(resolvedSelector, result, rule, s => checkSelector(s, rule, index, comparisonContext))
})
})
})
function checkSelector(selectorNode, rule, sourceIndex, comparisonContext) {
const selector = selectorNode.toString()
const referenceSelectorNode = lastCompoundSelectorWithoutPseudoClasses(selectorNode)
const selectorSpecificity = specificity.calculate(selector)[0].specificityArray
const entry = { selector, specificity: selectorSpecificity }
if (!comparisonContext.has(referenceSelectorNode)) {
comparisonContext.set(referenceSelectorNode, [entry])
return
}
const priorComparableSelectors = comparisonContext.get(referenceSelectorNode)
priorComparableSelectors.forEach(priorEntry => {
if (specificity.compare(selectorSpecificity, priorEntry.specificity) === -1) {
report({
ruleName,
result,
node: rule,
message: messages.rejected(selector, priorEntry.selector),
index: sourceIndex,
})
}
})
priorComparableSelectors.push(entry)
}
}
}
function lastCompoundSelectorWithoutPseudoClasses(selectorNode) {
const nodesAfterLastCombinator = _.last(selectorNode.nodes[0].split(node => {
return node.type === "combinator"
}))
const nodesWithoutPseudoClasses = nodesAfterLastCombinator.filter(node => {
return node.type !== "pseudo" || keywordSets.pseudoElements.has(node.value.replace(/:/g, ""))
}).join("")
return nodesWithoutPseudoClasses.toString()
}
rule.ruleName = ruleName
rule.messages = messages
module.exports = rule