text: "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;
  