123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146 |
- // @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((ruleNode) => {
- // Ignore custom property set `--foo: {};`
- if (isCustomPropertySet(ruleNode)) {
- return;
- }
-
- // Ignore nested property `foo: {};`
- if (!isStandardSyntaxRule(ruleNode)) {
- return;
- }
-
- // Ignores selectors within list of selectors
- if (
- optionsMatches(options, 'ignore', 'selectors-within-list') &&
- ruleNode.selectors.length > 1
- ) {
- return;
- }
-
- const comparisonContext = selectorContextLookup.getContext(
- ruleNode,
- findAtRuleContext(ruleNode),
- );
-
- ruleNode.selectors.forEach((selector) => {
- const trimSelector = selector.trim();
-
- // Ignore `.selector, { }`
- if (trimSelector === '') {
- return;
- }
-
- // The edge-case of duplicate selectors will act acceptably
- const index = ruleNode.selector.indexOf(trimSelector);
-
- // Resolve any nested selectors before checking
- resolvedNestedSelector(selector, ruleNode).forEach((resolvedSelector) => {
- parseSelector(resolvedSelector, result, ruleNode, (s) => {
- if (!isStandardSyntaxSelector(resolvedSelector)) {
- return;
- }
-
- checkSelector(s, ruleNode, index, comparisonContext);
- });
- });
- });
- });
-
- function checkSelector(selectorNode, ruleNode, 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: ruleNode,
- 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;
|