index.js 2.6 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  1. 'use strict';
  2. const isNonNegativeInteger = require('../../utils/isNonNegativeInteger');
  3. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
  4. const parseSelector = require('../../utils/parseSelector');
  5. const report = require('../../utils/report');
  6. const resolvedNestedSelector = require('postcss-resolve-nested-selector');
  7. const ruleMessages = require('../../utils/ruleMessages');
  8. const selectorParser = require('postcss-selector-parser');
  9. const validateOptions = require('../../utils/validateOptions');
  10. const ruleName = 'selector-max-universal';
  11. const messages = ruleMessages(ruleName, {
  12. expected: (selector, max) =>
  13. `Expected "${selector}" to have no more than ${max} universal ${
  14. max === 1 ? 'selector' : 'selectors'
  15. }`,
  16. });
  17. const meta = {
  18. url: 'https://stylelint.io/user-guide/rules/list/selector-max-universal',
  19. };
  20. /** @type {import('stylelint').Rule} */
  21. const rule = (primary) => {
  22. return (root, result) => {
  23. const validOptions = validateOptions(result, ruleName, {
  24. actual: primary,
  25. possible: isNonNegativeInteger,
  26. });
  27. if (!validOptions) {
  28. return;
  29. }
  30. /**
  31. * @param {import('postcss-selector-parser').Container<unknown>} selectorNode
  32. * @param {import('postcss').Rule} ruleNode
  33. */
  34. function checkSelector(selectorNode, ruleNode) {
  35. const count = selectorNode.reduce((total, childNode) => {
  36. // Only traverse inside actual selectors
  37. // All logical combinations will be resolved as nested selector in `postcss-resolve-nested-selector`
  38. if (childNode.type === 'selector') {
  39. checkSelector(childNode, ruleNode);
  40. }
  41. if (childNode.type === 'universal') total += 1;
  42. return total;
  43. }, 0);
  44. if (selectorNode.type !== 'root' && selectorNode.type !== 'pseudo' && count > primary) {
  45. const selector = selectorNode.toString();
  46. report({
  47. ruleName,
  48. result,
  49. node: ruleNode,
  50. message: messages.expected(selector, primary),
  51. word: selector,
  52. });
  53. }
  54. }
  55. root.walkRules((ruleNode) => {
  56. if (!isStandardSyntaxRule(ruleNode)) {
  57. return;
  58. }
  59. /** @type {string[]} */
  60. const selectors = [];
  61. selectorParser()
  62. .astSync(ruleNode.selector)
  63. .walk((node) => {
  64. if (node.type === 'selector') {
  65. selectors.push(String(node).trim());
  66. }
  67. });
  68. for (const selector of selectors) {
  69. for (const resolvedSelector of resolvedNestedSelector(selector, ruleNode)) {
  70. parseSelector(resolvedSelector, result, ruleNode, (container) =>
  71. checkSelector(container, ruleNode),
  72. );
  73. }
  74. }
  75. });
  76. };
  77. };
  78. rule.ruleName = ruleName;
  79. rule.messages = messages;
  80. rule.meta = meta;
  81. module.exports = rule;