| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140 |
- // @ts-nocheck
- 'use strict';
- const _ = require('lodash');
- const findAtRuleContext = require('../../utils/findAtRuleContext');
- const isCustomPropertySet = require('../../utils/isCustomPropertySet');
- const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
- const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector');
- const keywordSets = require('../../reference/keywordSets');
- const nodeContextLookup = require('../../utils/nodeContextLookup');
- const optionsMatches = require('../../utils/optionsMatches');
- 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}"`,
- });
- function rule(on, options) {
- return (root, result) => {
- const validOptions = validateOptions(
- result,
- ruleName,
- {
- actual: on,
- },
- {
- optional: true,
- actual: options,
- possible: {
- ignore: ['selectors-within-list'],
- },
- },
- );
- if (!validOptions) {
- return;
- }
- const selectorContextLookup = nodeContextLookup();
- root.walkRules((rule) => {
- // Ignore custom property set `--foo: {};`
- if (isCustomPropertySet(rule)) {
- return;
- }
- // Ignore nested property `foo: {};`
- if (!isStandardSyntaxRule(rule)) {
- return;
- }
- // Ignores selectors within list of selectors
- if (optionsMatches(options, 'ignore', 'selectors-within-list') && rule.selectors.length > 1) {
- 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) => {
- if (!isStandardSyntaxSelector(resolvedSelector)) {
- return;
- }
- 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;
|