selectorCombinatorSpaceChecker.js 2.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105
  1. 'use strict';
  2. const isStandardSyntaxCombinator = require('../utils/isStandardSyntaxCombinator');
  3. const isStandardSyntaxRule = require('../utils/isStandardSyntaxRule');
  4. const parseSelector = require('../utils/parseSelector');
  5. const report = require('../utils/report');
  6. /**
  7. * @typedef {(args: { source: string, index: number, errTarget: string, err: (message: string) => void }) => void} LocationChecker
  8. *
  9. * @param {{
  10. * root: import('postcss').Root,
  11. * result: import('stylelint').PostcssResult,
  12. * locationChecker: LocationChecker,
  13. * locationType: 'before' | 'after',
  14. * checkedRuleName: string,
  15. * fix: ((combinator: import('postcss-selector-parser').Combinator) => boolean) | null,
  16. * }} opts
  17. * @returns {void}
  18. */
  19. module.exports = function selectorCombinatorSpaceChecker(opts) {
  20. let hasFixed;
  21. opts.root.walkRules((rule) => {
  22. if (!isStandardSyntaxRule(rule)) {
  23. return;
  24. }
  25. hasFixed = false;
  26. const selector = rule.raws.selector ? rule.raws.selector.raw : rule.selector;
  27. const fixedSelector = parseSelector(selector, opts.result, rule, (selectorTree) => {
  28. selectorTree.walkCombinators((node) => {
  29. // Ignore non-standard combinators
  30. if (!isStandardSyntaxCombinator(node)) {
  31. return;
  32. }
  33. // Ignore spaced descendant combinator
  34. if (/\s/.test(node.value)) {
  35. return;
  36. }
  37. // Check the exist of node in prev of the combinator.
  38. // in case some that aren't the first begin with combinators (nesting syntax)
  39. if (opts.locationType === 'before' && !node.prev()) {
  40. return;
  41. }
  42. const parentParentNode = node.parent && node.parent.parent;
  43. // Ignore pseudo-classes selector like `.foo:nth-child(2n + 1) {}`
  44. if (parentParentNode && parentParentNode.type === 'pseudo') {
  45. return;
  46. }
  47. const sourceIndex = node.sourceIndex;
  48. const index =
  49. node.value.length > 1 && opts.locationType === 'before'
  50. ? sourceIndex
  51. : sourceIndex + node.value.length - 1;
  52. check(selector, node, index, rule, sourceIndex);
  53. });
  54. });
  55. if (hasFixed && fixedSelector) {
  56. if (!rule.raws.selector) {
  57. rule.selector = fixedSelector;
  58. } else {
  59. rule.raws.selector.raw = fixedSelector;
  60. }
  61. }
  62. });
  63. /**
  64. * @param {string} source
  65. * @param {import('postcss-selector-parser').Combinator} combinator
  66. * @param {number} index
  67. * @param {import('postcss').Node} node
  68. * @param {number} sourceIndex
  69. */
  70. function check(source, combinator, index, node, sourceIndex) {
  71. opts.locationChecker({
  72. source,
  73. index,
  74. errTarget: combinator.value,
  75. err: (message) => {
  76. if (opts.fix && opts.fix(combinator)) {
  77. hasFixed = true;
  78. return;
  79. }
  80. report({
  81. message,
  82. node,
  83. index: sourceIndex,
  84. result: opts.result,
  85. ruleName: opts.checkedRuleName,
  86. });
  87. },
  88. });
  89. }
  90. };