+++ /dev/null
-"use strict";
-
-const _ = require("lodash");
-const findAtRuleContext = require("../../utils/findAtRuleContext");
-const isCustomPropertySet = require("../../utils/isCustomPropertySet");
-const keywordSets = require("../../reference/keywordSets");
-const nodeContextLookup = require("../../utils/nodeContextLookup");
-const parseSelector = require("../../utils/parseSelector");
-const report = require("../../utils/report");
-const resolvedNestedSelector = require("postcss-resolve-nested-selector");
-const ruleMessages = require("../../utils/ruleMessages");
-const specificity = require("specificity");
-const validateOptions = require("../../utils/validateOptions");
-
-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;