index.js 2.3 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788
  1. // @ts-nocheck
  2. 'use strict';
  3. const isContextFunctionalPseudoClass = require('../../utils/isContextFunctionalPseudoClass');
  4. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
  5. const keywordSets = require('../../reference/keywordSets');
  6. const parseSelector = require('../../utils/parseSelector');
  7. const report = require('../../utils/report');
  8. const resolvedNestedSelector = require('postcss-resolve-nested-selector');
  9. const ruleMessages = require('../../utils/ruleMessages');
  10. const validateOptions = require('../../utils/validateOptions');
  11. const ruleName = 'selector-max-pseudo-class';
  12. const messages = ruleMessages(ruleName, {
  13. expected: (selector, max) =>
  14. `Expected "${selector}" to have no more than ${max} pseudo-${max === 1 ? 'class' : 'classes'}`,
  15. });
  16. function rule(max) {
  17. return (root, result) => {
  18. const validOptions = validateOptions(result, ruleName, {
  19. actual: max,
  20. possible: [
  21. function (max) {
  22. return typeof max === 'number' && max >= 0;
  23. },
  24. ],
  25. });
  26. if (!validOptions) {
  27. return;
  28. }
  29. function checkSelector(selectorNode, ruleNode) {
  30. const count = selectorNode.reduce((total, childNode) => {
  31. // Only traverse inside actual selectors and context functional pseudo-classes
  32. if (childNode.type === 'selector' || isContextFunctionalPseudoClass(childNode)) {
  33. checkSelector(childNode, ruleNode);
  34. }
  35. // Exclude pseudo elements from the count
  36. if (
  37. childNode.type === 'pseudo' &&
  38. (childNode.value.includes('::') ||
  39. keywordSets.levelOneAndTwoPseudoElements.has(childNode.value.toLowerCase().slice(1)))
  40. ) {
  41. return total;
  42. }
  43. if (childNode.type === 'pseudo') {
  44. return (total += 1);
  45. }
  46. return total;
  47. }, 0);
  48. if (count > max) {
  49. report({
  50. ruleName,
  51. result,
  52. node: ruleNode,
  53. message: messages.expected(selectorNode, max),
  54. word: selectorNode,
  55. });
  56. }
  57. }
  58. root.walkRules((ruleNode) => {
  59. if (!isStandardSyntaxRule(ruleNode)) {
  60. return;
  61. }
  62. ruleNode.selectors.forEach((selector) => {
  63. resolvedNestedSelector(selector, ruleNode).forEach((resolvedSelector) => {
  64. parseSelector(resolvedSelector, result, rule, (selectorTree) => {
  65. checkSelector(selectorTree, ruleNode);
  66. });
  67. });
  68. });
  69. });
  70. };
  71. }
  72. rule.ruleName = ruleName;
  73. rule.messages = messages;
  74. module.exports = rule;