index.js 2.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110
  1. 'use strict';
  2. const { parse } = require('css-tree');
  3. const {
  4. aNPlusBNotationPseudoClasses,
  5. aNPlusBOfSNotationPseudoClasses,
  6. } = require('../../reference/selectors');
  7. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
  8. const report = require('../../utils/report');
  9. const ruleMessages = require('../../utils/ruleMessages');
  10. const validateOptions = require('../../utils/validateOptions');
  11. const ruleName = 'selector-anb-no-unmatchable';
  12. const messages = ruleMessages(ruleName, {
  13. rejected: (pseudoClass) => `Unexpected unmatchable An+B selector "${pseudoClass}"`,
  14. });
  15. const meta = {
  16. url: 'https://stylelint.io/user-guide/rules/selector-anb-no-unmatchable',
  17. };
  18. function isUnmatchableNth(/** @type {import('css-tree').AnPlusB} */ nth) {
  19. const { a, b } = nth;
  20. if (a !== null && a !== '0' && a !== '-0') {
  21. return false;
  22. }
  23. if (b !== null && b !== '0' && b !== '-0') {
  24. return false;
  25. }
  26. return true;
  27. }
  28. /** @type {import('stylelint').Rule} */
  29. const rule = (primary) => {
  30. return (root, result) => {
  31. const validOptions = validateOptions(result, ruleName, { actual: primary });
  32. if (!validOptions) {
  33. return;
  34. }
  35. root.walkRules((ruleNode) => {
  36. if (!isStandardSyntaxRule(ruleNode)) {
  37. return;
  38. }
  39. ruleNode.selectors.forEach((selector) => {
  40. let cssTreeSelector;
  41. try {
  42. cssTreeSelector = parse(selector, { context: 'selector', positions: true });
  43. } catch (e) {
  44. return;
  45. }
  46. checkSelector(cssTreeSelector);
  47. });
  48. function checkSelector(/** @type {import('css-tree').CssNode} */ selector) {
  49. if (selector.type !== 'Selector') {
  50. return;
  51. }
  52. selector.children.forEach((selectorChild) => {
  53. if (
  54. selectorChild.type !== 'PseudoClassSelector' ||
  55. (!aNPlusBNotationPseudoClasses.has(selectorChild.name) &&
  56. !aNPlusBOfSNotationPseudoClasses.has(selectorChild.name))
  57. ) {
  58. return;
  59. }
  60. const pseudoClassSelector = selectorChild;
  61. if (pseudoClassSelector.children === null) {
  62. return;
  63. }
  64. pseudoClassSelector.children.forEach((child) => {
  65. if (child.type !== 'Nth' || child.nth.type !== 'AnPlusB') {
  66. return;
  67. }
  68. if (isUnmatchableNth(child.nth)) {
  69. report({
  70. message: messages.rejected,
  71. messageArgs: [`:${pseudoClassSelector.name}`],
  72. node: ruleNode,
  73. index: pseudoClassSelector.loc?.start.column,
  74. endIndex: pseudoClassSelector.loc?.end.column,
  75. result,
  76. ruleName,
  77. });
  78. }
  79. });
  80. });
  81. }
  82. });
  83. };
  84. };
  85. rule.ruleName = ruleName;
  86. rule.messages = messages;
  87. rule.meta = meta;
  88. module.exports = rule;