index.mjs 2.4 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495
  1. import { isBoolean, isRegExp, isString } from '../../utils/validateTypes.mjs';
  2. import getRuleSelector from '../../utils/getRuleSelector.mjs';
  3. import getStrippedSelectorSource from '../../utils/getStrippedSelectorSource.mjs';
  4. import isStandardSyntaxRule from '../../utils/isStandardSyntaxRule.mjs';
  5. import parseSelector from '../../utils/parseSelector.mjs';
  6. import report from '../../utils/report.mjs';
  7. import ruleMessages from '../../utils/ruleMessages.mjs';
  8. import validateOptions from '../../utils/validateOptions.mjs';
  9. /** @import { Rule } from 'postcss' */
  10. const ruleName = 'selector-nested-pattern';
  11. const messages = ruleMessages(ruleName, {
  12. expected: (selector, pattern) => `Expected "${selector}" to match pattern "${pattern}"`,
  13. });
  14. const meta = {
  15. url: 'https://stylelint.io/user-guide/rules/selector-nested-pattern',
  16. };
  17. /** @type {import('stylelint').CoreRules[ruleName]} */
  18. const rule = (primary, secondaryOptions) => {
  19. return (root, result) => {
  20. const validOptions = validateOptions(
  21. result,
  22. ruleName,
  23. {
  24. actual: primary,
  25. possible: [isRegExp, isString],
  26. },
  27. {
  28. actual: secondaryOptions,
  29. possible: {
  30. splitList: [isBoolean],
  31. },
  32. optional: true,
  33. },
  34. );
  35. if (!validOptions) {
  36. return;
  37. }
  38. const normalizedPattern = isString(primary) ? new RegExp(primary) : primary;
  39. const splitList = secondaryOptions && secondaryOptions.splitList;
  40. root.walkRules((ruleNode) => {
  41. if (ruleNode.parent && ruleNode.parent.type !== 'rule') {
  42. return;
  43. }
  44. if (!isStandardSyntaxRule(ruleNode)) {
  45. return;
  46. }
  47. const selectors = getRuleSelector(ruleNode);
  48. if (splitList) {
  49. parseSelector(selectors, result, ruleNode)?.each((selector) => {
  50. const { index, endIndex, selector: selectorStr } = getStrippedSelectorSource(selector);
  51. check(ruleNode, selectorStr, index, endIndex);
  52. });
  53. } else {
  54. check(ruleNode, selectors, 0, selectors.length);
  55. }
  56. });
  57. /**
  58. * @param {Rule} ruleNode
  59. * @param {string} selectorStr
  60. * @param {number} index
  61. * @param {number} endIndex
  62. */
  63. function check(ruleNode, selectorStr, index, endIndex) {
  64. if (normalizedPattern.test(selectorStr)) return;
  65. report({
  66. result,
  67. ruleName,
  68. message: messages.expected,
  69. messageArgs: [selectorStr, primary],
  70. node: ruleNode,
  71. index,
  72. endIndex,
  73. });
  74. }
  75. };
  76. };
  77. rule.ruleName = ruleName;
  78. rule.messages = messages;
  79. rule.meta = meta;
  80. export default rule;