webpack 5.108.4 → 5.109.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (200) hide show
  1. package/bin/webpack.js +12 -0
  2. package/lib/APIPlugin.js +48 -29
  3. package/lib/CacheFacade.js +8 -0
  4. package/lib/ChunkGraph.js +17 -15
  5. package/lib/ChunkGroup.js +1 -0
  6. package/lib/CompatibilityPlugin.js +62 -29
  7. package/lib/Compilation.js +58 -6
  8. package/lib/ConstPlugin.js +11 -11
  9. package/lib/ContextModule.js +239 -6
  10. package/lib/ContextModuleFactory.js +139 -14
  11. package/lib/DefinePlugin.js +433 -112
  12. package/lib/Entrypoint.js +1 -1
  13. package/lib/ExportsInfo.js +22 -5
  14. package/lib/ExportsInfoApiPlugin.js +2 -2
  15. package/lib/ExternalModule.js +107 -5
  16. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  17. package/lib/FileSystemInfo.js +256 -100
  18. package/lib/FlagDependencyUsagePlugin.js +33 -12
  19. package/lib/HotModuleReplacementPlugin.js +18 -15
  20. package/lib/InitFragment.js +20 -32
  21. package/lib/Module.js +3 -0
  22. package/lib/ModuleFilenameHelpers.js +2 -2
  23. package/lib/ModuleTypeConstants.js +8 -1
  24. package/lib/MultiCompiler.js +13 -2
  25. package/lib/MultiWatching.js +1 -0
  26. package/lib/NodeStuffPlugin.js +51 -33
  27. package/lib/NormalModule.js +175 -401
  28. package/lib/NormalModuleFactory.js +76 -2
  29. package/lib/ProgressPlugin.js +211 -119
  30. package/lib/ProvidePlugin.js +2 -2
  31. package/lib/RuntimeGlobals.js +47 -0
  32. package/lib/RuntimePlugin.js +67 -107
  33. package/lib/RuntimeTemplate.js +304 -16
  34. package/lib/Template.js +23 -3
  35. package/lib/UseStrictPlugin.js +1 -1
  36. package/lib/WebpackIsIncludedPlugin.js +1 -1
  37. package/lib/WebpackOptionsApply.js +24 -19
  38. package/lib/asset/AssetGenerator.js +110 -28
  39. package/lib/asset/AssetModulesPlugin.js +92 -2
  40. package/lib/asset/WebManifestGenerator.js +164 -0
  41. package/lib/asset/WebManifestParser.js +130 -0
  42. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  43. package/lib/async-modules/isGeneratorLowered.js +26 -0
  44. package/lib/cache/PackFileCacheStrategy.js +9 -3
  45. package/lib/config/browserslistTargetHandler.js +40 -0
  46. package/lib/config/defaults.js +219 -29
  47. package/lib/config/normalization.js +26 -0
  48. package/lib/config/target.js +9 -1
  49. package/lib/container/HoistContainerReferencesPlugin.js +47 -55
  50. package/lib/css/CssGenerator.js +40 -31
  51. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  52. package/lib/css/CssModulesPlugin.js +13 -0
  53. package/lib/css/CssParser.js +729 -313
  54. package/lib/css/syntax.js +1371 -861
  55. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  56. package/lib/dependencies/AMDPlugin.js +2 -2
  57. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  58. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  59. package/lib/dependencies/CommonJsExportRequireDependency.js +19 -0
  60. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  61. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  62. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  63. package/lib/dependencies/CommonJsPlugin.js +2 -2
  64. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  65. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  66. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  67. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  68. package/lib/dependencies/CssIcssExportDependency.js +162 -71
  69. package/lib/dependencies/CssUrlDependency.js +35 -4
  70. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  71. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  72. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  73. package/lib/dependencies/HarmonyEvaluatedImportSpecifierDependency.js +2 -1
  74. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  75. package/lib/dependencies/HarmonyExportImportedSpecifierDependency.js +17 -9
  76. package/lib/dependencies/HarmonyImportDependency.js +13 -2
  77. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +11 -11
  78. package/lib/dependencies/HarmonyImportSpecifierDependency.js +10 -7
  79. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  80. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  81. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  82. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  83. package/lib/dependencies/ImportDependency.js +43 -1
  84. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  85. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  86. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  87. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  88. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  89. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  90. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  91. package/lib/dependencies/ImportParserPlugin.js +80 -17
  92. package/lib/dependencies/ImportPhase.js +10 -8
  93. package/lib/dependencies/LoaderPlugin.js +5 -1
  94. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  95. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  96. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  97. package/lib/dependencies/SystemPlugin.js +2 -4
  98. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  99. package/lib/dependencies/URLDependency.js +141 -21
  100. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  101. package/lib/dependencies/WorkerDependency.js +86 -18
  102. package/lib/dependencies/WorkletDependency.js +278 -0
  103. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  104. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  105. package/lib/errors/LoaderLoadingError.js +20 -0
  106. package/lib/errors/ModuleParseError.js +39 -22
  107. package/lib/errors/NodeStuffInWebError.js +1 -1
  108. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  109. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  110. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  111. package/lib/hmr/lazyCompilationBackend.js +51 -12
  112. package/lib/html/HtmlGenerator.js +907 -15
  113. package/lib/html/HtmlModule.js +2 -2
  114. package/lib/html/HtmlModulesPlugin.js +610 -166
  115. package/lib/html/HtmlParser.js +603 -247
  116. package/lib/html/favicon.svg +1 -0
  117. package/lib/html/syntax.js +3451 -2762
  118. package/lib/ids/IdHelpers.js +25 -11
  119. package/lib/index.js +6 -0
  120. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  121. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  122. package/lib/javascript/JavascriptGenerator.js +197 -49
  123. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  124. package/lib/javascript/JavascriptParser.js +704 -355
  125. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  126. package/lib/javascript/syntax.js +5037 -0
  127. package/lib/library/ModuleLibraryPlugin.js +38 -1
  128. package/lib/loaders/LoaderRunner.js +714 -0
  129. package/lib/loaders/loadLoader.js +100 -0
  130. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +29 -2
  131. package/lib/node/ReadFileCompileWasmPlugin.js +24 -2
  132. package/lib/optimize/AggressiveMergingPlugin.js +19 -15
  133. package/lib/optimize/ConcatenatedModule.js +139 -31
  134. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  135. package/lib/optimize/InnerGraphPlugin.js +50 -30
  136. package/lib/optimize/ModuleConcatenationPlugin.js +139 -24
  137. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  138. package/lib/optimize/SplitChunksPlugin.js +146 -44
  139. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  140. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  141. package/lib/prefetch/ResourceHintPlugin.js +687 -0
  142. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  143. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  144. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  145. package/lib/rules/RuleSetCompiler.js +114 -0
  146. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  147. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  148. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  149. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  150. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  151. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  152. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  153. package/lib/schemes/HttpUriPlugin.js +40 -1
  154. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  155. package/lib/serialization/BinaryMiddleware.js +270 -938
  156. package/lib/serialization/FileMiddleware.js +29 -3
  157. package/lib/stats/DefaultStatsFactoryPlugin.js +229 -194
  158. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  159. package/lib/stats/DefaultStatsPrinterPlugin.js +36 -168
  160. package/lib/stats/StatsFactory.js +46 -17
  161. package/lib/url/URLParserPlugin.js +66 -13
  162. package/lib/util/AsyncQueue.js +122 -72
  163. package/lib/util/SetHelpers.js +1 -1
  164. package/lib/util/StackedMap.js +122 -48
  165. package/lib/util/binarySearchBounds.js +2 -2
  166. package/lib/util/concatenate.js +40 -14
  167. package/lib/util/createHooksRegistry.js +13 -5
  168. package/lib/util/dataURL.js +2 -2
  169. package/lib/util/deterministicGrouping.js +7 -5
  170. package/lib/util/formatSize.js +6 -2
  171. package/lib/util/globUtils.js +710 -0
  172. package/lib/util/identifier.js +23 -6
  173. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  174. package/lib/util/internalSerializables.js +88 -71
  175. package/lib/util/magicComment.js +121 -25
  176. package/lib/util/numberHash.js +2 -2
  177. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  178. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  179. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  180. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  181. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  182. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  183. package/lib/webpack.js +65 -0
  184. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  185. package/module.d.ts +21 -0
  186. package/package.json +12 -11
  187. package/schemas/WebpackOptions.check.js +1 -1
  188. package/schemas/WebpackOptions.json +759 -36
  189. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  190. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  191. package/schemas/plugins/ProgressPlugin.json +17 -1
  192. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  193. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  194. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  195. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  196. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  197. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  198. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  199. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  200. package/types.d.ts +1212 -99
package/lib/css/syntax.js CHANGED
@@ -199,6 +199,7 @@ const CC_AT_SIGN = "@".charCodeAt(0);
199
199
 
200
200
  const CC_LOW_LINE = "_".charCodeAt(0);
201
201
  const CC_LOWER_A = "a".charCodeAt(0);
202
+ const CC_LOWER_D = "d".charCodeAt(0);
202
203
  const CC_LOWER_F = "f".charCodeAt(0);
203
204
  const CC_LOWER_E = "e".charCodeAt(0);
204
205
  const CC_LOWER_U = "u".charCodeAt(0);
@@ -341,11 +342,15 @@ const _isIdentStartCodePointCC = (cc) =>
341
342
  /**
342
343
  * Spec: ident-code = ident-start / digit / hyphen-minus.
343
344
  */
344
- // ASCII lookup built from the predicate so behaviour is identical; non-ASCII
345
- // (>= 128) keeps the inline check. `_consumeAnIdentSequence` hits this per code
346
- // point, and class/property names (hyphen + digit heavy) are the longest
347
- // comparison-chain path — a table load is constant-time instead.
348
- const _identCharTable = new Uint8Array(128);
345
+ // Full `charCodeAt` range (0..0xFFFF) so the per-code-point ident test is one
346
+ // table load with no `cc < 128` branch — `_consumeAnIdentSequence` runs this on
347
+ // every character of every ident / class / property name (the tokenizer's
348
+ // hottest loop). Every non-ASCII code unit (>= 0x80) is an ident code point per
349
+ // spec, so those default to 1; only the ASCII rows carry real classification.
350
+ // Callers must index with `cc | 0`: EOF (`charCodeAt` → NaN) becomes 0 (NUL,
351
+ // not an ident) — a raw NaN index is an out-of-range access that permanently
352
+ // degrades the load site's IC.
353
+ const _identCharTable = new Uint8Array(0x10000).fill(1);
349
354
  for (let i = 0; i < 128; i++) {
350
355
  _identCharTable[i] =
351
356
  _isLetter(i) || i === CC_LOW_LINE || _isDigit(i) || i === CC_HYPHEN_MINUS
@@ -356,8 +361,7 @@ for (let i = 0; i < 128; i++) {
356
361
  * @param {number} cc char code
357
362
  * @returns {boolean} true, if cc is an ident-sequence code point
358
363
  */
359
- const _isIdentCodePoint = (cc) =>
360
- cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc);
364
+ const _isIdentCodePoint = (cc) => _identCharTable[cc | 0] === 1;
361
365
 
362
366
  /**
363
367
  * ASCII case-insensitive equality against a lowercase literal — avoids the
@@ -377,6 +381,58 @@ const equalsLowerCase = (s, lit) => {
377
381
  return true;
378
382
  };
379
383
 
384
+ /**
385
+ * Case-sensitive equality of a source range against a literal — no slice.
386
+ * @param {string} input source
387
+ * @param {number} start range start
388
+ * @param {number} end range end (exclusive)
389
+ * @param {string} lit literal to match
390
+ * @returns {boolean} true when the range equals `lit`
391
+ */
392
+ const rangeEquals = (input, start, end, lit) =>
393
+ end - start === lit.length && input.startsWith(lit, start);
394
+
395
+ /**
396
+ * ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
397
+ * @param {string} input source
398
+ * @param {number} start range start
399
+ * @param {number} end range end (exclusive)
400
+ * @param {string} lit lowercase ASCII literal to match
401
+ * @returns {boolean} true when the range equals `lit` ignoring ASCII case
402
+ */
403
+ const rangeEqualsLowerCase = (input, start, end, lit) => {
404
+ if (end - start !== lit.length) return false;
405
+ for (let i = 0; i < lit.length; i++) {
406
+ let c = input.charCodeAt(start + i);
407
+ if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20;
408
+ if (c !== lit.charCodeAt(i)) return false;
409
+ }
410
+ return true;
411
+ };
412
+
413
+ /**
414
+ * `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
415
+ * change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
416
+ * left to the real `toLowerCase`).
417
+ * @param {string} s string
418
+ * @returns {string} lowercased string
419
+ */
420
+ const toLowerCaseIfNeeded = (s) => {
421
+ for (let i = 0; i < s.length; i++) {
422
+ const c = s.charCodeAt(i);
423
+ if ((c >= CC_UPPER_A && c <= CC_UPPER_Z) || c > 127) return s.toLowerCase();
424
+ }
425
+ return s;
426
+ };
427
+
428
+ /**
429
+ * A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`.
430
+ * @param {string} identifier identifier
431
+ * @returns {boolean} true when identifier is dashed, otherwise false
432
+ */
433
+ const isDashedIdentifier = (identifier) =>
434
+ identifier.startsWith("--") && identifier.length >= 3;
435
+
380
436
  /**
381
437
  * Consume an escaped code point.
382
438
  * @param {string} input input
@@ -394,7 +450,8 @@ const _consumeAnEscapedCodePoint = (input, pos) => {
394
450
  if (pos === input.length) return pos;
395
451
  if (_isHexDigit(cc)) {
396
452
  for (let i = 0; i < 5; i++) {
397
- if (_isHexDigit(input.charCodeAt(pos))) pos++;
453
+ if (!_isHexDigit(input.charCodeAt(pos))) break;
454
+ pos++;
398
455
  }
399
456
  const trail = input.charCodeAt(pos);
400
457
  if (_isWhiteSpace(trail)) {
@@ -480,12 +537,13 @@ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
480
537
  const _consumeAnIdentSequence = (input, pos) => {
481
538
  // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
482
539
  // are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the
483
- // ASCII ident test is a single table load, and the escape test reads the
484
- // following code point only when `cc` is a `\` (rare) instead of eagerly.
540
+ // ident test is a single full-range table load (no `cc < 128` branch), and the
541
+ // escape test reads the following code point only when `cc` is a `\` (rare)
542
+ // instead of eagerly.
485
543
  for (;;) {
486
- const cc = input.charCodeAt(pos);
544
+ const cc = input.charCodeAt(pos) | 0;
487
545
  pos++;
488
- if (cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc)) {
546
+ if (_identCharTable[cc] === 1) {
489
547
  continue;
490
548
  }
491
549
  if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
@@ -625,6 +683,15 @@ function consumeSpace(input, pos, out) {
625
683
  return fill(out, TT_WHITESPACE, start, pos);
626
684
  }
627
685
 
686
+ // Sticky fast-forward classes: a native run-skip over the ordinary characters of
687
+ // a string / url token, so long values (data: URIs, base64) don't cost one JS
688
+ // char read each. The negated classes match exactly the per-char terminators the
689
+ // loops below handle (quotes / backslash / newlines for strings; plus `(`, `)`,
690
+ // whitespace and non-printable code points for urls).
691
+ const _STRING_SAFE = /[^"'\\\n\r\f]+/y;
692
+ // eslint-disable-next-line no-control-regex -- url terminators include the control range and DEL (they make a bad-url)
693
+ const _URL_SAFE = /[^\u0000-\u0020\u007F"'()\\]+/y;
694
+
628
695
  /**
629
696
  * Consume a string token. Caller advanced past the opening quote so
630
697
  * `pos - 1` holds the ending code point and `pos - 1` is the start.
@@ -637,6 +704,8 @@ function consumeAStringToken(input, pos, out) {
637
704
  const start = pos - 1;
638
705
  const endingCodePoint = input.charCodeAt(pos - 1);
639
706
  for (;;) {
707
+ _STRING_SAFE.lastIndex = pos;
708
+ if (_STRING_SAFE.test(input)) pos = _STRING_SAFE.lastIndex;
640
709
  if (pos === input.length) {
641
710
  return fill(out, TT_STRING, start, pos);
642
711
  }
@@ -700,17 +769,33 @@ function consumeNumberSign(input, pos, out) {
700
769
  * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
701
770
  */
702
771
  function consumeHyphenMinus(input, pos, out) {
703
- if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
772
+ // Read the two lookahead code points once; the lead is the known `-`.
773
+ const second = input.charCodeAt(pos);
774
+ const third = input.charCodeAt(pos + 1);
775
+ if (
776
+ _ifThreeCodePointsWouldStartANumber(
777
+ input,
778
+ pos,
779
+ CC_HYPHEN_MINUS,
780
+ second,
781
+ third
782
+ )
783
+ ) {
704
784
  pos--;
705
785
  return consumeANumericToken(input, pos, out);
706
786
  }
707
- if (
708
- input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
709
- input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN
710
- ) {
787
+ if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) {
711
788
  return fill(out, TT_CDC, pos - 1, pos + 2);
712
789
  }
713
- if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) {
790
+ if (
791
+ _ifThreeCodePointsWouldStartAnIdentSequence(
792
+ input,
793
+ pos,
794
+ CC_HYPHEN_MINUS,
795
+ second,
796
+ third
797
+ )
798
+ ) {
714
799
  pos--;
715
800
  return consumeAnIdentLikeToken(input, pos, out);
716
801
  }
@@ -800,8 +885,10 @@ function consumeAUrlToken(input, pos, fnStart, out) {
800
885
  const contentStart = pos;
801
886
  out.contentStart = contentStart;
802
887
  for (;;) {
888
+ _URL_SAFE.lastIndex = pos;
889
+ if (_URL_SAFE.test(input)) pos = _URL_SAFE.lastIndex;
803
890
  if (pos === input.length) {
804
- out.contentEnd = pos - 1;
891
+ out.contentEnd = pos;
805
892
  return fill(out, TT_URL, fnStart, pos);
806
893
  }
807
894
  const cc = input.charCodeAt(pos);
@@ -999,7 +1086,11 @@ const HC_AT_SIGN = 9;
999
1086
  const HC_REVERSE_SOLIDUS = 10;
1000
1087
  const HC_DIGIT = 11;
1001
1088
  const HC_IDENT = 12;
1002
- const _charClass = new Uint8Array(128);
1089
+ // Full `charCodeAt` range so `consumeAToken` dispatches with one table load and
1090
+ // no `cc < 128` branch. Every non-ASCII code point (>= 0x80) is an ident-start
1091
+ // lead per §4, so those rows are seeded to `HC_IDENT`; the ASCII rows below
1092
+ // overwrite 0..127 with their real class.
1093
+ const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128);
1003
1094
  const _singleTT = new Uint8Array(128);
1004
1095
  _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
1005
1096
  _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
@@ -1054,7 +1145,7 @@ for (let i = 0; i < 128; i++) {
1054
1145
  function consumeAToken(input, pos, cc, out) {
1055
1146
  // `u` / `U` would start a unicode-range token in the spec; those are not
1056
1147
  // produced, so they map to HC_IDENT and fall through to ident-like.
1057
- switch (cc < 128 ? _charClass[cc] : HC_IDENT) {
1148
+ switch (_charClass[cc]) {
1058
1149
  // Run of whitespace → one <whitespace-token>.
1059
1150
  case HC_WHITESPACE:
1060
1151
  return consumeSpace(input, pos, out);
@@ -1211,78 +1302,23 @@ const {
1211
1302
  } = NodeType;
1212
1303
 
1213
1304
  /**
1214
- * Base AST node. All concrete nodes (tokens, simple blocks, functions,
1215
- * declarations) inherit from this and carry the `[start, end)` byte `range`
1216
- * of the source slice they cover. `loc` is computed on demand from a
1217
- * shared `LocConverter` so we don't pay for line/column conversion until
1218
- * a consumer (warning, error, dependency) actually needs it.
1305
+ * Base AST node — the property-accessor view the `parseA*` entry points return
1306
+ * (see `_makeReader`). Every concrete node carries the `[start, end)` byte
1307
+ * `range` of the source slice it covers; `loc` is computed on demand from the
1308
+ * shared `LocConverter`, so line/column conversion is only paid when a consumer
1309
+ * needs it. The concrete node typedefs below extend this via `&`.
1310
+ *
1311
+ * Inside the parser a node ref is an integer id into the columns; the reader
1312
+ * exposes this property shape over a retained snapshot of those columns.
1313
+ * @typedef {object} Node
1314
+ * @property {number} type node-type discriminator
1315
+ * @property {number} start byte offset of the node's first code point
1316
+ * @property {number} end byte offset just past the node's last code point
1317
+ * @property {[number, number]} range the `[start, end)` byte range
1318
+ * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location (1-based line, 0-based column)
1319
+ * @property {() => string} toString source slice for this node
1320
+ * @property {string} unescapedName name with CSS escapes resolved (name-bearing nodes only)
1219
1321
  */
1220
- class Node {
1221
- /**
1222
- * @param {number} type node type discriminator
1223
- * @param {number} start byte offset of the node's first code point
1224
- * @param {number} end byte offset just past the node's last code point
1225
- * @param {LocConverter} locConverter shared loc converter
1226
- */
1227
- constructor(type, start, end, locConverter) {
1228
- /** @type {number} */
1229
- this.type = type;
1230
- // Byte range as two inline fields rather than a `[start, end]` array —
1231
- // one fewer allocation per node and ~56 bytes lighter (×100k+ nodes).
1232
- /** @type {number} */
1233
- this.start = start;
1234
- /** @type {number} */
1235
- this.end = end;
1236
- /** @type {LocConverter} */
1237
- this._locConverter = locConverter;
1238
- }
1239
-
1240
- /**
1241
- * The `[start, end)` byte range as a tuple — compatibility view over
1242
- * `start` / `end` (builds the array lazily; hot code reads the
1243
- * fields directly).
1244
- * @returns {[number, number]} the byte range
1245
- */
1246
- get range() {
1247
- return [this.start, this.end];
1248
- }
1249
-
1250
- get loc() {
1251
- const lc = this._locConverter;
1252
- // `LocConverter#get` mutates and returns the converter itself, so we
1253
- // must snapshot `line`/`column` between the two calls.
1254
- const s = lc.get(this.start);
1255
- const sl = s.line;
1256
- const sc = s.column;
1257
- const e = lc.get(this.end);
1258
- return {
1259
- start: { line: sl, column: sc },
1260
- end: { line: e.line, column: e.column }
1261
- };
1262
- }
1263
-
1264
- /**
1265
- * Serialize back to source — re-slices the original input (zero-alloc for
1266
- * untouched nodes).
1267
- * @returns {string} the source slice for this node
1268
- */
1269
- toString() {
1270
- return this._locConverter._input.slice(this.start, this.end);
1271
- }
1272
-
1273
- /**
1274
- * For name-bearing nodes (function / at-rule): the `name` with CSS escapes
1275
- * resolved, for case-insensitive keyword matching (`\75 rl` → `url`). Computed
1276
- * on read via `unescapeIdentifier`'s no-escape fast path. Callers without a
1277
- * `name` must not read this.
1278
- * @returns {string} the unescaped name
1279
- */
1280
- get unescapedName() {
1281
- return unescapeIdentifier(
1282
- /** @type {{ name: string }} */ (/** @type {unknown} */ (this)).name
1283
- );
1284
- }
1285
- }
1286
1322
 
1287
1323
  /**
1288
1324
  * @param {string} s numeric text
@@ -1301,175 +1337,15 @@ const _typeFlagOf = (s) =>
1301
1337
  s.includes(".") || s.includes("e") || s.includes("E") ? "number" : "integer";
1302
1338
 
1303
1339
  /**
1304
- * Leaf token node — the only `Node` subclass. `value` is the raw source slice
1340
+ * Leaf token node (property-accessor view) — `value` is the raw source slice
1305
1341
  * (identifier text, quoted string including quotes, a dimension's full `123px`,
1306
- * …). Token-specific extras are named by the `HashToken` / `UrlToken` /
1307
- * `NumberToken` / `DimensionToken` shape typedefs below.
1308
- *
1309
- * The numeric accessors (`numericValue` / `typeFlag` / `sign` / `unit`) are
1310
- * getters, not stored fields: a number / dimension / percentage token costs
1311
- * nothing beyond the base node unless a consumer reads them, and every leaf
1312
- * token keeps a single object shape so the walker's `node.type` dispatch stays
1313
- * monomorphic. They are only meaningful on the matching token type.
1342
+ * …; hash / at-keyword drop their `#` / `@` prefix, url uses its content range).
1343
+ * The `NumberToken` / `HashToken` / `UrlToken` / `DimensionToken` typedefs below
1344
+ * narrow the value accessors. `numericValue` / `typeFlag` / `sign` / `unit` are
1345
+ * derived from the source on read and are only meaningful on the matching token
1346
+ * type; `contentStart` / `contentEnd` mark a url token's inner content range.
1347
+ * @typedef {Node & { value: string, unescaped: string, numericValue: number, typeFlag: "integer" | "number" | "id" | "unrestricted", sign: "+" | "-" | "", unit: string, contentStart: number, contentEnd: number }} Token
1314
1348
  */
1315
- class Token extends Node {
1316
- /**
1317
- * @param {number} type node type
1318
- * @param {number} start byte offset of the token's first code point
1319
- * @param {number} end byte offset just past the token's last code point
1320
- * @param {LocConverter} locConverter shared loc converter
1321
- */
1322
- // No own fields: a leaf token is exactly a `Node` plus the value getters
1323
- // below. `value` is derived from the byte range on read instead of cached in
1324
- // a `_value` slot — most tokens (whitespace, punctuation) never read it, and
1325
- // dropping the slot is ~8 bytes saved on every token (the bulk of all nodes).
1326
- // hash / at-keyword strip their `#` / `@` prefix; url uses its content range
1327
- // (`contentStart` / `contentEnd`, the token's only own fields).
1328
-
1329
- /**
1330
- * @returns {string} the token's value (raw source slice unless overridden)
1331
- */
1332
- get value() {
1333
- const input = this._locConverter._input;
1334
- const type = this.type;
1335
- // hash (`#name` → `name`) and at-keyword (`@name` → `name`) drop one char.
1336
- if (type === T_HASH || type === T_AT_KEYWORD) {
1337
- return input.slice(this.start + 1, this.end);
1338
- }
1339
- if (type === T_URL) {
1340
- const u = /** @type {UrlToken} */ (/** @type {unknown} */ (this));
1341
- return input.slice(u.contentStart, u.contentEnd);
1342
- }
1343
- return input.slice(this.start, this.end);
1344
- }
1345
-
1346
- /**
1347
- * The token's value with CSS escapes resolved (`\2d` → `-`, `\75 rl` → `url`),
1348
- * per https://www.w3.org/TR/css-syntax-3/#consume-escaped-code-point — the
1349
- * form to match keywords / export as a CSS-Modules name against. For a string
1350
- * token it is the content between the quotes (the spec string value). Computed
1351
- * on read; `unescapeIdentifier` fast-returns the value unchanged when it has no
1352
- * escapes (the common case), so nothing is stored per token.
1353
- * @returns {string} the unescaped value
1354
- */
1355
- get unescaped() {
1356
- const v = this.value;
1357
- // A string token's `value` carries its delimiting quotes; its value is the content between them.
1358
- return this.type === T_STRING
1359
- ? unescapeIdentifier(v.slice(1, -1))
1360
- : unescapeIdentifier(v);
1361
- }
1362
-
1363
- /**
1364
- * Parsed numeric value (number / percentage / dimension tokens). Derived from
1365
- * `value` on access — the `%` is dropped for percentages and the unit for
1366
- * dimensions (split with `_consumeANumber`, recomputed here so nothing is
1367
- * stored per token).
1368
- * @returns {number} the parsed numeric value
1369
- */
1370
- get numericValue() {
1371
- const v = this.value;
1372
- if (this.type === T_DIMENSION) {
1373
- return Number(v.slice(0, _consumeANumber(v, 0)));
1374
- }
1375
- if (this.type === T_PERCENTAGE) return Number(v.slice(0, -1));
1376
- return Number(v);
1377
- }
1378
-
1379
- /**
1380
- * Spec type flag. For number / dimension tokens it's "integer" / "number"
1381
- * (derived from `value`); for hash tokens it's "id" / "unrestricted" (re-derived
1382
- * from the source). The two senses share the name in the spec; both are computed
1383
- * on read so every leaf token keeps the same object shape (no own `typeFlag`).
1384
- * @returns {"integer" | "number" | "id" | "unrestricted"} the spec type flag
1385
- */
1386
- get typeFlag() {
1387
- if (this.type === T_HASH) {
1388
- // Re-derive id-ness from the source (whether the name after `#` starts an
1389
- // ident sequence) rather than storing an `_isId` slot — keeps hash tokens
1390
- // the same shape as every other leaf token. Matches the lexer's
1391
- // `consumeNumberSign`, which sets `isId` from the same check.
1392
- const input = this._locConverter._input;
1393
- const p = this.start + 1;
1394
- return _ifThreeCodePointsWouldStartAnIdentSequence(
1395
- input,
1396
- p,
1397
- input.charCodeAt(p),
1398
- input.charCodeAt(p + 1),
1399
- input.charCodeAt(p + 2)
1400
- )
1401
- ? "id"
1402
- : "unrestricted";
1403
- }
1404
- const v = this.value;
1405
- return _typeFlagOf(
1406
- this.type === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
1407
- );
1408
- }
1409
-
1410
- /**
1411
- * @returns {"+" | "-" | ""} the spec sign (number / percentage / dimension tokens)
1412
- */
1413
- get sign() {
1414
- return _signOf(this.value);
1415
- }
1416
-
1417
- /**
1418
- * @returns {string} the unit, lower-cased per spec (dimension tokens)
1419
- */
1420
- get unit() {
1421
- const v = this.value;
1422
- return v.slice(_consumeANumber(v, 0)).toLowerCase();
1423
- }
1424
- }
1425
-
1426
- /**
1427
- * The non-leaf `Node` subclass: functions, simple blocks, declarations, at-rules
1428
- * and qualified rules. It declares the union of every container field up front so
1429
- * all five share **one** hidden class — the consume algorithms only overwrite the
1430
- * slots relevant to their type, never adding a property, so the shape never
1431
- * transitions. This caps the shape count: the walker's hot `.type` / `.value` /
1432
- * `.prelude` loads otherwise see eight distinct node maps (five container types
1433
- * plus the token / hash / url token maps), tipping V8's inline cache into the
1434
- * slow megamorphic path; folding the five containers into one leaves four maps
1435
- * (token, hash, url, container) — within the polymorphic limit, so those loads
1436
- * stay inline-cached. Unused-for-the-type slots keep their defaults (the field
1437
- * typedefs below document which fields each type uses). The stylesheet node is
1438
- * rare (one per parse) and stays a bare `Node`.
1439
- */
1440
- class Container extends Node {
1441
- /**
1442
- * @param {number} type node type
1443
- * @param {number} start byte offset of the node's first code point
1444
- * @param {number} end byte offset just past the node's last code point
1445
- * @param {LocConverter} locConverter shared loc converter
1446
- */
1447
- constructor(type, start, end, locConverter) {
1448
- super(type, start, end, locConverter);
1449
- /** @type {string} name (function / at-rule / declaration) */
1450
- this.name = "";
1451
- /** @type {number} */
1452
- this.nameStart = start;
1453
- /** @type {number} */
1454
- this.nameEnd = start;
1455
- /** @type {ComponentValue[] | null} component values (function / block / declaration) */
1456
- this.value = null;
1457
- /** @type {ComponentValue[] | null} prelude (at-rule / qualified rule) */
1458
- this.prelude = null;
1459
- /** @type {Declaration[] | null} */
1460
- this.declarations = null;
1461
- /** @type {Rule[] | null} */
1462
- this.childRules = null;
1463
- /** @type {number} `{` start offset, or -1 (at-rule / qualified rule) */
1464
- this.blockStart = -1;
1465
- /** @type {number} `}` end offset, or -1 (at-rule / qualified rule) */
1466
- this.blockEnd = -1;
1467
- /** @type {boolean} stripped `!important` (declaration) */
1468
- this.important = false;
1469
- /** @type {SimpleBlockToken | undefined} opening char (simple block) */
1470
- this.token = undefined;
1471
- }
1472
- }
1473
1349
 
1474
1350
  /**
1475
1351
  * Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`).
@@ -1568,10 +1444,10 @@ class Container extends Node {
1568
1444
  * @typedef {Node & { rules: Rule[] }} Stylesheet
1569
1445
  */
1570
1446
 
1571
- // Lexer-token-type → AST-node-type map. A single \`new Token\` construct site
1572
- // (vs a ~20-case switch with a \`new Token\` in each arm) keeps V8 on the fast
1573
- // monomorphic allocation path — the switch form showed up as generic construct
1574
- // stubs in profiles. URL is the one type with extra own state, handled first.
1447
+ // Lexer-token-type → AST-node-type map. A single `_makeLeaf` call site (vs a
1448
+ // ~20-case switch with an alloc in each arm) keeps V8 on the fast monomorphic
1449
+ // path — the switch form showed up as generic stubs in profiles. URL is the one
1450
+ // type with extra own state, handled first.
1575
1451
  const _ttToNodeType = new Uint8Array(27);
1576
1452
  _ttToNodeType[TT_WHITESPACE] = T_WHITESPACE;
1577
1453
  _ttToNodeType[TT_IDENTIFIER] = T_IDENT;
@@ -1593,19 +1469,14 @@ _ttToNodeType[TT_RIGHT_CURLY_BRACKET] = T_RIGHT_CURLY_BRACKET;
1593
1469
  _ttToNodeType[TT_CDO] = T_CDO;
1594
1470
  _ttToNodeType[TT_CDC] = T_CDC;
1595
1471
 
1596
- // === AST construction backend ===
1597
- // The consume algorithms build nodes through these module-level primitives
1598
- // rather than `new Token` / `new Container` directly, so the node
1599
- // representation can be swapped under the parser. The object backend below
1600
- // builds the retainable `Node` / `Token` / `Container` tree the `parseA*`
1601
- // entry points return; the Struct-of-Arrays backend (added separately) writes
1602
- // the same nodes into reused typed arrays for the streaming `grammar`, where
1603
- // per-node allocation dominates cost. Child lists are plain arrays in both
1604
- // backends (a node ref is an object or an integer index); only node creation /
1605
- // field access differs, so only those ops are swapped.
1606
-
1607
- // Current loc converter for the active parse (object backend reads it).
1608
- let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1472
+ // === AST construction ===
1473
+ // Nodes live in one struct-of-arrays node store: a node ref is an integer id
1474
+ // into parallel typed-array columns, so per-node allocation is avoided entirely.
1475
+ // The consume algorithms build nodes through the `_make*` / `_set*` primitives
1476
+ // below, which write those columns directly. The streaming `grammar` walks each
1477
+ // top-level node and recycles the columns; the `parseA*` entry points instead
1478
+ // retain the columns as a snapshot and hand back property-accessor nodes over it
1479
+ // (see `_makeReader`). Child lists are plain arrays of node ids in both modes.
1609
1480
 
1610
1481
  // Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
1611
1482
  // `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
@@ -1614,8 +1485,15 @@ let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1614
1485
  // / functions kept, so `url()` in a selector or `@import url(…)` still resolves).
1615
1486
  // A skipped node is still tokenized (positions stay correct) but never pushed,
1616
1487
  // so it is never walked or read — the caller must only skip what nothing reads.
1617
- // `useObjectBackend` restores the defaults so `parseA*` build the full tree.
1488
+ // `parseA*` leave these at their no-skip defaults so they build the full tree.
1618
1489
  const _NO_SKIP_TYPES = new Uint8Array(32);
1490
+ // Shared frozen empty list for block bodies with no decls / no child rules (the
1491
+ // common case — most rules carry only declarations). Every consumer reads these
1492
+ // lists read-only and null-guards, so one immutable instance replaces ~one empty
1493
+ // array allocation per rule; frozen so any errant push fails loud.
1494
+ const _EMPTY_LIST = /** @type {Rule[]} */ (
1495
+ /** @type {unknown} */ (Object.freeze([]))
1496
+ );
1619
1497
  /** @type {Uint8Array} */
1620
1498
  let _skipTypes = _NO_SKIP_TYPES;
1621
1499
  // Fast-path flag: true only when a real skip set is active, so the (dominant)
@@ -1624,278 +1502,255 @@ let _skipActive = false;
1624
1502
  let _skipSelectorPrelude = false;
1625
1503
  let _skipAtRulePrelude = false;
1626
1504
 
1627
- /** @type {(type: number, start: number, end: number) => Node} */
1628
- let _mkLeaf;
1629
- /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1630
- let _mkUrl;
1631
- /** @type {(type: number, start: number, end: number) => Node} */
1632
- let _mkContainer;
1633
- /** @type {(start: number) => Node} */
1634
- let _mkStylesheet;
1635
- /** @type {(r: Node, v: string, nameStart: number, nameEnd: number) => void} */
1636
- let _setName;
1637
- /** @type {(r: Node, v: number) => void} */
1638
- let _setEnd;
1639
- /** @type {(r: Node, blockStart: number, blockEnd: number) => void} */
1640
- let _setBlock;
1641
- /** @type {(r: Node) => void} */
1642
- let _setImportant;
1643
- /** @type {(r: Node, ch: SimpleBlockToken) => void} */
1644
- let _setToken;
1645
- /** @type {(r: Node, list: Node[]) => void} */
1646
- let _setValue;
1647
- /** @type {(r: Node, list: Node[]) => void} */
1648
- let _setPrelude;
1649
- /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
1650
- let _setBody;
1651
- /** @type {(r: Node, list: Node[]) => void} */
1652
- let _setRules;
1653
- /** @type {(r: Node) => number} */
1654
- let _nType;
1655
- /** @type {(r: Node) => number} */
1656
- let _nStart;
1657
- /** @type {(r: Node) => string} */
1658
- let _nValue;
1659
- /** @type {(r: Node) => SimpleBlockToken} */
1660
- let _nToken;
1661
-
1662
- // Child lists are plain arrays in every backend; refs are pushed by value.
1663
- const _list = () => /** @type {Node[]} */ ([]);
1664
-
1665
- // -- object backend: builds the retainable class-instance tree --
1666
- /** @type {typeof _mkLeaf} */
1667
- const _objLeaf = (type, start, end) => new Token(type, start, end, _lc);
1668
- /** @type {typeof _mkUrl} */
1669
- const _objUrl = (start, end, cs, ce) => {
1670
- const u = /** @type {UrlToken} */ (new Token(T_URL, start, end, _lc));
1671
- u.contentStart = cs;
1672
- u.contentEnd = ce;
1673
- return u;
1674
- };
1675
- /** @type {typeof _mkContainer} */
1676
- const _objContainer = (type, start, end) =>
1677
- new Container(type, start, end, _lc);
1678
- /** @type {typeof _mkStylesheet} */
1679
- const _objStylesheet = (start) => {
1680
- const s = /** @type {Stylesheet} */ (
1681
- new Node(T_STYLESHEET, start, start, _lc)
1682
- );
1683
- s.rules = [];
1684
- return s;
1685
- };
1686
- /** @param {LocConverter} lc loc converter for this parse */
1687
- const useObjectBackend = (lc) => {
1688
- _lc = lc;
1689
- // `parseA*` return the full tree, so nothing is skipped.
1690
- _skipTypes = _NO_SKIP_TYPES;
1691
- _skipActive = false;
1692
- _skipSelectorPrelude = false;
1693
- _skipAtRulePrelude = false;
1694
- _mkLeaf = _objLeaf;
1695
- _mkUrl = _objUrl;
1696
- _mkContainer = _objContainer;
1697
- _mkStylesheet = _objStylesheet;
1698
- _setName = (r, v, ns, ne) => {
1699
- const c = /** @type {Container} */ (r);
1700
- c.name = v;
1701
- c.nameStart = ns;
1702
- c.nameEnd = ne;
1703
- };
1704
- _setEnd = (r, v) => {
1705
- r.end = v;
1706
- };
1707
- _setBlock = (r, bs, be) => {
1708
- const c = /** @type {Container} */ (r);
1709
- c.blockStart = bs;
1710
- c.blockEnd = be;
1711
- };
1712
- _setImportant = (r) => {
1713
- /** @type {Container} */ (r).important = true;
1714
- };
1715
- _setToken = (r, ch) => {
1716
- /** @type {Container} */ (r).token = ch;
1717
- };
1718
- _setValue = (r, list) => {
1719
- /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1720
- };
1721
- _setPrelude = (r, list) => {
1722
- /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (
1723
- list
1724
- );
1725
- };
1726
- _setBody = (r, decls, childRules) => {
1727
- const c = /** @type {Container} */ (r);
1728
- c.declarations = /** @type {Declaration[]} */ (decls);
1729
- c.childRules = /** @type {Rule[]} */ (childRules);
1730
- };
1731
- _setRules = (r, list) => {
1732
- /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1733
- };
1734
- _nType = (r) => r.type;
1735
- _nStart = (r) => r.start;
1736
- _nValue = (r) => /** @type {Token} */ (r).value;
1737
- _nToken = (r) =>
1738
- /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1739
- };
1740
-
1741
- // -- struct-of-arrays backend: writes nodes into reused typed arrays --
1505
+ // Scratch content-list pool: a container's value / prelude is built in a plain
1506
+ // array, then sealed into the flat value buffer by `_setValue`, which returns
1507
+ // the array to the pool — so a parse allocates almost no per-list arrays. An
1508
+ // abandoned (never-sealed) list simply falls out of the pool.
1509
+ /** @type {Node[][]} */
1510
+ const _listPool = [];
1511
+ const _takeList = () =>
1512
+ _listPool.length > 0
1513
+ ? /** @type {Node[]} */ (_listPool.pop())
1514
+ : /** @type {Node[]} */ ([]);
1515
+
1516
+ // -- struct-of-arrays store: nodes live in reused typed-array columns --
1742
1517
  // A node ref is its integer id; fields live in parallel arrays indexed by id.
1743
- // Three reused int slots (`_soaA0/1/2`) plus a flags byte carry the per-type
1744
- // extras; child lists hang off three object arrays. Slot meaning by type:
1745
- // url: a0 contentStart, a1 contentEnd
1746
- // function: a0 nameEnd
1747
- // declaration: a0 nameEnd, flags bit0 important
1748
- // at-rule: a0 nameEnd, a1 blockStart, a2 blockEnd
1749
- // qualified: a1 blockStart, a2 blockEnd
1518
+ // Two reused int slots (`_aux0/1`) plus a flags byte carry the per-type
1519
+ // extras; child lists hang off three object arrays. Aux slot meaning by type:
1520
+ // url: aux0 contentStart, aux1 contentEnd
1521
+ // function: aux0 nameEnd
1522
+ // declaration: aux0 nameEnd, flags bit0 important
1523
+ // at-rule: aux0 nameEnd, aux1 blockStart (blockEnd == end)
1524
+ // qualified: aux1 blockStart (blockEnd == end)
1750
1525
  // `name` / `nameStart` / a simple block's `token` are derived from the source
1751
- // on read (see the SoA accessors), so they need no slot. Lists: l0 =
1752
- // value | prelude | stylesheet rules, l1 = declarations, l2 = childRules.
1753
- // `grammar` resets `_soaN` to 0 after each top-level rule's walk, so the
1526
+ // on read (see the accessors), so they need no slot. A node's main content
1527
+ // (value | prelude | stylesheet rules) is a `_flat` span (see below).
1528
+ // `grammar` resets `_nodeCount` to 0 after each top-level rule's walk, so the
1754
1529
  // buffers are reused across rules and the parse allocates almost nothing.
1755
- let _soaCap = 0;
1756
- let _soaN = 0;
1757
- let _soaTy = new Uint8Array(0);
1758
- let _soaSt = new Int32Array(0);
1759
- let _soaEn = new Int32Array(0);
1760
- let _soaA0 = new Int32Array(0);
1761
- let _soaA1 = new Int32Array(0);
1762
- let _soaA2 = new Int32Array(0);
1763
- let _soaFl = new Uint8Array(0);
1764
- /** @type {(Node[] | null)[]} */
1765
- const _soaL0 = [];
1530
+ let _capacity = 0;
1531
+ let _nodeCount = 0;
1532
+ let _types = new Uint8Array(0);
1533
+ let _starts = new Int32Array(0);
1534
+ let _ends = new Int32Array(0);
1535
+ let _aux0 = new Int32Array(0);
1536
+ let _aux1 = new Int32Array(0);
1537
+ let _flags = new Uint8Array(0);
1538
+ // Content-list spans: a container's value / prelude is `_flat[start, start+len)`
1539
+ // (node refs), recycled per top-level rule like the node columns.
1540
+ let _listStarts = new Int32Array(0);
1541
+ let _listLens = new Int32Array(0);
1542
+ let _flat = new Int32Array(0);
1543
+ let _flatTop = 0;
1544
+ // Peak usage of the current parse, and use-once regrow hints: after an
1545
+ // over-capacity shrink the next grow jumps straight back to the previous
1546
+ // parse's peak (one exact-fit allocation instead of re-doubling up).
1547
+ let _peak = 0;
1548
+ let _flatPeak = 0;
1549
+ let _growHint = 0;
1550
+ let _flatGrowHint = 0;
1551
+
1552
+ /** @param {number} need minimum flat-buffer capacity */
1553
+ const _flatGrow = (need) => {
1554
+ let cap = _flat.length || 4096;
1555
+ if (_flatGrowHint > cap) cap = _flatGrowHint;
1556
+ _flatGrowHint = 0;
1557
+ while (cap < need) cap *= 2;
1558
+ const next = new Int32Array(cap);
1559
+ next.set(_flat);
1560
+ _flat = next;
1561
+ };
1562
+ // Reassigned (not mutated) when a `parseA*` parse hands its columns to a
1563
+ // retained snapshot, so the next parse starts on fresh arrays.
1766
1564
  /** @type {(Node[] | null)[]} */
1767
- const _soaL1 = [];
1565
+ let _declarationLists = [];
1768
1566
  /** @type {(Node[] | null)[]} */
1769
- const _soaL2 = [];
1770
- let _soaInput = "";
1771
- let _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1567
+ let _childRuleLists = [];
1568
+ let _input = "";
1569
+ let _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1772
1570
 
1773
1571
  // Node refs are integers here but typed `Node` across the parser; these are
1774
1572
  // identity casts that just satisfy the type system at the boundary.
1775
1573
  /** @type {(n: Node) => number} */
1776
- const _ix = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1574
+ const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1777
1575
  /** @type {(i: number) => Node} */
1778
- const _ref = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1576
+ const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1779
1577
 
1780
1578
  /** @param {number} need minimum capacity */
1781
- const _soaGrow = (need) => {
1782
- let cap = _soaCap || 4096;
1579
+ const _grow = (need) => {
1580
+ let cap = _capacity || 4096;
1581
+ if (_growHint > cap) cap = _growHint;
1582
+ _growHint = 0;
1783
1583
  while (cap < need) cap *= 2;
1784
1584
  const ty = new Uint8Array(cap);
1785
- ty.set(_soaTy);
1786
- _soaTy = ty;
1585
+ ty.set(_types);
1586
+ _types = ty;
1787
1587
  const st = new Int32Array(cap);
1788
- st.set(_soaSt);
1789
- _soaSt = st;
1588
+ st.set(_starts);
1589
+ _starts = st;
1790
1590
  const en = new Int32Array(cap);
1791
- en.set(_soaEn);
1792
- _soaEn = en;
1591
+ en.set(_ends);
1592
+ _ends = en;
1793
1593
  const a0 = new Int32Array(cap);
1794
- a0.set(_soaA0);
1795
- _soaA0 = a0;
1594
+ a0.set(_aux0);
1595
+ _aux0 = a0;
1796
1596
  const a1 = new Int32Array(cap);
1797
- a1.set(_soaA1);
1798
- _soaA1 = a1;
1799
- const a2 = new Int32Array(cap);
1800
- a2.set(_soaA2);
1801
- _soaA2 = a2;
1597
+ a1.set(_aux1);
1598
+ _aux1 = a1;
1802
1599
  const fl = new Uint8Array(cap);
1803
- fl.set(_soaFl);
1804
- _soaFl = fl;
1805
- _soaCap = cap;
1600
+ fl.set(_flags);
1601
+ _flags = fl;
1602
+ const ls = new Int32Array(cap);
1603
+ ls.set(_listStarts);
1604
+ _listStarts = ls;
1605
+ const ll = new Int32Array(cap);
1606
+ ll.set(_listLens);
1607
+ _listLens = ll;
1608
+ _capacity = cap;
1806
1609
  };
1807
1610
  /** @type {(type: number, start: number, end: number) => Node} */
1808
- const _soaAlloc = (type, start, end) => {
1611
+ const _makeLeaf = (type, start, end) => {
1809
1612
  // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1810
1613
  // so 0 must stay reserved for "no node".
1811
- // Leaves never read the flag / list slots — `_soaAllocContainer` clears
1614
+ // Leaves never read the flag / list slots — `_makeContainer` clears
1812
1615
  // them instead, keeping the dominant leaf allocation at three writes.
1813
- const i = _soaN + 1;
1814
- if (i >= _soaCap) _soaGrow(i + 1);
1815
- _soaTy[i] = type;
1816
- _soaSt[i] = start;
1817
- _soaEn[i] = end;
1818
- _soaN = i;
1819
- return _ref(i);
1616
+ const i = _nodeCount + 1;
1617
+ if (i >= _capacity) _grow(i + 1);
1618
+ _types[i] = type;
1619
+ _starts[i] = start;
1620
+ _ends[i] = end;
1621
+ _nodeCount = i;
1622
+ return _nodeRef(i);
1820
1623
  };
1821
1624
  /** @type {(type: number, start: number, end: number) => Node} */
1822
- const _soaAllocContainer = (type, start, end) => {
1823
- const r = _soaAlloc(type, start, end);
1824
- const i = _ix(r);
1825
- _soaFl[i] = 0;
1826
- // Clear list slots so a reused id never exposes a previous node's children.
1827
- _soaL0[i] = null;
1828
- _soaL1[i] = null;
1829
- _soaL2[i] = null;
1625
+ const _makeContainer = (type, start, end) => {
1626
+ const r = _makeLeaf(type, start, end);
1627
+ const i = _nodeIndex(r);
1628
+ _flags[i] = 0;
1629
+ // Clear the content-span length so a reused id never exposes a previous
1630
+ // node's children (content lists are flat spans, so zeroing the length
1631
+ // suffices). `blockStart` (aux1) is NOT defaulted here: only at-rules and
1632
+ // qualified rules carry a block, and they set it on every return path
1633
+ // (`_setBlock`, or `-1` for the no-block forms). `blockEnd` is not stored — a
1634
+ // block rule's `end` is its `blockEnd` (see `_setBlock`), else it is `-1`.
1635
+ _listLens[i] = 0;
1636
+ // The decl / child-rule slots are read only for rules (the walk guards on
1637
+ // type; readers treat a missing slot as `null`), so only rules clear them —
1638
+ // clearing every container would write these `id`-indexed arrays at scattered
1639
+ // ids and degrade them to slow dictionary mode on large non-recycling parses.
1640
+ if (type === T_AT_RULE || type === T_QUALIFIED_RULE) {
1641
+ _declarationLists[i] = null;
1642
+ _childRuleLists[i] = null;
1643
+ }
1830
1644
  return r;
1831
1645
  };
1832
1646
  // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
1833
1647
  // one-char prefix, url uses its content range. Shared by the parser's
1834
- // mid-parse reads and the SoA accessor.
1648
+ // mid-parse reads and the accessor.
1835
1649
  /**
1836
1650
  * @param {number} i node id
1837
1651
  * @returns {string} raw token value
1838
1652
  */
1839
- const _soaValueOf = (i) => {
1840
- const ty = _soaTy[i];
1653
+ const _valueOf = (i) => {
1654
+ const ty = _types[i];
1841
1655
  if (ty === T_HASH || ty === T_AT_KEYWORD) {
1842
- return _soaInput.slice(_soaSt[i] + 1, _soaEn[i]);
1656
+ return _input.slice(_starts[i] + 1, _ends[i]);
1843
1657
  }
1844
- if (ty === T_URL) return _soaInput.slice(_soaA0[i], _soaA1[i]);
1845
- return _soaInput.slice(_soaSt[i], _soaEn[i]);
1658
+ if (ty === T_URL) return _input.slice(_aux0[i], _aux1[i]);
1659
+ return _input.slice(_starts[i], _ends[i]);
1660
+ };
1661
+ // Module-level constants (not per-parse closures), so each consume-algorithm
1662
+ // call site keeps one function identity and stays monomorphic.
1663
+ /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1664
+ const _makeUrl = (start, end, cs, ce) => {
1665
+ const r = _makeLeaf(T_URL, start, end);
1666
+ _aux0[_nodeIndex(r)] = cs;
1667
+ _aux1[_nodeIndex(r)] = ce;
1668
+ return r;
1669
+ };
1670
+ /** @type {(start: number) => Node} */
1671
+ const _makeStylesheet = (start) => _makeContainer(T_STYLESHEET, start, start);
1672
+ // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
1673
+ /** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
1674
+ const _setName = (r, ns, ne) => {
1675
+ _aux0[_nodeIndex(r)] = ne;
1676
+ };
1677
+ /** @type {(r: Node, v: number) => void} */
1678
+ const _setEnd = (r, v) => {
1679
+ _ends[_nodeIndex(r)] = v;
1680
+ };
1681
+ // `blockEnd` is not stored: for a block rule the parser sets `end` to the
1682
+ // block end right after this (so `A.blockEnd` reads `end`); a `-1` blockStart
1683
+ // marks the no-block forms, where `blockEnd` derives to `-1`.
1684
+ /** @type {(r: Node, blockStart: number) => void} */
1685
+ const _setBlock = (r, bs) => {
1686
+ _aux1[_nodeIndex(r)] = bs;
1846
1687
  };
1688
+ /** @type {(r: Node) => void} */
1689
+ const _setImportant = (r) => {
1690
+ _flags[_nodeIndex(r)] |= 1;
1691
+ };
1692
+ // A simple block's token is derived from its opening char on read.
1693
+ /** @type {(r: Node, ch: SimpleBlockToken) => void} */
1694
+ const _setToken = (r, ch) => {};
1695
+ /** @type {(r: Node, list: Node[]) => void} */
1696
+ const _setValue = (r, list) => {
1697
+ // Seal the finished list: copy its refs into the flat buffer and hand the
1698
+ // scratch array back to the pool. The caller never touches `list` again.
1699
+ const len = list.length;
1700
+ // Empty seal (common in non-modules skip mode, where value/prelude leaves are
1701
+ // dropped): `_makeContainer` already left `_listLens` at 0, so skip the flat
1702
+ // writes entirely and just recycle the scratch array.
1703
+ if (len !== 0) {
1704
+ const i = _nodeIndex(r);
1705
+ const start = _flatTop;
1706
+ if (start + len > _flat.length) _flatGrow(start + len);
1707
+ for (let k = 0; k < len; k++) {
1708
+ _flat[start + k] = _nodeIndex(list[k]);
1709
+ }
1710
+ _flatTop = start + len;
1711
+ _listStarts[i] = start;
1712
+ _listLens[i] = len;
1713
+ list.length = 0;
1714
+ }
1715
+ _listPool.push(list);
1716
+ };
1717
+ /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
1718
+ const _setBody = (r, decls, childRules) => {
1719
+ const i = _nodeIndex(r);
1720
+ _declarationLists[i] = decls;
1721
+ _childRuleLists[i] = childRules;
1722
+ };
1723
+ /** @type {(r: Node) => number} */
1724
+ const _nodeTypeOf = (r) => _types[_nodeIndex(r)];
1725
+ /** @type {(r: Node) => number} */
1726
+ const _nodeStartOf = (r) => _starts[_nodeIndex(r)];
1727
+ /** @type {(r: Node) => string} */
1728
+ const _nodeValueOf = (r) => _valueOf(_nodeIndex(r));
1729
+ /** @type {(r: Node) => SimpleBlockToken} */
1730
+ const _nodeTokenOf = (r) =>
1731
+ /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(r)]]);
1732
+ // A container's value, a rule's prelude, and a stylesheet's rules all seal into
1733
+ // the one content-list writer, so `_setPrelude` / `_setRules` are named views of
1734
+ // `_setValue` that keep the consume algorithms reading in spec terms.
1735
+ const _setPrelude = _setValue;
1736
+ const _setRules = _setValue;
1737
+
1847
1738
  /**
1739
+ * Start a parse into the store: point it at this source, reset the node /
1740
+ * flat cursors so nodes accumulate from id 1, and clear skip state (`grammar`
1741
+ * sets its own afterwards; `parseA*` leave it off to build the full tree).
1848
1742
  * @param {string} input source
1849
1743
  * @param {LocConverter} lc loc converter
1850
1744
  */
1851
- const useSoaBackend = (input, lc) => {
1852
- _soaInput = input;
1853
- _soaLC = lc;
1854
- _soaN = 0;
1855
- _mkLeaf = (type, start, end) => _soaAlloc(type, start, end);
1856
- _mkUrl = (start, end, cs, ce) => {
1857
- const r = _soaAlloc(T_URL, start, end);
1858
- _soaA0[_ix(r)] = cs;
1859
- _soaA1[_ix(r)] = ce;
1860
- return r;
1861
- };
1862
- _mkContainer = (type, start, end) => _soaAllocContainer(type, start, end);
1863
- _mkStylesheet = (start) => _soaAllocContainer(T_STYLESHEET, start, start);
1864
- // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
1865
- _setName = (r, v, ns, ne) => {
1866
- _soaA0[_ix(r)] = ne;
1867
- };
1868
- _setEnd = (r, v) => {
1869
- _soaEn[_ix(r)] = v;
1870
- };
1871
- _setBlock = (r, bs, be) => {
1872
- const i = _ix(r);
1873
- _soaA1[i] = bs;
1874
- _soaA2[i] = be;
1875
- };
1876
- _setImportant = (r) => {
1877
- _soaFl[_ix(r)] |= 1;
1878
- };
1879
- // A simple block's token is derived from its opening char on read.
1880
- _setToken = (r, ch) => {};
1881
- _setValue = (r, list) => {
1882
- _soaL0[_ix(r)] = list;
1883
- };
1884
- _setPrelude = (r, list) => {
1885
- _soaL0[_ix(r)] = list;
1886
- };
1887
- _setBody = (r, decls, childRules) => {
1888
- const i = _ix(r);
1889
- _soaL1[i] = decls;
1890
- _soaL2[i] = childRules;
1891
- };
1892
- _setRules = (r, list) => {
1893
- _soaL0[_ix(r)] = list;
1894
- };
1895
- _nType = (r) => _soaTy[_ix(r)];
1896
- _nStart = (r) => _soaSt[_ix(r)];
1897
- _nValue = (r) => _soaValueOf(_ix(r));
1898
- _nToken = (r) => /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(r)]]);
1745
+ const _setupParse = (input, lc) => {
1746
+ _input = input;
1747
+ _locConverter = lc;
1748
+ _nodeCount = 0;
1749
+ _flatTop = 0;
1750
+ _skipTypes = _NO_SKIP_TYPES;
1751
+ _skipActive = false;
1752
+ _skipSelectorPrelude = false;
1753
+ _skipAtRulePrelude = false;
1899
1754
  };
1900
1755
 
1901
1756
  /**
@@ -1909,9 +1764,9 @@ const tokenToNode = (t) => {
1909
1764
  // plain leaf whose node type comes from the map.
1910
1765
  if (tt === TT_URL) {
1911
1766
  const ut = /** @type {CssUrlToken} */ (t);
1912
- return _mkUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
1767
+ return _makeUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
1913
1768
  }
1914
- return _mkLeaf(_ttToNodeType[tt], t.start, t.end);
1769
+ return _makeLeaf(_ttToNodeType[tt], t.start, t.end);
1915
1770
  };
1916
1771
 
1917
1772
  /**
@@ -2033,6 +1888,20 @@ class TokenStream {
2033
1888
  }
2034
1889
  }
2035
1890
 
1891
+ /**
1892
+ * Advance past the already-peeked next token, skipping the redundant `next()`
1893
+ * re-check `consume` / `discard` pay. Precondition: the caller has just called
1894
+ * `next()` (so `_tok` is the cached next token and `_hasNext` is true) and that
1895
+ * token is not the `<eof-token>` — the hot "peek, decide, advance" sites where
1896
+ * both always hold. Callers that can't guarantee a non-EOF cached token use
1897
+ * `consume` / `discard` instead.
1898
+ * @returns {void}
1899
+ */
1900
+ advance() {
1901
+ this._pos = this._tok.end;
1902
+ this._hasNext = false;
1903
+ }
1904
+
2036
1905
  /**
2037
1906
  * Mark (CSS Syntax §3 "mark") — push the current cursor position.
2038
1907
  * @returns {void}
@@ -2088,30 +1957,380 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
2088
1957
  // algorithms that drive the tokenizer.
2089
1958
 
2090
1959
  /**
2091
- * @typedef {object} ParseListOptions
1960
+ * @typedef {object} ParseOptions
2092
1961
  * @property {((input: string, start: number, end: number) => number)=} comment optional comment-token callback; the public `parse*` entry points use it to build the `TokenStream` so the outer parser's comment tracker still sees magic comments inside the consumed range
2093
1962
  */
2094
1963
 
1964
+ // === parseA* retained store + property-accessor readers ===
1965
+ // `grammar` recycles the columns per top-level rule, but the `parseA*` entry
1966
+ // points must hand back a tree that outlives the parse. Each `parseA*` run parses
1967
+ // into the columns without recycling, then `_finishStore` hands them to a
1968
+ // snapshot object and resets the module columns to fresh arrays so the next parse
1969
+ // can't clobber it. Nodes are exposed as `parseA*` readers: plain objects sharing
1970
+ // one module-level prototype whose getters index the reader's own snapshot by node
1971
+ // id — no per-node class, no eager string slices, child readers built lazily on
1972
+ // access. A single shared prototype (rather than one per store) keeps the readers
1973
+ // monomorphic across parses, so `_readerAt` and every getter stay on the fast path.
1974
+
1975
+ /**
1976
+ * @typedef {object} NodeReader
1977
+ * @property {NodeStore} _store snapshot this reader indexes
1978
+ * @property {number} _i node id into the snapshot columns
1979
+ * @property {number} type node type
1980
+ * @property {number} start start offset
1981
+ * @property {number} end end offset
1982
+ * @property {[number, number]} range start / end offsets
1983
+ * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location
1984
+ * @property {() => string} toString source slice
1985
+ * @property {string | ComponentValue[]} value token value (leaf) or component-value list (function / block / declaration)
1986
+ * @property {string} unescaped unescaped token value
1987
+ * @property {number} numericValue parsed numeric value
1988
+ * @property {"integer" | "number" | "id" | "unrestricted"} typeFlag spec type flag
1989
+ * @property {"+" | "-" | ""} sign spec sign
1990
+ * @property {string} unit dimension unit (lower-cased)
1991
+ * @property {number} contentStart url content start offset
1992
+ * @property {number} contentEnd url content end offset
1993
+ * @property {string} name rule / declaration / function name
1994
+ * @property {number} nameStart name start offset
1995
+ * @property {number} nameEnd name end offset
1996
+ * @property {string} unescapedName unescaped name
1997
+ * @property {ComponentValue[]} prelude rule prelude
1998
+ * @property {Declaration[] | null} declarations block declarations
1999
+ * @property {Rule[] | null} childRules block child rules
2000
+ * @property {number} blockStart `{` start offset
2001
+ * @property {number} blockEnd `}` end offset
2002
+ * @property {boolean} important `!important` flag
2003
+ * @property {SimpleBlockToken} token simple-block opening char
2004
+ * @property {Rule[]} rules stylesheet rules
2005
+ */
2006
+
2007
+ /**
2008
+ * @typedef {object} NodeStore
2009
+ * @property {string} input source
2010
+ * @property {LocConverter} lc loc converter
2011
+ * @property {Uint8Array} types node-type column
2012
+ * @property {Int32Array} starts start-offset column
2013
+ * @property {Int32Array} ends end-offset column
2014
+ * @property {Int32Array} aux0 aux slot 0
2015
+ * @property {Int32Array} aux1 aux slot 1
2016
+ * @property {Uint8Array} flags flags column
2017
+ * @property {Int32Array} listStarts content-span start column
2018
+ * @property {Int32Array} listLens content-span length column
2019
+ * @property {Int32Array} flat flat node-ref buffer
2020
+ * @property {(Node[] | null)[]} declLists per-node declaration lists
2021
+ * @property {(Node[] | null)[]} childLists per-node child-rule lists
2022
+ */
2023
+
2024
+ // Shared frozen empty list for a block body with no declarations / child rules,
2025
+ // so equal-empty reads return one reference (mirrors `_EMPTY_LIST`).
2026
+ const _READER_EMPTY = /** @type {Rule[]} */ (
2027
+ /** @type {unknown} */ (Object.freeze([]))
2028
+ );
2029
+
2030
+ /**
2031
+ * Raw token value over a snapshot (the lazy `Token.value` form): hash / at-keyword
2032
+ * drop their one-char prefix, url uses its content range.
2033
+ * @param {NodeStore} store snapshot
2034
+ * @param {number} i node id
2035
+ * @returns {string} raw token value
2036
+ */
2037
+ const _storeValueOf = (store, i) => {
2038
+ const ty = store.types[i];
2039
+ if (ty === T_HASH || ty === T_AT_KEYWORD) {
2040
+ return store.input.slice(store.starts[i] + 1, store.ends[i]);
2041
+ }
2042
+ if (ty === T_URL) return store.input.slice(store.aux0[i], store.aux1[i]);
2043
+ return store.input.slice(store.starts[i], store.ends[i]);
2044
+ };
2045
+
2046
+ /**
2047
+ * @param {NodeStore} store snapshot
2048
+ * @param {number} i node id
2049
+ * @returns {Node} reader over node `i`
2050
+ */
2051
+ const _readerAt = (store, i) => {
2052
+ const o = Object.create(NODE_READER_PROTO);
2053
+ o._store = store;
2054
+ o._i = i;
2055
+ return /** @type {Node} */ (o);
2056
+ };
2057
+
2058
+ /**
2059
+ * Readers over a container's flat content span (value / prelude / rules).
2060
+ * @param {NodeStore} store snapshot
2061
+ * @param {number} i container id
2062
+ * @returns {Node[]} child readers
2063
+ */
2064
+ const _readList = (store, i) => {
2065
+ const s = store.listStarts[i];
2066
+ const len = store.listLens[i];
2067
+ const flat = store.flat;
2068
+ /** @type {Node[]} */
2069
+ const out = [];
2070
+ for (let k = 0; k < len; k++) out.push(_readerAt(store, flat[s + k]));
2071
+ return out;
2072
+ };
2073
+
2074
+ /**
2075
+ * Readers over a node-id list (declarations / child rules).
2076
+ * @param {NodeStore} store snapshot
2077
+ * @param {Node[]} list node-id list
2078
+ * @returns {Node[]} child readers
2079
+ */
2080
+ const _readRefList = (store, list) => {
2081
+ /** @type {Node[]} */
2082
+ const out = [];
2083
+ for (let k = 0; k < list.length; k++) {
2084
+ out.push(_readerAt(store, _nodeIndex(list[k])));
2085
+ }
2086
+ return out;
2087
+ };
2088
+
2089
+ // Module-level reader prototype shared by every `parseA*` reader. Getters index
2090
+ // the reader's own `_store` snapshot by its `_i` node id; because the prototype is
2091
+ // created once (not per store), all readers share one hidden map and stay
2092
+ // monomorphic across parses.
2093
+ const NODE_READER_PROTO = /** @type {NodeReader} */ ({
2094
+ _store: /** @type {NodeStore} */ (/** @type {unknown} */ (null)),
2095
+ _i: 0,
2096
+ get type() {
2097
+ return this._store.types[this._i];
2098
+ },
2099
+ get start() {
2100
+ return this._store.starts[this._i];
2101
+ },
2102
+ get end() {
2103
+ return this._store.ends[this._i];
2104
+ },
2105
+ get range() {
2106
+ const store = this._store;
2107
+ const i = this._i;
2108
+ return /** @type {[number, number]} */ ([store.starts[i], store.ends[i]]);
2109
+ },
2110
+ get loc() {
2111
+ const store = this._store;
2112
+ const i = this._i;
2113
+ const lc = store.lc;
2114
+ // `LocConverter#get` mutates and returns itself, so snapshot the first.
2115
+ const s = lc.get(store.starts[i]);
2116
+ const sl = s.line;
2117
+ const sc = s.column;
2118
+ const e = lc.get(store.ends[i]);
2119
+ return {
2120
+ start: { line: sl, column: sc },
2121
+ end: { line: e.line, column: e.column }
2122
+ };
2123
+ },
2124
+ toString() {
2125
+ const store = this._store;
2126
+ const i = this._i;
2127
+ return store.input.slice(store.starts[i], store.ends[i]);
2128
+ },
2129
+ get value() {
2130
+ const store = this._store;
2131
+ const i = this._i;
2132
+ const ty = store.types[i];
2133
+ // function / simple-block / declaration expose their component-value
2134
+ // list; every leaf token exposes its raw string value.
2135
+ return ty === T_FUNCTION || ty === T_SIMPLE_BLOCK || ty === T_DECLARATION
2136
+ ? /** @type {ComponentValue[]} */ (_readList(store, i))
2137
+ : _storeValueOf(store, i);
2138
+ },
2139
+ get unescaped() {
2140
+ const store = this._store;
2141
+ const i = this._i;
2142
+ const v = _storeValueOf(store, i);
2143
+ return store.types[i] === T_STRING
2144
+ ? unescapeIdentifier(v.slice(1, -1))
2145
+ : unescapeIdentifier(v);
2146
+ },
2147
+ get numericValue() {
2148
+ const store = this._store;
2149
+ const i = this._i;
2150
+ const v = _storeValueOf(store, i);
2151
+ if (store.types[i] === T_DIMENSION) {
2152
+ return Number(v.slice(0, _consumeANumber(v, 0)));
2153
+ }
2154
+ if (store.types[i] === T_PERCENTAGE) return Number(v.slice(0, -1));
2155
+ return Number(v);
2156
+ },
2157
+ get typeFlag() {
2158
+ const store = this._store;
2159
+ const i = this._i;
2160
+ if (store.types[i] === T_HASH) {
2161
+ const input = store.input;
2162
+ const p = store.starts[i] + 1;
2163
+ return _ifThreeCodePointsWouldStartAnIdentSequence(
2164
+ input,
2165
+ p,
2166
+ input.charCodeAt(p),
2167
+ input.charCodeAt(p + 1),
2168
+ input.charCodeAt(p + 2)
2169
+ )
2170
+ ? "id"
2171
+ : "unrestricted";
2172
+ }
2173
+ const v = _storeValueOf(store, i);
2174
+ return _typeFlagOf(
2175
+ store.types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
2176
+ );
2177
+ },
2178
+ get sign() {
2179
+ return _signOf(_storeValueOf(this._store, this._i));
2180
+ },
2181
+ get unit() {
2182
+ const v = _storeValueOf(this._store, this._i);
2183
+ return v.slice(_consumeANumber(v, 0)).toLowerCase();
2184
+ },
2185
+ get contentStart() {
2186
+ return this._store.aux0[this._i];
2187
+ },
2188
+ get contentEnd() {
2189
+ return this._store.aux1[this._i];
2190
+ },
2191
+ get name() {
2192
+ const store = this._store;
2193
+ const i = this._i;
2194
+ // An at-rule's name skips its `@`; others start at the node.
2195
+ return store.types[i] === T_AT_RULE
2196
+ ? store.input.slice(store.starts[i] + 1, store.aux0[i])
2197
+ : store.input.slice(store.starts[i], store.aux0[i]);
2198
+ },
2199
+ get nameStart() {
2200
+ return this._store.starts[this._i];
2201
+ },
2202
+ get nameEnd() {
2203
+ return this._store.aux0[this._i];
2204
+ },
2205
+ get unescapedName() {
2206
+ return unescapeIdentifier(this.name);
2207
+ },
2208
+ get prelude() {
2209
+ return /** @type {ComponentValue[]} */ (_readList(this._store, this._i));
2210
+ },
2211
+ get declarations() {
2212
+ const store = this._store;
2213
+ // Non-rule containers never populate this slot (see `_makeContainer`), so a
2214
+ // missing entry (`undefined`) is normalized to `null` — same as a rule with
2215
+ // no block.
2216
+ const list = store.declLists[this._i] || null;
2217
+ return list === null
2218
+ ? null
2219
+ : /** @type {Declaration[]} */ (
2220
+ list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
2221
+ );
2222
+ },
2223
+ get childRules() {
2224
+ const store = this._store;
2225
+ const list = store.childLists[this._i] || null;
2226
+ return list === null
2227
+ ? null
2228
+ : /** @type {Rule[]} */ (
2229
+ list.length > 0 ? _readRefList(store, list) : _READER_EMPTY
2230
+ );
2231
+ },
2232
+ get blockStart() {
2233
+ return this._store.aux1[this._i];
2234
+ },
2235
+ get blockEnd() {
2236
+ const store = this._store;
2237
+ const i = this._i;
2238
+ return store.aux1[i] !== -1 ? store.ends[i] : -1;
2239
+ },
2240
+ get important() {
2241
+ return (this._store.flags[this._i] & 1) !== 0;
2242
+ },
2243
+ get token() {
2244
+ const store = this._store;
2245
+ return /** @type {SimpleBlockToken} */ (store.input[store.starts[this._i]]);
2246
+ },
2247
+ get rules() {
2248
+ return /** @type {Rule[]} */ (_readList(this._store, this._i));
2249
+ }
2250
+ });
2251
+
2252
+ /**
2253
+ * Hand the module's live columns to a retained snapshot, then reset the
2254
+ * module columns to fresh empty arrays so the next parse starts clean and can't
2255
+ * mutate this store. Called once per `parseA*` after all consuming is done.
2256
+ * @returns {NodeStore} the retained snapshot
2257
+ */
2258
+ const _finishStore = () => {
2259
+ /** @type {NodeStore} */
2260
+ const store = {
2261
+ input: _input,
2262
+ lc: _locConverter,
2263
+ types: _types,
2264
+ starts: _starts,
2265
+ ends: _ends,
2266
+ aux0: _aux0,
2267
+ aux1: _aux1,
2268
+ flags: _flags,
2269
+ listStarts: _listStarts,
2270
+ listLens: _listLens,
2271
+ flat: _flat,
2272
+ declLists: _declarationLists,
2273
+ childLists: _childRuleLists
2274
+ };
2275
+ // Carry this parse's size forward as a one-shot grow hint so the next parse
2276
+ // allocates its columns once instead of re-doubling from scratch (node ids
2277
+ // are 1-based, so `+1`).
2278
+ _growHint = _nodeCount + 1;
2279
+ _flatGrowHint = _flatTop;
2280
+ // Reset module state — the columns now belong to `store`.
2281
+ _capacity = 0;
2282
+ _nodeCount = 0;
2283
+ _flatTop = 0;
2284
+ _types = new Uint8Array(0);
2285
+ _starts = new Int32Array(0);
2286
+ _ends = new Int32Array(0);
2287
+ _aux0 = new Int32Array(0);
2288
+ _aux1 = new Int32Array(0);
2289
+ _flags = new Uint8Array(0);
2290
+ _listStarts = new Int32Array(0);
2291
+ _listLens = new Int32Array(0);
2292
+ _flat = new Int32Array(0);
2293
+ _declarationLists = [];
2294
+ _childRuleLists = [];
2295
+ _listPool.length = 0;
2296
+ _input = "";
2297
+ _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
2298
+ return store;
2299
+ };
2300
+
2301
+ /**
2302
+ * Finish the parse and wrap one consumed node ref as a `parseA*` reader, keeping
2303
+ * the caller's node type.
2304
+ * @template {Node} T
2305
+ * @param {T} ref consumed node ref
2306
+ * @returns {T} reader over the retained snapshot
2307
+ */
2308
+ const _finishOne = (ref) =>
2309
+ /** @type {T} */ (_readerAt(_finishStore(), _nodeIndex(ref)));
2310
+
2095
2311
  /**
2096
2312
  * Parse a stylesheet, CSS Syntax Level 3
2097
2313
  * [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
2098
2314
  * @param {string | TokenStream} input source string or an existing token stream
2099
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2315
+ * @param {number=} pos start position (string input only)
2316
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2100
2317
  * @returns {Stylesheet} the parsed stylesheet
2101
2318
  */
2102
- const parseAStylesheet = (input, comment) => {
2319
+ const parseAStylesheet = (input, pos = 0, options = {}) => {
2103
2320
  // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
2104
2321
  // 2. Normalize input, and set input to the result.
2105
- const ts = normalizeIntoTokenStream(input, 0, comment);
2106
- useObjectBackend(ts.locConverter);
2322
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2323
+ _setupParse(ts.input, ts.locConverter);
2107
2324
  // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
2108
2325
  const start = ts.next().start;
2109
- const stylesheet = /** @type {Stylesheet} */ (_mkStylesheet(start));
2326
+ const stylesheet = _makeStylesheet(start);
2110
2327
  // 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
2111
2328
  _setRules(stylesheet, consumeAStylesheetsContents(ts));
2112
2329
  _setEnd(stylesheet, ts.next().start);
2113
2330
  // 5. Return the stylesheet.
2114
- return stylesheet;
2331
+ return /** @type {Stylesheet} */ (
2332
+ _readerAt(_finishStore(), _nodeIndex(stylesheet))
2333
+ );
2115
2334
  };
2116
2335
 
2117
2336
  /**
@@ -2121,15 +2340,18 @@ const parseAStylesheet = (input, comment) => {
2121
2340
  * declarations are parse errors (never produced) and top-level CDO (`<!--`) /
2122
2341
  * CDC (`-->`) tokens are discarded.
2123
2342
  * @param {string | TokenStream} input source string or an existing token stream
2124
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2343
+ * @param {number=} pos start position (string input only)
2344
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2125
2345
  * @returns {Rule[]} top-level rules
2126
2346
  */
2127
- const parseAStylesheetsContents = (input, comment) => {
2347
+ const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
2128
2348
  // 1. Normalize input, and set input to the result.
2129
- const ts = normalizeIntoTokenStream(input, 0, comment);
2130
- useObjectBackend(ts.locConverter);
2349
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2350
+ _setupParse(ts.input, ts.locConverter);
2131
2351
  // 2. Consume a stylesheet’s contents from input, and return the result.
2132
- return consumeAStylesheetsContents(ts);
2352
+ const rules = consumeAStylesheetsContents(ts);
2353
+ const store = _finishStore();
2354
+ return /** @type {Rule[]} */ (_readRefList(store, rules));
2133
2355
  };
2134
2356
 
2135
2357
  /**
@@ -2137,15 +2359,23 @@ const parseAStylesheetsContents = (input, comment) => {
2137
2359
  * [§5.3.6](https://drafts.csswg.org/css-syntax/#parse-block-contents).
2138
2360
  * @param {string | TokenStream} input source string or an existing token stream
2139
2361
  * @param {number=} pos start position (string input only; just past the opening `{`, or 0)
2140
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2362
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2141
2363
  * @returns {{ decls: Declaration[], rules: Rule[] }} block decls + rules
2142
2364
  */
2143
- const parseABlocksContents = (input, pos, comment) => {
2365
+ const parseABlocksContents = (input, pos = 0, options = {}) => {
2144
2366
  // 1. Normalize input, and set input to the result.
2145
- const ts = normalizeIntoTokenStream(input, pos, comment);
2146
- useObjectBackend(ts.locConverter);
2367
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2368
+ _setupParse(ts.input, ts.locConverter);
2147
2369
  // 2. Consume a block’s contents from input, and return the result.
2148
- return consumeABlocksContents(ts);
2370
+ const { decls, rules } = consumeABlocksContents(ts);
2371
+ const store = _finishStore();
2372
+ return {
2373
+ decls: /** @type {Declaration[]} */ (_readRefList(store, decls)),
2374
+ rules:
2375
+ rules.length > 0
2376
+ ? /** @type {Rule[]} */ (_readRefList(store, rules))
2377
+ : _READER_EMPTY
2378
+ };
2149
2379
  };
2150
2380
 
2151
2381
  /**
@@ -2155,15 +2385,15 @@ const parseABlocksContents = (input, pos, comment) => {
2155
2385
  * whitespace; `undefined` (syntax error) otherwise.
2156
2386
  * @param {string | TokenStream} input source string or an existing token stream
2157
2387
  * @param {number=} pos start position (string input only)
2158
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2388
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2159
2389
  * @returns {Rule | undefined} the parsed rule
2160
2390
  */
2161
- const parseARule = (input, pos, comment) => {
2391
+ const parseARule = (input, pos = 0, options = {}) => {
2162
2392
  // 1. Normalize input, and set input to the result.
2163
- const ts = normalizeIntoTokenStream(input, pos, comment);
2164
- useObjectBackend(ts.locConverter);
2393
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2394
+ _setupParse(ts.input, ts.locConverter);
2165
2395
  // 2. Discard whitespace from input.
2166
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2396
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2167
2397
  // 3. If the next token from input is an <EOF-token>, return a syntax error.
2168
2398
  // Otherwise, if the next token from input is an <at-keyword-token>, consume an at-rule from input, and let rule be the return value.
2169
2399
  // Otherwise, consume a qualified rule from input and let rule be the return value.
@@ -2176,9 +2406,9 @@ const parseARule = (input, pos, comment) => {
2176
2406
  : consumeAQualifiedRule(ts);
2177
2407
  if (!rule) return undefined;
2178
2408
  // 4. Discard whitespace from input.
2179
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2409
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2180
2410
  // 5. If the next token from input is an <EOF-token>, return rule. Otherwise, return a syntax error.
2181
- return ts.next().type === TT_EOF ? rule : undefined;
2411
+ return ts.next().type === TT_EOF ? _finishOne(rule) : undefined;
2182
2412
  };
2183
2413
 
2184
2414
  /**
@@ -2186,41 +2416,41 @@ const parseARule = (input, pos, comment) => {
2186
2416
  * [§5.3.8](https://drafts.csswg.org/css-syntax/#parse-declaration).
2187
2417
  * @param {string | TokenStream} input source string or an existing token stream
2188
2418
  * @param {number=} pos start position (string input only)
2189
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2419
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2190
2420
  * @returns {Declaration | undefined} the parsed declaration, or undefined
2191
2421
  */
2192
- const parseADeclaration = (input, pos, comment) => {
2422
+ const parseADeclaration = (input, pos = 0, options = {}) => {
2193
2423
  // 1. Normalize input, and set input to the result.
2194
- const ts = normalizeIntoTokenStream(input, pos, comment);
2195
- useObjectBackend(ts.locConverter);
2424
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2425
+ _setupParse(ts.input, ts.locConverter);
2196
2426
  // 2. Discard whitespace from input.
2197
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2427
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2198
2428
  // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
2199
- return consumeADeclaration(ts);
2429
+ const decl = consumeADeclaration(ts);
2430
+ return decl === undefined ? undefined : _finishOne(decl);
2200
2431
  };
2201
2432
 
2202
2433
  /**
2203
2434
  * Parse a component value, CSS Syntax Level 3 [§5.3.9](https://drafts.csswg.org/css-syntax/#parse-component-value) — strict entry point that consumes one value and returns `undefined` if non-whitespace input trails (use `consumeAComponentValue` for "one value, ignore the rest").
2204
2435
  * @param {string | TokenStream} input source string or an existing token stream
2205
2436
  * @param {number=} pos start position (string input only)
2206
- * @param {{ comment?: (input: string, start: number, end: number) => number }=} options optional comment-token callback (string input only)
2437
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2207
2438
  * @returns {ComponentValue | undefined} the parsed component value, or `undefined` on empty / trailing-garbage input
2208
2439
  */
2209
- const parseAComponentValue = (input, pos, options = {}) => {
2440
+ const parseAComponentValue = (input, pos = 0, options = {}) => {
2210
2441
  // 1. Normalize input, and set input to the result.
2211
2442
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2212
- useObjectBackend(ts.locConverter);
2443
+ _setupParse(ts.input, ts.locConverter);
2213
2444
  // 2. Discard whitespace from input.
2214
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2445
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2215
2446
  // 3. If input is empty, return a syntax error.
2216
2447
  if (ts.next().type === TT_EOF) return undefined;
2217
2448
  // 4. Consume a component value from input and let value be the return value.
2218
2449
  const result = consumeAComponentValue(ts);
2219
2450
  // 5. Discard whitespace from input.
2220
- while (ts.next().type === TT_WHITESPACE) ts.discard();
2451
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2221
2452
  // 6. If input is empty, return value. Otherwise, return a syntax error.
2222
- if (ts.next().type === TT_EOF) return result;
2223
- return undefined;
2453
+ return ts.next().type === TT_EOF ? _finishOne(result) : undefined;
2224
2454
  };
2225
2455
 
2226
2456
  /**
@@ -2228,44 +2458,52 @@ const parseAComponentValue = (input, pos, options = {}) => {
2228
2458
  * [§5.3.10](https://drafts.csswg.org/css-syntax/#parse-list-of-components).
2229
2459
  * @param {string | TokenStream} input source string or an existing token stream
2230
2460
  * @param {number=} pos start position (string input only)
2231
- * @param {ParseListOptions=} options comment callback
2461
+ * @param {ParseOptions=} options comment callback
2232
2462
  * @returns {ComponentValue[]} component values
2233
2463
  */
2234
- const parseAListOfComponentValues = (input, pos, options = {}) => {
2464
+ const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
2235
2465
  // 1. Normalize input, and set input to the result.
2236
2466
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2237
- useObjectBackend(ts.locConverter);
2467
+ _setupParse(ts.input, ts.locConverter);
2238
2468
  // 2. Consume a list of component values from input, and return the result.
2239
- return consumeAListOfComponentValues(ts);
2469
+ // (`null` needs `bailOnCurly`, which is not passed here.)
2470
+ const values = /** @type {Node[]} */ (consumeAListOfComponentValues(ts));
2471
+ const store = _finishStore();
2472
+ return /** @type {ComponentValue[]} */ (_readRefList(store, values));
2240
2473
  };
2241
2474
 
2242
2475
  /**
2243
2476
  * Parse a comma-separated list of component values, CSS Syntax Level 3 [§5.3.11](https://drafts.csswg.org/css-syntax/#parse-comma-list) — consumes one `<comma-token>`-stopped group of component values per iteration until EOF.
2244
2477
  * @param {string | TokenStream} input source string or an existing token stream
2245
2478
  * @param {number=} pos start position (string input only)
2246
- * @param {ParseListOptions=} options comment callback
2479
+ * @param {ParseOptions=} options comment callback
2247
2480
  * @returns {ComponentValue[][]} comma-separated groups of component values
2248
2481
  */
2249
2482
  const parseACommaSeparatedListOfComponentValues = (
2250
2483
  input,
2251
- pos,
2484
+ pos = 0,
2252
2485
  options = {}
2253
2486
  ) => {
2254
2487
  // 1. Normalize input, and set input to the result.
2255
2488
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2256
- useObjectBackend(ts.locConverter);
2489
+ _setupParse(ts.input, ts.locConverter);
2257
2490
  // 2. Let groups be an empty list.
2258
- /** @type {ComponentValue[][]} */
2491
+ /** @type {Node[][]} */
2259
2492
  const groups = [];
2260
2493
  // 3. While input is not empty:
2261
2494
  while (ts.next().type !== TT_EOF) {
2262
2495
  // 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
2263
- groups.push(consumeAListOfComponentValues(ts, TT_COMMA));
2496
+ groups.push(
2497
+ /** @type {Node[]} */ (consumeAListOfComponentValues(ts, TT_COMMA))
2498
+ );
2264
2499
  // 3.2 Discard a token from input.
2265
2500
  ts.discard();
2266
2501
  }
2267
- // 4. Return groups.
2268
- return groups;
2502
+ // 4. Return groups — wrap each group's refs against the retained snapshot.
2503
+ const store = _finishStore();
2504
+ return groups.map(
2505
+ (g) => /** @type {ComponentValue[]} */ (_readRefList(store, g))
2506
+ );
2269
2507
  };
2270
2508
 
2271
2509
  // === Parser algorithms (CSS Syntax Level 3 §5.4) ===
@@ -2335,18 +2573,14 @@ const consumeAnAtRule = (ts, nested = false) => {
2335
2573
  // Consume a token from input, and let rule be a new at-rule with its name set to the returned token’s value, its prelude initially set to an empty list, and no declarations or child rules.
2336
2574
  const head = ts.consume();
2337
2575
  const rule = /** @type {AtRule} */ (
2338
- _mkContainer(T_AT_RULE, head.start, head.end)
2576
+ _makeContainer(T_AT_RULE, head.start, head.end)
2339
2577
  );
2340
- _setName(
2341
- rule,
2342
- ts.input.slice(head.start + 1, head.end),
2343
- head.start,
2344
- head.end
2345
- );
2346
- const prelude = _list();
2347
- _setPrelude(rule, prelude);
2348
- // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2349
- // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
2578
+ _setName(rule, head.start, head.end);
2579
+ // Sealed (`_setPrelude`) at each return — the store consumes the
2580
+ // scratch array when sealing, so it must be complete by then.
2581
+ const prelude = _takeList();
2582
+ // declarations / childRules stay null (no block); the `;` / EOF / nested-`}`
2583
+ // forms set blockStart / blockEnd to -1 explicitly at their return below.
2350
2584
 
2351
2585
  // Like `consumeAQualifiedRule`: skip mode scans the prelude without
2352
2586
  // materializing it (url tokens / functions kept so `@import url(…)` still
@@ -2362,6 +2596,8 @@ const consumeAnAtRule = (ts, nested = false) => {
2362
2596
  // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
2363
2597
  if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
2364
2598
  ts.discard();
2599
+ _setPrelude(rule, prelude);
2600
+ _setBlock(rule, -1);
2365
2601
  _setEnd(rule, t.start);
2366
2602
  return rule;
2367
2603
  }
@@ -2370,6 +2606,8 @@ const consumeAnAtRule = (ts, nested = false) => {
2370
2606
  // Otherwise, consume a token and append the result to rule’s prelude.
2371
2607
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2372
2608
  if (nested) {
2609
+ _setPrelude(rule, prelude);
2610
+ _setBlock(rule, -1);
2373
2611
  _setEnd(rule, t.start);
2374
2612
  return rule;
2375
2613
  }
@@ -2380,9 +2618,10 @@ const consumeAnAtRule = (ts, nested = false) => {
2380
2618
  // <{-token>
2381
2619
  // Consume a block from input, and assign the result to rule's declarations and child rules.
2382
2620
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2621
+ _setPrelude(rule, prelude);
2383
2622
  const block = consumeABlock(ts);
2384
2623
  _setBody(rule, block.decls, block.rules);
2385
- _setBlock(rule, block.blockStart, block.blockEnd);
2624
+ _setBlock(rule, block.blockStart);
2386
2625
  _setEnd(rule, block.blockEnd);
2387
2626
  return rule;
2388
2627
  }
@@ -2392,7 +2631,10 @@ const consumeAnAtRule = (ts, nested = false) => {
2392
2631
  const node = consumeAComponentValue(ts, t);
2393
2632
  if (!skip) {
2394
2633
  prelude.push(node);
2395
- } else if (_nType(node) === T_FUNCTION || _nType(node) === T_URL) {
2634
+ } else if (
2635
+ _nodeTypeOf(node) === T_FUNCTION ||
2636
+ _nodeTypeOf(node) === T_URL
2637
+ ) {
2396
2638
  prelude.push(node);
2397
2639
  }
2398
2640
  }
@@ -2423,19 +2665,31 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2423
2665
  const start = ts.next().start;
2424
2666
  // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
2425
2667
  const rule = /** @type {QualifiedRule} */ (
2426
- _mkContainer(T_QUALIFIED_RULE, start, start)
2668
+ _makeContainer(T_QUALIFIED_RULE, start, start)
2427
2669
  );
2428
- const prelude = _list();
2429
- _setPrelude(rule, prelude);
2430
- // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2431
- // defaults (no block until a `{` is reached).
2670
+ // Sealed (`_setPrelude`) at the `{` exit — the only path that returns the
2671
+ // rule; the parse-error exits abandon the scratch unsealed. Allocated lazily
2672
+ // on first push: skip mode usually pushes nothing (empty selector prelude),
2673
+ // and `_makeContainer` already zeroed the rule's list length, so a null
2674
+ // prelude reads as empty in the walk.
2675
+ /** @type {Node[] | null} */
2676
+ let prelude = null;
2677
+ // A returned qualified rule always has a block (only the `{` exit returns a
2678
+ // rule), so `_setBlock` always runs — no blockStart / blockEnd default needed.
2432
2679
 
2433
2680
  // Skip mode leaves `prelude` empty (selector text is recovered from the
2434
2681
  // rule's byte range, not its nodes); `first`/`second` still track the first
2435
2682
  // two non-whitespace tokens the `--foo: {` disambiguation below needs.
2436
2683
  const skip = _skipSelectorPrelude;
2684
+ // Non-skip: first two non-whitespace prelude nodes (computed at the `{`).
2437
2685
  let first = /** @type {Node} */ (/** @type {unknown} */ (0));
2438
2686
  let second = /** @type {Node} */ (/** @type {unknown} */ (0));
2687
+ // Skip mode: the same two tokens tracked by token type + start, so a dropped
2688
+ // leaf selector token needs no materialized node just for the disambiguation.
2689
+ // `0` (no token type) = unset.
2690
+ let firstTT = 0;
2691
+ let firstStart = 0;
2692
+ let secondTT = 0;
2439
2693
 
2440
2694
  // Process input
2441
2695
  for (;;) {
@@ -2450,12 +2704,17 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2450
2704
  // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
2451
2705
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2452
2706
  if (nested) return undefined;
2453
- const node = consumeATokenAsNode(ts);
2454
2707
  if (skip) {
2455
- if (!first) first = node;
2456
- else if (!second) second = node;
2708
+ // Stray `}` is a non-ws token; record its type/start, drop the node.
2709
+ if (firstTT === 0) {
2710
+ firstTT = t.type;
2711
+ firstStart = t.start;
2712
+ } else if (secondTT === 0) {
2713
+ secondTT = t.type;
2714
+ }
2715
+ ts.advance();
2457
2716
  } else {
2458
- prelude.push(node);
2717
+ (prelude || (prelude = _takeList())).push(consumeATokenAsNode(ts));
2459
2718
  }
2460
2719
  continue;
2461
2720
  }
@@ -2466,34 +2725,45 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2466
2725
  // (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
2467
2726
  // Otherwise, consume a block from input, and let child rules be the result.
2468
2727
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2469
- if (!skip) {
2728
+ // `--foo: {` disambiguation: are the first two non-ws prelude tokens an
2729
+ // ident starting with `--` followed by a colon? Skip mode reads the
2730
+ // tracked token type/start; non-skip reads the materialized prelude.
2731
+ let dashedDeclaration;
2732
+ if (skip) {
2733
+ dashedDeclaration =
2734
+ firstTT === TT_IDENTIFIER &&
2735
+ ts.input.startsWith("--", firstStart) &&
2736
+ secondTT === TT_COLON;
2737
+ } else if (prelude === null) {
2738
+ dashedDeclaration = false;
2739
+ } else {
2470
2740
  let firstIdx = 0;
2471
2741
  /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2472
2742
  while (
2473
2743
  firstIdx < prelude.length &&
2474
- _nType(prelude[firstIdx]) === T_WHITESPACE
2744
+ _nodeTypeOf(prelude[firstIdx]) === T_WHITESPACE
2475
2745
  ) {
2476
2746
  firstIdx++;
2477
2747
  }
2478
2748
  let secondIdx = firstIdx + 1;
2479
2749
  while (
2480
2750
  secondIdx < prelude.length &&
2481
- _nType(prelude[secondIdx]) === T_WHITESPACE
2751
+ _nodeTypeOf(prelude[secondIdx]) === T_WHITESPACE
2482
2752
  ) {
2483
2753
  secondIdx++;
2484
2754
  }
2485
2755
  first = prelude[firstIdx];
2486
2756
  second = prelude[secondIdx];
2757
+ dashedDeclaration =
2758
+ first &&
2759
+ _nodeTypeOf(first) === T_IDENT &&
2760
+ // Test the source bytes directly — avoids forcing the lazy `value`
2761
+ // slice just to check the `--` custom-property prefix.
2762
+ ts.input.startsWith("--", _nodeStartOf(first)) &&
2763
+ second &&
2764
+ _nodeTypeOf(second) === T_COLON;
2487
2765
  }
2488
- if (
2489
- first &&
2490
- _nType(first) === T_IDENT &&
2491
- // Test the source bytes directly — avoids forcing the lazy `value`
2492
- // slice just to check the `--` custom-property prefix.
2493
- ts.input.startsWith("--", _nStart(first)) &&
2494
- second &&
2495
- _nType(second) === T_COLON
2496
- ) {
2766
+ if (dashedDeclaration) {
2497
2767
  /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
2498
2768
  if (nested) {
2499
2769
  consumeTheRemnantsOfABadDeclaration(ts, true);
@@ -2502,29 +2772,45 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2502
2772
  }
2503
2773
  return undefined;
2504
2774
  }
2775
+ if (prelude !== null) _setPrelude(rule, prelude);
2505
2776
  const block = consumeABlock(ts);
2506
2777
  _setBody(rule, block.decls, block.rules);
2507
- _setBlock(rule, block.blockStart, block.blockEnd);
2778
+ _setBlock(rule, block.blockStart);
2508
2779
  _setEnd(rule, block.blockEnd);
2509
2780
  return rule;
2510
2781
  }
2511
2782
 
2512
2783
  // anything else
2513
2784
  // Consume a component value from input and append the result to rule’s prelude.
2514
- const node = consumeAComponentValue(ts, t);
2515
2785
  if (skip) {
2516
- // Keep only url-bearing nodes (url tokens, or functions that may hold a
2517
- // url like `:unknown(url(x))`) so the url visitor still rewrites them;
2518
- // other selector tokens have no non-modules consumer, so drop them.
2519
- const ty = _nType(node);
2520
- if (ty === T_FUNCTION || ty === T_URL) prelude.push(node);
2786
+ const tt = t.type;
2521
2787
  // Track the first two non-whitespace tokens for the disambiguation above.
2522
- if (t.type !== TT_WHITESPACE) {
2523
- if (!first) first = node;
2524
- else if (!second) second = node;
2788
+ if (tt !== TT_WHITESPACE) {
2789
+ if (firstTT === 0) {
2790
+ firstTT = tt;
2791
+ firstStart = t.start;
2792
+ } else if (secondTT === 0) {
2793
+ secondTT = tt;
2794
+ }
2795
+ }
2796
+ // Only functions (which may hold a url like `:unknown(url(x))`) and the
2797
+ // `(` / `[` blocks that must be balanced are materialized; the url
2798
+ // visitor keeps url / function nodes. Every other selector leaf token has
2799
+ // no non-modules consumer — drop it without building a node.
2800
+ if (
2801
+ tt === TT_FUNCTION ||
2802
+ (tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET)
2803
+ ) {
2804
+ const node = consumeAComponentValue(ts, t);
2805
+ const ty = _nodeTypeOf(node);
2806
+ if (ty === T_FUNCTION || ty === T_URL) {
2807
+ (prelude || (prelude = _takeList())).push(node);
2808
+ }
2809
+ } else {
2810
+ ts.advance();
2525
2811
  }
2526
2812
  } else {
2527
- prelude.push(node);
2813
+ (prelude || (prelude = _takeList())).push(consumeAComponentValue(ts, t));
2528
2814
  }
2529
2815
  }
2530
2816
  };
@@ -2601,22 +2887,26 @@ const declarationStartLikely = (ts) => {
2601
2887
  const consumeABlocksContents = (ts, onNode) => {
2602
2888
  /** @type {Declaration[]} */
2603
2889
  const decls = [];
2604
- /** @type {Rule[]} */
2605
- const rules = [];
2890
+ // Child rules are the common empty case (most rules carry only declarations),
2891
+ // so `rules` is allocated lazily and returned as the shared frozen
2892
+ // `_EMPTY_LIST` when nothing was appended — one fewer array per rule. `decls`
2893
+ // stays eager so the hot declaration append keeps a branch-free `push`.
2894
+ /** @type {Rule[] | null} */
2895
+ let rules = null;
2606
2896
 
2607
2897
  // Process input:
2608
2898
  for (;;) {
2609
2899
  const t = ts.next();
2610
2900
 
2611
2901
  // <whitespace-token> / <semicolon-token>
2612
- // Discard a token from input.
2902
+ // Discard a token from input (`t` was just peeked and is non-EOF).
2613
2903
  if (t.type === TT_WHITESPACE || t.type === TT_SEMICOLON) {
2614
- ts.discard();
2904
+ ts.advance();
2615
2905
  }
2616
2906
  // <EOF-token> / <}-token>
2617
2907
  // Return decls and rules.
2618
2908
  else if (t.type === TT_EOF || t.type === TT_RIGHT_CURLY_BRACKET) {
2619
- return { decls, rules };
2909
+ return { decls, rules: rules || _EMPTY_LIST };
2620
2910
  }
2621
2911
  // <at-keyword-token>
2622
2912
  // Consume an at-rule from input, with nested set to true. If a rule was returned, append it to rules.
@@ -2624,7 +2914,7 @@ const consumeABlocksContents = (ts, onNode) => {
2624
2914
  const atRule = consumeAnAtRule(ts, true);
2625
2915
  if (atRule) {
2626
2916
  if (onNode) onNode(atRule);
2627
- else rules.push(atRule);
2917
+ else (rules || (rules = [])).push(atRule);
2628
2918
  }
2629
2919
  }
2630
2920
  // anything else
@@ -2647,7 +2937,7 @@ const consumeABlocksContents = (ts, onNode) => {
2647
2937
  const rule = consumeAQualifiedRule(ts, TT_SEMICOLON, true);
2648
2938
  if (rule) {
2649
2939
  if (onNode) onNode(rule);
2650
- else rules.push(rule);
2940
+ else (rules || (rules = [])).push(rule);
2651
2941
  }
2652
2942
  }
2653
2943
  }
@@ -2692,54 +2982,70 @@ const consumeADeclaration = (ts, nested = false) => {
2692
2982
  const { input } = ts;
2693
2983
  // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
2694
2984
  const start = ts.next().start;
2695
- // name "" / nameStart / nameEnd (= start) / important (false) keep their
2696
- // `Container` defaults; `value` is set unconditionally at step 5 below.
2985
+ // nameEnd (= start) / important (unset) keep their container defaults;
2986
+ // `value` is set unconditionally at step 5 below.
2697
2987
  const decl = /** @type {Declaration} */ (
2698
- _mkContainer(T_DECLARATION, start, start)
2988
+ _makeContainer(T_DECLARATION, start, start)
2699
2989
  );
2700
2990
 
2701
2991
  // 1. If the next token is an <ident-token>, consume a token from input and set decl's name to the returned token's value.
2702
2992
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2703
2993
  if (ts.next().type === TT_IDENTIFIER) {
2704
2994
  const head = ts.consume();
2705
- _setName(decl, input.slice(head.start, head.end), head.start, head.end);
2995
+ _setName(decl, head.start, head.end);
2706
2996
  } else {
2707
2997
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2708
2998
  return undefined;
2709
2999
  }
2710
3000
 
2711
3001
  // 2. Discard whitespace from input.
2712
- while (ts.next().type === TT_WHITESPACE) ts.discard();
3002
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
2713
3003
 
2714
3004
  // 3. If the next token is a <colon-token>, discard a token from input.
2715
3005
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2716
3006
  if (ts.next().type === TT_COLON) {
2717
- ts.discard();
3007
+ ts.advance();
2718
3008
  } else {
2719
3009
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2720
3010
  return undefined;
2721
3011
  }
2722
3012
 
2723
3013
  // 4. Discard whitespace from input.
2724
- while (ts.next().type === TT_WHITESPACE) ts.discard();
3014
+ while (ts.next().type === TT_WHITESPACE) ts.advance();
3015
+
3016
+ // Step 8's custom-property test, computed early so the value parse can bail.
3017
+ const isCustomProperty = input.startsWith("--", start);
2725
3018
 
2726
3019
  // 5. Consume a list of component values from input, with nested, and with <semicolon-token> as the stop token, and set decl's value to the result.
2727
- const value = consumeAListOfComponentValues(ts, TT_SEMICOLON, nested);
2728
- _setValue(decl, value);
3020
+ // A nested non-custom declaration bails on a top-level `{` — step 8 would
3021
+ // reject it and the caller restores its mark, so parsing the block (the
3022
+ // entire nested-rule body, re-parsed as a qualified rule after the
3023
+ // restore) would be pure waste.
3024
+ const value = consumeAListOfComponentValues(
3025
+ ts,
3026
+ TT_SEMICOLON,
3027
+ nested,
3028
+ nested && !isCustomProperty
3029
+ );
3030
+ if (value === null) return undefined;
3031
+ // `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
3032
+ // and the store consumes it when sealing.
2729
3033
  _setEnd(decl, ts.next().start);
2730
3034
 
2731
3035
  // 6. If the last two non-<whitespace-token>s in decl's value are a <delim-token> with the value "!" followed by an <ident-token> with a value that is an ASCII case-insensitive match for "important", remove them from decl's value and set decl's important flag.
2732
3036
  {
2733
3037
  let last = value.length - 1;
2734
- while (last >= 0 && _nType(value[last]) === T_WHITESPACE) last--;
3038
+ while (last >= 0 && _nodeTypeOf(value[last]) === T_WHITESPACE) last--;
2735
3039
  let prev = last - 1;
2736
- while (prev >= 0 && _nType(value[prev]) === T_WHITESPACE) prev--;
3040
+ while (prev >= 0 && _nodeTypeOf(value[prev]) === T_WHITESPACE) prev--;
3041
+ // `!` delim first: it's almost always absent, and `_nodeValueOf` allocates
3042
+ // a slice — this order pays it only for genuine `!important` candidates.
2737
3043
  if (
2738
3044
  prev >= 0 &&
2739
- _nType(value[last]) === T_IDENT &&
2740
- equalsLowerCase(_nValue(value[last]), "important") &&
2741
- _nType(value[prev]) === T_DELIM &&
2742
- input.charCodeAt(_nStart(value[prev])) === CC_EXCLAMATION
3045
+ _nodeTypeOf(value[prev]) === T_DELIM &&
3046
+ input.charCodeAt(_nodeStartOf(value[prev])) === CC_EXCLAMATION &&
3047
+ _nodeTypeOf(value[last]) === T_IDENT &&
3048
+ equalsLowerCase(_nodeValueOf(value[last]), "important")
2743
3049
  ) {
2744
3050
  _setImportant(decl);
2745
3051
  value.length = prev;
@@ -2747,7 +3053,10 @@ const consumeADeclaration = (ts, nested = false) => {
2747
3053
  }
2748
3054
 
2749
3055
  // 7. While the last item in decl's value is a <whitespace-token>, remove that token.
2750
- while (value.length > 0 && _nType(value[value.length - 1]) === T_WHITESPACE) {
3056
+ while (
3057
+ value.length > 0 &&
3058
+ _nodeTypeOf(value[value.length - 1]) === T_WHITESPACE
3059
+ ) {
2751
3060
  value.pop();
2752
3061
  }
2753
3062
 
@@ -2755,17 +3064,17 @@ const consumeADeclaration = (ts, nested = false) => {
2755
3064
  // Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, return nothing.
2756
3065
  // (That is, a top-level {}-block is only allowed as the entire value of a non-custom property — for CSS Nesting, `consumeABlocksContents`'s `mark` / `restore a mark` will retry the input as a qualified rule.)
2757
3066
  // Otherwise, accept the declaration. (The spec also checks "contains any non-whitespace-tokens at the top level" → return nothing; we keep empty-value declarations because callers — e.g. `@value name:;` — rely on them.)
2758
- const isCustomProperty = input.startsWith("--", start);
2759
3067
  if (!isCustomProperty) {
2760
3068
  for (let i = 0; i < value.length; i++) {
2761
3069
  const v = value[i];
2762
- if (_nType(v) === T_SIMPLE_BLOCK && _nToken(v) === "{") {
3070
+ if (_nodeTypeOf(v) === T_SIMPLE_BLOCK && _nodeTokenOf(v) === "{") {
2763
3071
  return undefined;
2764
3072
  }
2765
3073
  }
2766
3074
  }
2767
3075
 
2768
3076
  // 9. Return decl.
3077
+ _setValue(decl, value);
2769
3078
  return decl;
2770
3079
  };
2771
3080
 
@@ -2774,11 +3083,16 @@ const consumeADeclaration = (ts, nested = false) => {
2774
3083
  * @param {TokenStream} ts token stream
2775
3084
  * @param {number=} stopToken token type that terminates the list (left unconsumed)
2776
3085
  * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the list (left unconsumed)
2777
- * @returns {ComponentValue[]} consumed component values
3086
+ * @param {boolean=} bailOnCurly abort with `null` on a top-level `{` (left unconsumed) — for callers that would reject the list anyway (consume-a-declaration step 8) and restore a mark
3087
+ * @returns {ComponentValue[] | null} consumed component values, or `null` when `bailOnCurly` hit
2778
3088
  */
2779
- const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2780
- /** @type {ComponentValue[]} */
2781
- const values = [];
3089
+ const consumeAListOfComponentValues = (
3090
+ ts,
3091
+ stopToken,
3092
+ nested = false,
3093
+ bailOnCurly = false
3094
+ ) => {
3095
+ const values = /** @type {ComponentValue[]} */ (_takeList());
2782
3096
  // Process input
2783
3097
  for (;;) {
2784
3098
  const t = ts.next();
@@ -2797,12 +3111,17 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2797
3111
  const closer = consumeATokenAsNode(ts);
2798
3112
  // Keep unless the type is explicitly marked skip (1); an out-of-range
2799
3113
  // lookup on a short `skip.types` yields `undefined`, which must not drop.
2800
- if (!_skipActive || _skipTypes[_nType(closer)] !== 1) values.push(closer);
3114
+ if (!_skipActive || _skipTypes[_nodeTypeOf(closer)] !== 1) {
3115
+ values.push(closer);
3116
+ }
2801
3117
  continue;
2802
3118
  }
3119
+ // A top-level `{` dooms the list for a bailing caller — stop before the
3120
+ // whole block is parsed only to be thrown away on the caller's restore.
3121
+ if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) return null;
2803
3122
  // anything else
2804
3123
  // Consume a component value from input, and append the result to values.
2805
- // Skipped leaf types short-circuit before materializing: no SoA slot is
3124
+ // Skipped leaf types short-circuit before materializing: no column slot is
2806
3125
  // written and no node is built (blocks / functions never skip here).
2807
3126
  const tt = t.type;
2808
3127
  if (
@@ -2811,11 +3130,12 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2811
3130
  !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
2812
3131
  _skipTypes[_ttToNodeType[tt]] === 1
2813
3132
  ) {
2814
- ts.consume();
3133
+ // `t` was just peeked and is a skipped value leaf (never EOF).
3134
+ ts.advance();
2815
3135
  continue;
2816
3136
  }
2817
3137
  const node = consumeAComponentValue(ts, t);
2818
- if (!_skipActive || _skipTypes[_nType(node)] !== 1) values.push(node);
3138
+ if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) values.push(node);
2819
3139
  }
2820
3140
  };
2821
3141
 
@@ -2840,10 +3160,11 @@ const consumeAComponentValue = (ts, t = ts.next()) => {
2840
3160
  }
2841
3161
  // anything else
2842
3162
  // Consume a token from input and return the result. (Asserted: not EOF.)
2843
- // Inlined `consumeATokenAsNode`: `t` is already the peeked next token, so
2844
- // advance past it and materialize it directly — one fewer call per leaf
2845
- // component value (the bulk of the nodes on a large stylesheet).
2846
- ts.consume();
3163
+ // Inlined `consumeATokenAsNode`: `t` is already the peeked next token (and not
3164
+ // EOF), so `advance` past it and materialize it directly — no redundant
3165
+ // `next()` and one fewer call per leaf component value (the bulk of the nodes
3166
+ // on a large stylesheet).
3167
+ ts.advance();
2847
3168
  return /** @type {ComponentValue} */ (tokenToNode(t));
2848
3169
  };
2849
3170
 
@@ -2861,11 +3182,11 @@ const consumeASimpleBlock = (ts) => {
2861
3182
 
2862
3183
  // Let block be a new simple block with its associated token set to the next token and with its value initially set to an empty list.
2863
3184
  const block = /** @type {SimpleBlock} */ (
2864
- _mkContainer(T_SIMPLE_BLOCK, open.start, open.end)
3185
+ _makeContainer(T_SIMPLE_BLOCK, open.start, open.end)
2865
3186
  );
2866
3187
  _setToken(block, token);
2867
- const val = _list();
2868
- _setValue(block, val);
3188
+ // Sealed (`_setValue`) at the return, once complete.
3189
+ const val = _takeList();
2869
3190
 
2870
3191
  // Discard a token from input.
2871
3192
  ts.discard();
@@ -2879,6 +3200,7 @@ const consumeASimpleBlock = (ts) => {
2879
3200
  // Discard a token from input. Return block.
2880
3201
  if (t.type === TT_EOF || t.type === ending) {
2881
3202
  ts.discard();
3203
+ _setValue(block, val);
2882
3204
  _setEnd(block, t.end);
2883
3205
  return block;
2884
3206
  }
@@ -2895,16 +3217,15 @@ const consumeASimpleBlock = (ts) => {
2895
3217
  * @returns {FunctionNode | undefined} the consumed function node
2896
3218
  */
2897
3219
  const consumeAFunction = (ts) => {
2898
- const { input } = ts;
2899
3220
  // Assert (spec): the next token is a <function-token>.
2900
3221
  // Consume a token from input, and let function be a new function with its name equal the returned token’s value, and a value set to an empty list.
2901
3222
  const tFn = ts.consume();
2902
3223
  const fn = /** @type {FunctionNode} */ (
2903
- _mkContainer(T_FUNCTION, tFn.start, tFn.end)
3224
+ _makeContainer(T_FUNCTION, tFn.start, tFn.end)
2904
3225
  );
2905
- _setName(fn, input.slice(tFn.start, tFn.end - 1), tFn.start, tFn.end - 1);
2906
- const val = _list();
2907
- _setValue(fn, val);
3226
+ _setName(fn, tFn.start, tFn.end - 1);
3227
+ // Sealed (`_setValue`) at the return, once complete.
3228
+ const val = _takeList();
2908
3229
 
2909
3230
  // Process input
2910
3231
  for (;;) {
@@ -2915,6 +3236,7 @@ const consumeAFunction = (ts) => {
2915
3236
  // <)-token>
2916
3237
  // Discard a token from input. Return function.
2917
3238
  ts.discard();
3239
+ _setValue(fn, val);
2918
3240
  _setEnd(fn, t.end);
2919
3241
  return fn;
2920
3242
  }
@@ -2929,11 +3251,12 @@ const consumeAFunction = (ts) => {
2929
3251
  !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
2930
3252
  _skipTypes[_ttToNodeType[tt]] === 1
2931
3253
  ) {
2932
- ts.consume();
3254
+ // `t` was just peeked and is a skipped value leaf (never EOF).
3255
+ ts.advance();
2933
3256
  continue;
2934
3257
  }
2935
3258
  const node = consumeAComponentValue(ts, t);
2936
- if (!_skipActive || _skipTypes[_nType(node)] !== 1) val.push(node);
3259
+ if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) val.push(node);
2937
3260
  }
2938
3261
  };
2939
3262
 
@@ -2991,13 +3314,18 @@ const _escapeIdentifier = (str) => {
2991
3314
  }
2992
3315
  output = lastFlush === 0 ? str : output + str.slice(lastFlush);
2993
3316
 
2994
- const firstChar = str.charAt(0);
2995
-
2996
- if (/^-[-\d]/.test(output)) {
3317
+ // `-` and digits are class 0 (never escaped above), so testing `str`'s lead
3318
+ // char codes is equivalent to regexes over `output` — and keeps the common
3319
+ // nothing-to-do call regex-free.
3320
+ const first = str.charCodeAt(0);
3321
+ if (
3322
+ first === CC_HYPHEN_MINUS &&
3323
+ (str.charCodeAt(1) === CC_HYPHEN_MINUS || _isDigit(str.charCodeAt(1)))
3324
+ ) {
2997
3325
  output = `\\-${output.slice(1)}`;
2998
- } else if (/\d/.test(firstChar)) {
3326
+ } else if (_isDigit(first)) {
2999
3327
  // A leading digit becomes `\3<digit> `, another `\HEX ` run to clean up.
3000
- output = `\\3${firstChar} ${output.slice(1)}`;
3328
+ output = `\\3${str.charAt(0)} ${output.slice(1)}`;
3001
3329
  needSpaceFix = true;
3002
3330
  }
3003
3331
 
@@ -3031,7 +3359,6 @@ const _escapeIdentifier = (str) => {
3031
3359
  */
3032
3360
  const gobbleHex = (str, start) => {
3033
3361
  let hex = "";
3034
- let spaceTerminated = false;
3035
3362
 
3036
3363
  for (let i = 0; i < 6; i++) {
3037
3364
  const code = str.charCodeAt(start + i);
@@ -3040,8 +3367,6 @@ const gobbleHex = (str, start) => {
3040
3367
  (code >= 48 && code <= 57) ||
3041
3368
  (code >= 65 && code <= 70) ||
3042
3369
  (code >= 97 && code <= 102);
3043
- // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
3044
- spaceTerminated = code === 32;
3045
3370
  if (!valid) break;
3046
3371
  // parseInt below is case-insensitive, so keep the original char.
3047
3372
  hex += str[start + i];
@@ -3049,6 +3374,16 @@ const gobbleHex = (str, start) => {
3049
3374
 
3050
3375
  if (hex.length === 0) return undefined;
3051
3376
 
3377
+ // One trailing whitespace terminates the escape, matching the tokenizer's
3378
+ // `_consumeAnEscapedCodePoint` — including after a full 6-digit escape, for
3379
+ // any CSS whitespace (not just space), plus the extra LF of a CRLF pair.
3380
+ // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
3381
+ let consumed = hex.length;
3382
+ const trail = str.charCodeAt(start + hex.length);
3383
+ if (_isWhiteSpace(trail)) {
3384
+ consumed = consumeExtraNewline(trail, str, start + hex.length + 1) - start;
3385
+ }
3386
+
3052
3387
  const codePoint = Number.parseInt(hex, 16);
3053
3388
  const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
3054
3389
 
@@ -3056,13 +3391,10 @@ const gobbleHex = (str, start) => {
3056
3391
  // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
3057
3392
  // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
3058
3393
  if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
3059
- return ["�", hex.length + (spaceTerminated ? 1 : 0)];
3394
+ return ["�", consumed];
3060
3395
  }
3061
3396
 
3062
- return [
3063
- String.fromCodePoint(codePoint),
3064
- hex.length + (spaceTerminated ? 1 : 0)
3065
- ];
3397
+ return [String.fromCodePoint(codePoint), consumed];
3066
3398
  };
3067
3399
 
3068
3400
  /**
@@ -3108,6 +3440,67 @@ const _unescapeIdentifier = (str) => {
3108
3440
  const escapeIdentifier = makeCacheable(_escapeIdentifier);
3109
3441
  const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
3110
3442
 
3443
+ // A url-token / url-string value's escaped newlines (`url("im\<newline>g.png")`).
3444
+ const STRING_MULTILINE = /\\[\n\r\f]/g;
3445
+ // Leading / trailing CSS whitespace inside a quoted url value.
3446
+ const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
3447
+ // One CSS escape: `\` + up to 6 hex digits (+ optional whitespace) or any char.
3448
+ const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
3449
+
3450
+ /**
3451
+ * Normalize a url value (a url-token's content or a url string's body) into
3452
+ * the form requests are resolved from: escaped newlines removed (string form),
3453
+ * edge whitespace trimmed, CSS escapes and percent-encoding decoded
3454
+ * (`data:` URIs excepted).
3455
+ * @param {string} str url string
3456
+ * @param {boolean} isString is url wrapped in quotes
3457
+ * @returns {string} normalized url
3458
+ */
3459
+ const normalizeUrl = (str, isString) => {
3460
+ // Fast paths: skip the regex engine for the common URL with no escape and
3461
+ // no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
3462
+ // regex being a no-op.
3463
+ // Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
3464
+ if (isString && str.includes("\\")) {
3465
+ str = str.replace(STRING_MULTILINE, "");
3466
+ }
3467
+
3468
+ // Remove unnecessary spaces from `url(" img.png ")`
3469
+ if (
3470
+ str.length !== 0 &&
3471
+ (_isWhiteSpace(str.charCodeAt(0)) ||
3472
+ _isWhiteSpace(str.charCodeAt(str.length - 1)))
3473
+ ) {
3474
+ str = str.replace(TRIM_WHITE_SPACES, "");
3475
+ }
3476
+
3477
+ // Unescape
3478
+ if (str.includes("\\")) {
3479
+ str = str.replace(UNESCAPE, (match) => {
3480
+ if (match.length > 2) {
3481
+ return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
3482
+ }
3483
+ return match[1];
3484
+ });
3485
+ }
3486
+
3487
+ // Char-code gate so the dominant non-`data:` url skips the regex test.
3488
+ if ((str.charCodeAt(0) | 0x20) === CC_LOWER_D && /^data:/i.test(str)) {
3489
+ return str;
3490
+ }
3491
+
3492
+ if (str.includes("%")) {
3493
+ // Convert `url('%2E/img.png')` -> `url('./img.png')`
3494
+ try {
3495
+ str = decodeURIComponent(str);
3496
+ } catch (_err) {
3497
+ // Ignore
3498
+ }
3499
+ }
3500
+
3501
+ return str;
3502
+ };
3503
+
3111
3504
  // CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
3112
3505
  // re-exported so consumers keep importing them from this module.
3113
3506
  /**
@@ -3151,6 +3544,148 @@ const TOP_LEVEL_CONSUMERS = {
3151
3544
  * @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
3152
3545
  */
3153
3546
 
3547
+ // Per-parse walk state in module slots (same pattern as `_skip*`) so the walk
3548
+ // functions below are module-level constants: one function identity across
3549
+ // parses keeps the recursive per-node call sites monomorphic and drops the
3550
+ // per-parse closure allocations.
3551
+ /** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
3552
+ /** @type {CompiledVisitorMap} */
3553
+ let _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3554
+ let _recurseBlocks = true;
3555
+ /** @type {CompiledVisitorBucket | undefined} */
3556
+ let _commentBucket;
3557
+
3558
+ // Comments reach the visitor map through `NodeType.Comment` instead of a
3559
+ // side callback. They fire during tokenization — in source order among
3560
+ // comments, not interleaved with the node walk — on a transient store node so
3561
+ // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3562
+ // the tokenizer skips comments with zero overhead.
3563
+ /** @type {(input: string, start: number, end: number) => number} */
3564
+ const _grammarOnComment = (_src, start, end) => {
3565
+ const node = _makeLeaf(T_COMMENT, start, end);
3566
+ _currentNode = node;
3567
+ _currentParent = null;
3568
+ _currentIndex = 0;
3569
+ const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
3570
+ const e = bucket.enter;
3571
+ for (let i = 0; i < e.length; i++) e[i](A);
3572
+ const x = bucket.exit;
3573
+ for (let i = 0; i < x.length; i++) x[i](A);
3574
+ return end;
3575
+ };
3576
+
3577
+ /**
3578
+ * Walk a component-value subtree; children are already materialized. Fetches
3579
+ * the node's visitor bucket once (reused for enter + exit) and uses index
3580
+ * loops — `for…of` would allocate an iterator per node on this hot path.
3581
+ * @param {Node} node component-value root
3582
+ * @param {Node | null} parent enclosing node
3583
+ * @param {number} index node's index within its sibling list
3584
+ */
3585
+ const _walkValue = (node, parent, index) => {
3586
+ const ty = _types[_nodeIndex(node)];
3587
+ const b = _visitors[ty];
3588
+ let skip = false;
3589
+ if (b !== undefined && b.enter.length !== 0) {
3590
+ _walkSkip = false;
3591
+ _currentNode = node;
3592
+ _currentParent = parent;
3593
+ _currentIndex = index;
3594
+ const e = b.enter;
3595
+ for (let i = 0; i < e.length; i++) e[i](A);
3596
+ skip = _walkSkip;
3597
+ _walkSkip = false;
3598
+ }
3599
+ if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3600
+ const i0 = _nodeIndex(node);
3601
+ const vs = _listStarts[i0];
3602
+ const ve = vs + _listLens[i0];
3603
+ for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_flat[i]), node, i - vs);
3604
+ }
3605
+ if (b !== undefined) {
3606
+ // Rebind: descending into children moved the path.
3607
+ _currentNode = node;
3608
+ _currentParent = parent;
3609
+ _currentIndex = index;
3610
+ const x = b.exit;
3611
+ for (let i = 0; i < x.length; i++) x[i](A);
3612
+ }
3613
+ };
3614
+
3615
+ /**
3616
+ * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
3617
+ * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3618
+ * @param {Node} node structural-tree root
3619
+ * @param {Node | null} parent enclosing node
3620
+ * @param {number} index node's index within its sibling list (declarations and child rules index independently)
3621
+ */
3622
+ const _walkRule = (node, parent, index) => {
3623
+ const i0 = _nodeIndex(node);
3624
+ const ty = _types[i0];
3625
+ const b = _visitors[ty];
3626
+ let skip = false;
3627
+ if (b !== undefined && b.enter.length !== 0) {
3628
+ _walkSkip = false;
3629
+ _currentNode = node;
3630
+ _currentParent = parent;
3631
+ _currentIndex = index;
3632
+ const e = b.enter;
3633
+ for (let i = 0; i < e.length; i++) e[i](A);
3634
+ skip = _walkSkip;
3635
+ _walkSkip = false;
3636
+ }
3637
+ if (!skip) {
3638
+ if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3639
+ const ps = _listStarts[i0];
3640
+ const pe = ps + _listLens[i0];
3641
+ for (let i = ps; i < pe; i++) {
3642
+ _walkValue(_nodeRef(_flat[i]), node, i - ps);
3643
+ }
3644
+ if (_recurseBlocks) {
3645
+ // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3646
+ const decls = _declarationLists[i0];
3647
+ if (decls) {
3648
+ for (let i = 0; i < decls.length; i++) _walkRule(decls[i], node, i);
3649
+ }
3650
+ const ch = _childRuleLists[i0];
3651
+ if (ch) for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node, i);
3652
+ }
3653
+ } else if (ty === T_DECLARATION) {
3654
+ const vs = _listStarts[i0];
3655
+ const ve = vs + _listLens[i0];
3656
+ for (let i = vs; i < ve; i++) {
3657
+ _walkValue(_nodeRef(_flat[i]), node, i - vs);
3658
+ }
3659
+ }
3660
+ }
3661
+ if (b !== undefined) {
3662
+ // Rebind: descending into children moved the path.
3663
+ _currentNode = node;
3664
+ _currentParent = parent;
3665
+ _currentIndex = index;
3666
+ const x = b.exit;
3667
+ for (let i = 0; i < x.length; i++) x[i](A);
3668
+ }
3669
+ };
3670
+
3671
+ /**
3672
+ * The `grammar` streaming sink: walk one top-level node, then recycle the
3673
+ * buffers for the next.
3674
+ * @param {Rule | Declaration} node top-level node
3675
+ */
3676
+ const _walkTopLevel = (node) => {
3677
+ _walkRule(node, null, 0);
3678
+ if (_nodeCount > _peak) _peak = _nodeCount;
3679
+ if (_flatTop > _flatPeak) _flatPeak = _flatTop;
3680
+ _nodeCount = 0;
3681
+ _flatTop = 0;
3682
+ };
3683
+
3684
+ // The store buffers grow to the largest single top-level rule ever parsed and
3685
+ // live at module level; above this capacity they are re-shrunk after a parse
3686
+ // so one pathological rule can't pin megabytes for the process lifetime.
3687
+ const _SHRINK_CAPACITY = 65536;
3688
+
3154
3689
  /**
3155
3690
  * The CSS `SourceProcessor` grammar: consume top-level rules one at a time
3156
3691
  * (§5.4.1) and walk each immediately, firing `enter` / `exit` in source order
@@ -3163,136 +3698,61 @@ const TOP_LEVEL_CONSUMERS = {
3163
3698
  */
3164
3699
  const grammar = (input, visitors, options) => {
3165
3700
  const locConverter = options.locConverter || new LocConverter(input);
3166
- useSoaBackend(input, locConverter);
3701
+ _setupParse(input, locConverter);
3167
3702
  const skip = options.skip;
3168
3703
  _skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
3169
3704
  _skipActive = _skipTypes !== _NO_SKIP_TYPES;
3170
3705
  _skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
3171
3706
  _skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
3172
- const recurseBlocks = options.recurseBlocks !== false;
3173
-
3174
- // Comments reach the visitor map through `NodeType.Comment` instead of a
3175
- // side callback. They fire during tokenization — in source order among
3176
- // comments, not interleaved with the node walk — on a transient SoA node so
3177
- // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3178
- // the tokenizer skips comments with zero overhead.
3179
- const commentBucket = visitors[T_COMMENT];
3180
- const onComment =
3181
- commentBucket === undefined
3182
- ? undefined
3183
- : /** @type {(input: string, start: number, end: number) => number} */ (
3184
- (_input, start, end) => {
3185
- const node = _soaAlloc(T_COMMENT, start, end);
3186
- _curNode = node;
3187
- _curParent = null;
3188
- const e = commentBucket.enter;
3189
- for (let i = 0; i < e.length; i++) e[i](A);
3190
- const x = commentBucket.exit;
3191
- for (let i = 0; i < x.length; i++) x[i](A);
3192
- return end;
3193
- }
3194
- );
3195
-
3196
- /**
3197
- * Walk a component-value subtree; children are already materialized. Fetches
3198
- * the node's visitor bucket once (reused for enter + exit) and uses index
3199
- * loops — `for…of` would allocate an iterator per node on this hot path.
3200
- * @param {Node} node component-value root
3201
- * @param {Node | null} parent enclosing node
3202
- */
3203
- const walkValue = (node, parent) => {
3204
- const ty = _soaTy[_ix(node)];
3205
- const b = visitors[ty];
3206
- let skip = false;
3207
- if (b !== undefined && b.enter.length !== 0) {
3208
- _walkSkip = false;
3209
- _curNode = node;
3210
- _curParent = parent;
3211
- const e = b.enter;
3212
- for (let i = 0; i < e.length; i++) e[i](A);
3213
- skip = _walkSkip;
3214
- _walkSkip = false;
3215
- }
3216
- if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3217
- const v = /** @type {Node[]} */ (_soaL0[_ix(node)]);
3218
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3219
- }
3220
- if (b !== undefined) {
3221
- // Rebind: descending into children moved the path.
3222
- _curNode = node;
3223
- _curParent = parent;
3224
- const x = b.exit;
3225
- for (let i = 0; i < x.length; i++) x[i](A);
3226
- }
3227
- };
3228
-
3229
- /**
3230
- * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
3231
- * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3232
- * @param {Node} node structural-tree root
3233
- * @param {Node | null} parent enclosing node
3234
- */
3235
- const walkRule = (node, parent) => {
3236
- const i0 = _ix(node);
3237
- const ty = _soaTy[i0];
3238
- const b = visitors[ty];
3239
- let skip = false;
3240
- if (b !== undefined && b.enter.length !== 0) {
3241
- _walkSkip = false;
3242
- _curNode = node;
3243
- _curParent = parent;
3244
- const e = b.enter;
3245
- for (let i = 0; i < e.length; i++) e[i](A);
3246
- skip = _walkSkip;
3247
- _walkSkip = false;
3248
- }
3249
- if (!skip) {
3250
- if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3251
- const p = /** @type {Node[]} */ (_soaL0[i0]);
3252
- for (let i = 0; i < p.length; i++) walkValue(p[i], node);
3253
- if (recurseBlocks) {
3254
- // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3255
- const decls = _soaL1[i0];
3256
- if (decls) {
3257
- for (let i = 0; i < decls.length; i++) walkRule(decls[i], node);
3258
- }
3259
- const ch = _soaL2[i0];
3260
- if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
3261
- }
3262
- } else if (ty === T_DECLARATION) {
3263
- const v = /** @type {Node[]} */ (_soaL0[i0]);
3264
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3265
- }
3266
- }
3267
- if (b !== undefined) {
3268
- // Rebind: descending into children moved the path.
3269
- _curNode = node;
3270
- _curParent = parent;
3271
- const x = b.exit;
3272
- for (let i = 0; i < x.length; i++) x[i](A);
3273
- }
3274
- };
3707
+ _recurseBlocks = options.recurseBlocks !== false;
3708
+ _visitors = visitors;
3709
+ _commentBucket = visitors[T_COMMENT];
3275
3710
 
3276
3711
  // Stream each top-level node (selected by `as`) to the walker the moment it's
3277
3712
  // consumed, rather than collecting them first — so the whole AST is never
3278
3713
  // held at once; peak heap is ~one top-level node's subtree.
3279
- const ts = new TokenStream(input, 0, locConverter, onComment);
3714
+ const ts = new TokenStream(
3715
+ input,
3716
+ 0,
3717
+ locConverter,
3718
+ _commentBucket === undefined ? undefined : _grammarOnComment
3719
+ );
3280
3720
  const consume =
3281
3721
  TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
3282
3722
  consumeAStylesheetsContents;
3283
3723
  try {
3284
- consume(ts, (node) => {
3285
- walkRule(node, null);
3286
- _soaN = 0;
3287
- });
3724
+ consume(ts, _walkTopLevel);
3288
3725
  } finally {
3289
- // Drop the module-level SoA references so the last parsed source (and
3290
- // its LocConverter / child lists) don't stay alive between parses.
3291
- _soaInput = "";
3292
- _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
3293
- _soaL0.length = 0;
3294
- _soaL1.length = 0;
3295
- _soaL2.length = 0;
3726
+ // Drop the module-level column references so the last parsed source (and
3727
+ // its LocConverter / child lists / visitors) don't stay alive between
3728
+ // parses.
3729
+ _input = "";
3730
+ _locConverter = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
3731
+ _declarationLists.length = 0;
3732
+ _childRuleLists.length = 0;
3733
+ _flatTop = 0;
3734
+ _listPool.length = 0;
3735
+ if (_flat.length > _SHRINK_CAPACITY) {
3736
+ _flatGrowHint = _flatPeak;
3737
+ _flat = new Int32Array(0);
3738
+ }
3739
+ _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3740
+ _commentBucket = undefined;
3741
+ if (_capacity > _SHRINK_CAPACITY) {
3742
+ // +1: node ids are 1-based and grow fires at `id >= capacity`.
3743
+ _growHint = _peak + 1;
3744
+ _capacity = 0;
3745
+ _types = new Uint8Array(0);
3746
+ _starts = new Int32Array(0);
3747
+ _ends = new Int32Array(0);
3748
+ _aux0 = new Int32Array(0);
3749
+ _aux1 = new Int32Array(0);
3750
+ _flags = new Uint8Array(0);
3751
+ _listStarts = new Int32Array(0);
3752
+ _listLens = new Int32Array(0);
3753
+ }
3754
+ _peak = 0;
3755
+ _flatPeak = 0;
3296
3756
  }
3297
3757
  };
3298
3758
 
@@ -3340,36 +3800,56 @@ const buildSkipSet = (nodeTypes) => {
3340
3800
  */
3341
3801
  /* eslint-enable jsdoc/require-template */
3342
3802
 
3803
+ // A fresh (safely retainable) array view of a node's flat content span —
3804
+ // visitors that read `A.children` / `A.prelude` may keep the result.
3805
+ /** @type {(n: Node) => Node[]} */
3806
+ const _materializeList = (n) => {
3807
+ const i = _nodeIndex(n);
3808
+ const start = _listStarts[i];
3809
+ const len = _listLens[i];
3810
+ /** @type {Node[]} */
3811
+ const out = [];
3812
+ for (let k = 0; k < len; k++) out.push(_nodeRef(_flat[start + k]));
3813
+ return out;
3814
+ };
3815
+
3343
3816
  // Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
3344
3817
  // `enter` dispatch, consumed by the walk.
3345
3818
  let _walkSkip = false;
3346
3819
  // The walk's current position (`A.node` / `A.parent` read these; module-level
3347
3820
  // so the accessor methods' defaults avoid self-referential `this` typing).
3348
3821
  /** @type {Node} */
3349
- let _curNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3822
+ let _currentNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3350
3823
  /** @type {Node | null} */
3351
- let _curParent = null;
3824
+ let _currentParent = null;
3825
+ // Index of the current node within its sibling list (a rule body's declarations
3826
+ // and child rules are separate lists, so each indexes from 0 independently).
3827
+ let _currentIndex = 0;
3352
3828
 
3353
3829
  // AST field-access seam. Every AST-node field read by `CssParser` goes through
3354
- // one of these accessors so the node representation can change underneath the
3355
- // consumer without touching it. Today they are backed by the `Node` / `Token` /
3356
- // `Container` objects (`n` is a node); the Struct-of-Arrays migration rewrites
3357
- // the bodies to index typed arrays (`n` becomes an integer node id) without any
3358
- // consumer edit. `value` is the leaf-token string; container child lists are
3359
- // `children` / `prelude` / `declarations` / `childRules`.
3830
+ // one of these accessors (`n` is an integer node id into the columns), so
3831
+ // the storage stays behind the accessor without any consumer edit. `value` is
3832
+ // the leaf-token string; container child lists are `children` / `prelude` /
3833
+ // `declarations` / `childRules`.
3360
3834
  const A = {
3361
3835
  // === path position (rebound by the walk before every visitor call) ===
3362
3836
  /**
3363
3837
  * @returns {Node} current node — only valid during a visitor callback
3364
3838
  */
3365
3839
  get node() {
3366
- return _curNode;
3840
+ return _currentNode;
3367
3841
  },
3368
3842
  /**
3369
3843
  * @returns {Node | null} enclosing node (null = a top-level node)
3370
3844
  */
3371
3845
  get parent() {
3372
- return _curParent;
3846
+ return _currentParent;
3847
+ },
3848
+ /**
3849
+ * @returns {number} index of the current node within its sibling list (0 for a top-level node) — only valid during a visitor callback
3850
+ */
3851
+ get index() {
3852
+ return _currentIndex;
3373
3853
  },
3374
3854
  /** Stop the walk descending into the current node (enter only). */
3375
3855
  skipChildren() {
@@ -3380,42 +3860,42 @@ const A = {
3380
3860
  * @param {Node=} n node
3381
3861
  * @returns {number} node type
3382
3862
  */
3383
- type(n = _curNode) {
3384
- return _soaTy[_ix(n)];
3863
+ type(n = _currentNode) {
3864
+ return _types[_nodeIndex(n)];
3385
3865
  },
3386
3866
  /**
3387
3867
  * @param {Node=} n node
3388
3868
  * @returns {number} start offset
3389
3869
  */
3390
- start(n = _curNode) {
3391
- return _soaSt[_ix(n)];
3870
+ start(n = _currentNode) {
3871
+ return _starts[_nodeIndex(n)];
3392
3872
  },
3393
3873
  /**
3394
3874
  * @param {Node=} n node
3395
3875
  * @returns {number} end offset
3396
3876
  */
3397
- end(n = _curNode) {
3398
- return _soaEn[_ix(n)];
3877
+ end(n = _currentNode) {
3878
+ return _ends[_nodeIndex(n)];
3399
3879
  },
3400
3880
  /**
3401
3881
  * @param {Node=} n node
3402
3882
  * @returns {[number, number]} start / end offsets
3403
3883
  */
3404
- range(n = _curNode) {
3405
- const i = _ix(n);
3406
- return [_soaSt[i], _soaEn[i]];
3884
+ range(n = _currentNode) {
3885
+ const i = _nodeIndex(n);
3886
+ return [_starts[i], _ends[i]];
3407
3887
  },
3408
3888
  /**
3409
3889
  * @param {Node=} n node
3410
3890
  * @returns {{ start: { line: number, column: number }, end: { line: number, column: number } }} source location
3411
3891
  */
3412
- loc(n = _curNode) {
3413
- const i = _ix(n);
3414
- const lc = _soaLC;
3415
- const s = lc.get(_soaSt[i]);
3892
+ loc(n = _currentNode) {
3893
+ const i = _nodeIndex(n);
3894
+ const lc = _locConverter;
3895
+ const s = lc.get(_starts[i]);
3416
3896
  const sl = s.line;
3417
3897
  const sc = s.column;
3418
- const e = lc.get(_soaEn[i]);
3898
+ const e = lc.get(_ends[i]);
3419
3899
  return {
3420
3900
  start: { line: sl, column: sc },
3421
3901
  end: { line: e.line, column: e.column }
@@ -3425,25 +3905,25 @@ const A = {
3425
3905
  * @param {Node=} n node
3426
3906
  * @returns {string} raw source slice
3427
3907
  */
3428
- source(n = _curNode) {
3429
- const i = _ix(n);
3430
- return _soaInput.slice(_soaSt[i], _soaEn[i]);
3908
+ source(n = _currentNode) {
3909
+ const i = _nodeIndex(n);
3910
+ return _input.slice(_starts[i], _ends[i]);
3431
3911
  },
3432
3912
  /**
3433
3913
  * @param {Node=} n node
3434
3914
  * @returns {string} raw token value
3435
3915
  */
3436
- value(n = _curNode) {
3437
- return _soaValueOf(_ix(n));
3916
+ value(n = _currentNode) {
3917
+ return _valueOf(_nodeIndex(n));
3438
3918
  },
3439
3919
  /**
3440
3920
  * @param {Node=} n node
3441
3921
  * @returns {string} unescaped token value
3442
3922
  */
3443
- unescaped(n = _curNode) {
3444
- const i = _ix(n);
3445
- const v = _soaValueOf(i);
3446
- return _soaTy[i] === T_STRING
3923
+ unescaped(n = _currentNode) {
3924
+ const i = _nodeIndex(n);
3925
+ const v = _valueOf(i);
3926
+ return _types[i] === T_STRING
3447
3927
  ? unescapeIdentifier(v.slice(1, -1))
3448
3928
  : unescapeIdentifier(v);
3449
3929
  },
@@ -3451,11 +3931,11 @@ const A = {
3451
3931
  * @param {Node=} n node
3452
3932
  * @returns {string} hash / numeric type flag
3453
3933
  */
3454
- typeFlag(n = _curNode) {
3455
- const i = _ix(n);
3456
- if (_soaTy[i] === T_HASH) {
3457
- const input = _soaInput;
3458
- const p = _soaSt[i] + 1;
3934
+ typeFlag(n = _currentNode) {
3935
+ const i = _nodeIndex(n);
3936
+ if (_types[i] === T_HASH) {
3937
+ const input = _input;
3938
+ const p = _starts[i] + 1;
3459
3939
  return _ifThreeCodePointsWouldStartAnIdentSequence(
3460
3940
  input,
3461
3941
  p,
@@ -3466,139 +3946,162 @@ const A = {
3466
3946
  ? "id"
3467
3947
  : "unrestricted";
3468
3948
  }
3469
- const v = _soaValueOf(i);
3949
+ const v = _valueOf(i);
3470
3950
  return _typeFlagOf(
3471
- _soaTy[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3951
+ _types[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3472
3952
  );
3473
3953
  },
3474
3954
  /**
3475
3955
  * @param {Node=} n node
3476
3956
  * @returns {number} url content start offset
3477
3957
  */
3478
- contentStart(n = _curNode) {
3479
- return _soaA0[_ix(n)];
3958
+ contentStart(n = _currentNode) {
3959
+ return _aux0[_nodeIndex(n)];
3480
3960
  },
3481
3961
  /**
3482
3962
  * @param {Node=} n node
3483
3963
  * @returns {number} url content end offset
3484
3964
  */
3485
- contentEnd(n = _curNode) {
3486
- return _soaA1[_ix(n)];
3965
+ contentEnd(n = _currentNode) {
3966
+ return _aux1[_nodeIndex(n)];
3487
3967
  },
3488
3968
  /**
3489
3969
  * @param {Node=} n node
3490
3970
  * @returns {string} rule / declaration / function name
3491
3971
  */
3492
- name(n = _curNode) {
3493
- const i = _ix(n);
3494
- return _soaTy[i] === T_AT_RULE
3495
- ? _soaInput.slice(_soaSt[i] + 1, _soaA0[i])
3496
- : _soaInput.slice(_soaSt[i], _soaA0[i]);
3972
+ name(n = _currentNode) {
3973
+ const i = _nodeIndex(n);
3974
+ return _types[i] === T_AT_RULE
3975
+ ? _input.slice(_starts[i] + 1, _aux0[i])
3976
+ : _input.slice(_starts[i], _aux0[i]);
3497
3977
  },
3498
3978
  /**
3499
3979
  * @param {Node=} n node
3500
3980
  * @returns {number} name start offset
3501
3981
  */
3502
- nameStart(n = _curNode) {
3503
- return _soaSt[_ix(n)];
3982
+ nameStart(n = _currentNode) {
3983
+ return _starts[_nodeIndex(n)];
3504
3984
  },
3505
3985
  /**
3506
3986
  * @param {Node=} n node
3507
3987
  * @returns {number} name end offset
3508
3988
  */
3509
- nameEnd(n = _curNode) {
3510
- return _soaA0[_ix(n)];
3989
+ nameEnd(n = _currentNode) {
3990
+ return _aux0[_nodeIndex(n)];
3511
3991
  },
3512
3992
  /**
3513
3993
  * @param {Node=} n node
3514
3994
  * @returns {string} unescaped name
3515
3995
  */
3516
- unescapedName(n = _curNode) {
3996
+ unescapedName(n = _currentNode) {
3517
3997
  return unescapeIdentifier(A.name(n));
3518
3998
  },
3519
3999
  /**
3520
4000
  * @param {Node=} n node
3521
4001
  * @returns {ComponentValue[]} function / block children
3522
4002
  */
3523
- children(n = _curNode) {
3524
- return /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]);
4003
+ children(n = _currentNode) {
4004
+ return /** @type {ComponentValue[]} */ (_materializeList(n));
3525
4005
  },
3526
4006
  /**
3527
4007
  * @param {Node=} n node
3528
4008
  * @returns {ComponentValue[]} rule prelude
3529
4009
  */
3530
- prelude(n = _curNode) {
3531
- return /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]);
4010
+ prelude(n = _currentNode) {
4011
+ return /** @type {ComponentValue[]} */ (_materializeList(n));
4012
+ },
4013
+ /**
4014
+ * @param {Node=} n node
4015
+ * @returns {number} number of children (value / prelude) without materializing the list
4016
+ */
4017
+ childCount(n = _currentNode) {
4018
+ return _listLens[_nodeIndex(n)];
4019
+ },
4020
+ /**
4021
+ * @param {Node} n node
4022
+ * @param {number} i child index (`0 <= i < childCount(n)`)
4023
+ * @returns {ComponentValue} i-th child (value / prelude) without materializing the list
4024
+ */
4025
+ childAt(n, i) {
4026
+ return /** @type {ComponentValue} */ (
4027
+ _nodeRef(_flat[_listStarts[_nodeIndex(n)] + i])
4028
+ );
3532
4029
  },
3533
4030
  /**
3534
4031
  * @param {Node=} n node
3535
4032
  * @returns {Declaration[] | null} block declarations
3536
4033
  */
3537
- declarations(n = _curNode) {
3538
- return /** @type {Declaration[] | null} */ (_soaL1[_ix(n)]);
4034
+ declarations(n = _currentNode) {
4035
+ // `_makeContainer` only populates this slot for rules; a non-rule node
4036
+ // reads `undefined`, normalized to `null` to keep the documented contract.
4037
+ return /** @type {Declaration[] | null} */ (
4038
+ _declarationLists[_nodeIndex(n)] || null
4039
+ );
3539
4040
  },
3540
4041
  /**
3541
4042
  * @param {Node=} n node
3542
4043
  * @returns {Rule[] | null} block child rules
3543
4044
  */
3544
- childRules(n = _curNode) {
3545
- return /** @type {Rule[] | null} */ (_soaL2[_ix(n)]);
4045
+ childRules(n = _currentNode) {
4046
+ return /** @type {Rule[] | null} */ (
4047
+ _childRuleLists[_nodeIndex(n)] || null
4048
+ );
3546
4049
  },
3547
4050
  /**
3548
4051
  * @param {Node=} n node
3549
4052
  * @returns {number} block start offset
3550
4053
  */
3551
- blockStart(n = _curNode) {
3552
- return _soaA1[_ix(n)];
4054
+ blockStart(n = _currentNode) {
4055
+ return _aux1[_nodeIndex(n)];
3553
4056
  },
3554
4057
  /**
3555
4058
  * @param {Node=} n node
3556
4059
  * @returns {number} block end offset
3557
4060
  */
3558
- blockEnd(n = _curNode) {
3559
- return _soaA2[_ix(n)];
4061
+ blockEnd(n = _currentNode) {
4062
+ const i = _nodeIndex(n);
4063
+ return _aux1[i] !== -1 ? _ends[i] : -1;
3560
4064
  },
3561
4065
  /**
3562
4066
  * @param {Node=} n node
3563
4067
  * @returns {boolean} `!important` flag
3564
4068
  */
3565
- important(n = _curNode) {
3566
- return (_soaFl[_ix(n)] & 1) !== 0;
4069
+ important(n = _currentNode) {
4070
+ return (_flags[_nodeIndex(n)] & 1) !== 0;
3567
4071
  },
3568
4072
  /**
3569
4073
  * @param {Node=} n node
3570
4074
  * @returns {SimpleBlockToken} block opening token
3571
4075
  */
3572
- blockToken(n = _curNode) {
3573
- return /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(n)]]);
4076
+ blockToken(n = _currentNode) {
4077
+ return /** @type {SimpleBlockToken} */ (_input[_starts[_nodeIndex(n)]]);
3574
4078
  },
3575
4079
  // Writers — `CssParser` rewrites a rule's end / block-end when it folds an
3576
- // inline ICSS `:import` / `:export` body into a single dependency. The
3577
- // node stays explicit here: writes should never be implicit on position.
4080
+ // inline ICSS `:import` / `:export` body into a single dependency. A block
4081
+ // rule's `blockEnd` is its `end`, so `setBlockEnd` writes the same `end` slot.
3578
4082
  /**
3579
4083
  * @param {Node} n node
3580
4084
  * @param {number} v new end offset
3581
4085
  */
3582
4086
  setEnd(n, v) {
3583
- _soaEn[_ix(n)] = v;
4087
+ _ends[_nodeIndex(n)] = v;
3584
4088
  },
3585
4089
  /**
3586
4090
  * @param {Node} n node
3587
4091
  * @param {number} v new block end offset
3588
4092
  */
3589
4093
  setBlockEnd(n, v) {
3590
- _soaA2[_ix(n)] = v;
4094
+ _ends[_nodeIndex(n)] = v;
3591
4095
  }
3592
4096
  };
3593
4097
 
3594
- // The two AST runtime classes — `Node` and its sole subclass `Token` (the
3595
- // other node shapes are `@typedef`s over `Node`, exported as types only). Plus
3596
- // the full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, `consumeASimpleBlock`
3597
- // (the one §5.4 algorithm exposed as a byte entry point for `CssParser`), the
3598
- // `TokenStream` (so callers can pass a pre-built stream to any `parseA*`), and
3599
- // the `escape` / `unescapeIdentifier` string utils.
4098
+ // The AST node shapes (`Node`, `Token`, and the container typedefs) are types
4099
+ // only — nodes are integer ids into the store, surfaced by the `A` visitor
4100
+ // accessor and the `parseA*` readers. Runtime exports: the `A` accessor, the
4101
+ // full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, the `TokenStream` (so
4102
+ // callers can pass a pre-built stream to any `parseA*`), and the `escape` /
4103
+ // `unescapeIdentifier` string utils.
3600
4104
  module.exports.A = A;
3601
- module.exports.Node = Node;
3602
4105
  module.exports.NodeType = NodeType;
3603
4106
  module.exports.SourceProcessor = SourceProcessor;
3604
4107
  module.exports.TT_AT_KEYWORD = TT_AT_KEYWORD;
@@ -3627,11 +4130,15 @@ module.exports.TT_SEMICOLON = TT_SEMICOLON;
3627
4130
  module.exports.TT_STRING = TT_STRING;
3628
4131
  module.exports.TT_URL = TT_URL;
3629
4132
  module.exports.TT_WHITESPACE = TT_WHITESPACE;
3630
- module.exports.Token = Token;
3631
4133
  module.exports.TokenStream = TokenStream;
3632
4134
  module.exports.buildSkipSet = buildSkipSet;
3633
4135
  module.exports.equalsLowerCase = equalsLowerCase;
3634
4136
  module.exports.escapeIdentifier = escapeIdentifier;
4137
+ module.exports.isDashedIdentifier = isDashedIdentifier;
4138
+ // CSS Syntax §4.2 "whitespace" (space / tab / newline / CR / FF) — the
4139
+ // tokenizer's whitespace class, exported under the spec's name.
4140
+ module.exports.isWhitespace = _isWhiteSpace;
4141
+ module.exports.normalizeUrl = normalizeUrl;
3635
4142
  module.exports.parseABlocksContents = parseABlocksContents;
3636
4143
  module.exports.parseACommaSeparatedListOfComponentValues =
3637
4144
  parseACommaSeparatedListOfComponentValues;
@@ -3641,5 +4148,8 @@ module.exports.parseAListOfComponentValues = parseAListOfComponentValues;
3641
4148
  module.exports.parseARule = parseARule;
3642
4149
  module.exports.parseAStylesheet = parseAStylesheet;
3643
4150
  module.exports.parseAStylesheetsContents = parseAStylesheetsContents;
4151
+ module.exports.rangeEquals = rangeEquals;
4152
+ module.exports.rangeEqualsLowerCase = rangeEqualsLowerCase;
3644
4153
  module.exports.readToken = readToken;
4154
+ module.exports.toLowerCaseIfNeeded = toLowerCaseIfNeeded;
3645
4155
  module.exports.unescapeIdentifier = unescapeIdentifier;