index.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. 'use strict';
  2. const atRuleParamIndex = require('../../utils/atRuleParamIndex');
  3. const isCustomSelector = require('../../utils/isCustomSelector');
  4. const isStandardSyntaxAtRule = require('../../utils/isStandardSyntaxAtRule');
  5. const isStandardSyntaxRule = require('../../utils/isStandardSyntaxRule');
  6. const isStandardSyntaxSelector = require('../../utils/isStandardSyntaxSelector');
  7. const keywordSets = require('../../reference/keywordSets');
  8. const optionsMatches = require('../../utils/optionsMatches');
  9. const parseSelector = require('../../utils/parseSelector');
  10. const report = require('../../utils/report');
  11. const ruleMessages = require('../../utils/ruleMessages');
  12. const validateOptions = require('../../utils/validateOptions');
  13. const vendor = require('../../utils/vendor');
  14. const { isString } = require('../../utils/validateTypes');
  15. const ruleName = 'selector-pseudo-class-no-unknown';
  16. const messages = ruleMessages(ruleName, {
  17. rejected: (selector) => `Unexpected unknown pseudo-class selector "${selector}"`,
  18. });
  19. const meta = {
  20. url: 'https://stylelint.io/user-guide/rules/list/selector-pseudo-class-no-unknown',
  21. };
  22. /** @type {import('stylelint').Rule} */
  23. const rule = (primary, secondaryOptions) => {
  24. return (root, result) => {
  25. const validOptions = validateOptions(
  26. result,
  27. ruleName,
  28. { actual: primary },
  29. {
  30. actual: secondaryOptions,
  31. possible: {
  32. ignorePseudoClasses: [isString],
  33. },
  34. optional: true,
  35. },
  36. );
  37. if (!validOptions) {
  38. return;
  39. }
  40. /**
  41. * @param {string} selector
  42. * @param {import('postcss').ChildNode} node
  43. */
  44. function check(selector, node) {
  45. parseSelector(selector, result, node, (selectorTree) => {
  46. selectorTree.walkPseudos((pseudoNode) => {
  47. const value = pseudoNode.value;
  48. if (!isStandardSyntaxSelector(value)) {
  49. return;
  50. }
  51. if (isCustomSelector(value)) {
  52. return;
  53. }
  54. // Ignore pseudo-elements
  55. if (value.slice(0, 2) === '::') {
  56. return;
  57. }
  58. if (optionsMatches(secondaryOptions, 'ignorePseudoClasses', pseudoNode.value.slice(1))) {
  59. return;
  60. }
  61. let index = null;
  62. const name = value.slice(1).toLowerCase();
  63. if (node.type === 'atrule' && node.name === 'page') {
  64. if (keywordSets.atRulePagePseudoClasses.has(name)) {
  65. return;
  66. }
  67. index = atRuleParamIndex(node) + pseudoNode.sourceIndex;
  68. } else {
  69. if (
  70. vendor.prefix(name) ||
  71. keywordSets.pseudoClasses.has(name) ||
  72. keywordSets.pseudoElements.has(name)
  73. ) {
  74. return;
  75. }
  76. /** @type {import('postcss-selector-parser').Base} */
  77. let prevPseudoElement = pseudoNode;
  78. do {
  79. prevPseudoElement = /** @type {import('postcss-selector-parser').Base} */ (
  80. prevPseudoElement.prev()
  81. );
  82. if (prevPseudoElement && prevPseudoElement.value.slice(0, 2) === '::') {
  83. break;
  84. }
  85. } while (prevPseudoElement);
  86. if (prevPseudoElement) {
  87. const prevPseudoElementValue = prevPseudoElement.value.toLowerCase().slice(2);
  88. if (
  89. keywordSets.webkitScrollbarPseudoElements.has(prevPseudoElementValue) &&
  90. keywordSets.webkitScrollbarPseudoClasses.has(name)
  91. ) {
  92. return;
  93. }
  94. }
  95. index = pseudoNode.sourceIndex;
  96. }
  97. report({
  98. message: messages.rejected(value),
  99. node,
  100. index,
  101. ruleName,
  102. result,
  103. });
  104. });
  105. });
  106. }
  107. root.walk((node) => {
  108. let selector = null;
  109. if (node.type === 'rule') {
  110. if (!isStandardSyntaxRule(node)) {
  111. return;
  112. }
  113. selector = node.selector;
  114. } else if (node.type === 'atrule' && node.name === 'page' && node.params) {
  115. if (!isStandardSyntaxAtRule(node)) {
  116. return;
  117. }
  118. selector = node.params;
  119. }
  120. // Return if selector empty, it is meaning node type is not a rule or a at-rule
  121. if (!selector) {
  122. return;
  123. }
  124. // Return early before parse if no pseudos for performance
  125. if (!selector.includes(':')) {
  126. return;
  127. }
  128. check(selector, node);
  129. });
  130. };
  131. };
  132. rule.ruleName = ruleName;
  133. rule.messages = messages;
  134. rule.meta = meta;
  135. module.exports = rule;