token-before.cjs 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170
  1. 'use strict';
  2. const types = require('../tokenizer/types.cjs');
  3. const PLUSSIGN = 0x002B; // U+002B PLUS SIGN (+)
  4. const HYPHENMINUS = 0x002D; // U+002D HYPHEN-MINUS (-)
  5. const code = (type, value) => {
  6. if (type === types.Delim) {
  7. type = value;
  8. }
  9. if (typeof type === 'string') {
  10. const charCode = type.charCodeAt(0);
  11. return charCode > 0x7F ? 0x8000 : charCode << 8;
  12. }
  13. return type;
  14. };
  15. // https://www.w3.org/TR/css-syntax-3/#serialization
  16. // The only requirement for serialization is that it must "round-trip" with parsing,
  17. // that is, parsing the stylesheet must produce the same data structures as parsing,
  18. // serializing, and parsing again, except for consecutive <whitespace-token>s,
  19. // which may be collapsed into a single token.
  20. const specPairs = [
  21. [types.Ident, types.Ident],
  22. [types.Ident, types.Function],
  23. [types.Ident, types.Url],
  24. [types.Ident, types.BadUrl],
  25. [types.Ident, '-'],
  26. [types.Ident, types.Number],
  27. [types.Ident, types.Percentage],
  28. [types.Ident, types.Dimension],
  29. [types.Ident, types.CDC],
  30. [types.Ident, types.LeftParenthesis],
  31. [types.AtKeyword, types.Ident],
  32. [types.AtKeyword, types.Function],
  33. [types.AtKeyword, types.Url],
  34. [types.AtKeyword, types.BadUrl],
  35. [types.AtKeyword, '-'],
  36. [types.AtKeyword, types.Number],
  37. [types.AtKeyword, types.Percentage],
  38. [types.AtKeyword, types.Dimension],
  39. [types.AtKeyword, types.CDC],
  40. [types.Hash, types.Ident],
  41. [types.Hash, types.Function],
  42. [types.Hash, types.Url],
  43. [types.Hash, types.BadUrl],
  44. [types.Hash, '-'],
  45. [types.Hash, types.Number],
  46. [types.Hash, types.Percentage],
  47. [types.Hash, types.Dimension],
  48. [types.Hash, types.CDC],
  49. [types.Dimension, types.Ident],
  50. [types.Dimension, types.Function],
  51. [types.Dimension, types.Url],
  52. [types.Dimension, types.BadUrl],
  53. [types.Dimension, '-'],
  54. [types.Dimension, types.Number],
  55. [types.Dimension, types.Percentage],
  56. [types.Dimension, types.Dimension],
  57. [types.Dimension, types.CDC],
  58. ['#', types.Ident],
  59. ['#', types.Function],
  60. ['#', types.Url],
  61. ['#', types.BadUrl],
  62. ['#', '-'],
  63. ['#', types.Number],
  64. ['#', types.Percentage],
  65. ['#', types.Dimension],
  66. ['#', types.CDC], // https://github.com/w3c/csswg-drafts/pull/6874
  67. ['-', types.Ident],
  68. ['-', types.Function],
  69. ['-', types.Url],
  70. ['-', types.BadUrl],
  71. ['-', '-'],
  72. ['-', types.Number],
  73. ['-', types.Percentage],
  74. ['-', types.Dimension],
  75. ['-', types.CDC], // https://github.com/w3c/csswg-drafts/pull/6874
  76. [types.Number, types.Ident],
  77. [types.Number, types.Function],
  78. [types.Number, types.Url],
  79. [types.Number, types.BadUrl],
  80. [types.Number, types.Number],
  81. [types.Number, types.Percentage],
  82. [types.Number, types.Dimension],
  83. [types.Number, '%'],
  84. [types.Number, types.CDC], // https://github.com/w3c/csswg-drafts/pull/6874
  85. ['@', types.Ident],
  86. ['@', types.Function],
  87. ['@', types.Url],
  88. ['@', types.BadUrl],
  89. ['@', '-'],
  90. ['@', types.CDC], // https://github.com/w3c/csswg-drafts/pull/6874
  91. ['.', types.Number],
  92. ['.', types.Percentage],
  93. ['.', types.Dimension],
  94. ['+', types.Number],
  95. ['+', types.Percentage],
  96. ['+', types.Dimension],
  97. ['/', '*']
  98. ];
  99. // validate with scripts/generate-safe
  100. const safePairs = specPairs.concat([
  101. [types.Ident, types.Hash],
  102. [types.Dimension, types.Hash],
  103. [types.Hash, types.Hash],
  104. [types.AtKeyword, types.LeftParenthesis],
  105. [types.AtKeyword, types.String],
  106. [types.AtKeyword, types.Colon],
  107. [types.Percentage, types.Percentage],
  108. [types.Percentage, types.Dimension],
  109. [types.Percentage, types.Function],
  110. [types.Percentage, '-'],
  111. [types.RightParenthesis, types.Ident],
  112. [types.RightParenthesis, types.Function],
  113. [types.RightParenthesis, types.Percentage],
  114. [types.RightParenthesis, types.Dimension],
  115. [types.RightParenthesis, types.Hash],
  116. [types.RightParenthesis, '-']
  117. ]);
  118. function createMap(pairs) {
  119. const isWhiteSpaceRequired = new Set(
  120. pairs.map(([prev, next]) => (code(prev) << 16 | code(next)))
  121. );
  122. return function(prevCode, type, value) {
  123. const nextCode = code(type, value);
  124. const nextCharCode = value.charCodeAt(0);
  125. const emitWs =
  126. (nextCharCode === HYPHENMINUS &&
  127. type !== types.Ident &&
  128. type !== types.Function &&
  129. type !== types.CDC) ||
  130. (nextCharCode === PLUSSIGN)
  131. ? isWhiteSpaceRequired.has(prevCode << 16 | nextCharCode << 8)
  132. : isWhiteSpaceRequired.has(prevCode << 16 | nextCode);
  133. if (emitWs) {
  134. this.emit(' ', types.WhiteSpace, true);
  135. }
  136. return nextCode;
  137. };
  138. }
  139. const spec = createMap(specPairs);
  140. const safe = createMap(safePairs);
  141. exports.safe = safe;
  142. exports.spec = spec;