normalizeSelector.mjs 1.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384
  1. import parser from 'postcss-selector-parser';
  2. /** @import { Container, Node, Root, Selector } from 'postcss-selector-parser' */
  3. /**
  4. * Normalize a selector so that it can be more easily compared to other selectors.
  5. *
  6. * @template {Root | Selector} T
  7. * @param {T} node
  8. * @returns {T}
  9. */
  10. export default function normalizeSelector(node) {
  11. normalizeSelectorNodeWhitespaceAndComments(node);
  12. node.walk(normalizeSelectorNodeWhitespaceAndComments);
  13. normalizeSelectorNodesOrder(node);
  14. return node;
  15. }
  16. /**
  17. * @param {Node} node
  18. */
  19. function normalizeSelectorNodeWhitespaceAndComments(node) {
  20. if (parser.isComment(node)) {
  21. node.remove();
  22. return;
  23. }
  24. node.rawSpaceBefore = '';
  25. node.rawSpaceAfter = '';
  26. node.spaces.before = '';
  27. node.spaces.after = '';
  28. if (parser.isAttribute(node)) {
  29. delete node.spaces.insensitive;
  30. delete node.spaces.operator;
  31. delete node.spaces.attribute;
  32. delete node.spaces.value;
  33. delete node.raws.spaces;
  34. }
  35. }
  36. /**
  37. * @param {Node} node
  38. */
  39. function normalizeSelectorNodesOrder(node) {
  40. if (!parser.isContainer(node)) return;
  41. // Need to sort inside out, not outside in.
  42. // As the parents needs to sorted with their children already in the right order.
  43. node.each(normalizeSelectorNodesOrder);
  44. if (parser.isRoot(node) || parser.isPseudoClass(node) || parser.isPseudoElement(node)) {
  45. const nodes = node.nodes.map((childNode) => {
  46. // Make sure to only stringify the node once.
  47. return { node: childNode, string: childNode.toString() };
  48. });
  49. nodes.sort((a, b) => a.string.localeCompare(b.string));
  50. const uniqueNodes = new Set();
  51. const nodesIndices = new Map();
  52. nodes.forEach((x, i) => {
  53. if (uniqueNodes.has(x.string)) return;
  54. uniqueNodes.add(x.string);
  55. nodesIndices.set(x.node, i);
  56. });
  57. node.nodes = node.nodes
  58. .filter((childNode) => {
  59. return nodesIndices.has(childNode);
  60. })
  61. .sort((a, b) => {
  62. return nodesIndices.get(a) - nodesIndices.get(b);
  63. });
  64. }
  65. }