webpack 5.108.3 → 5.109.0

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 (191) hide show
  1. package/lib/APIPlugin.js +48 -29
  2. package/lib/CacheFacade.js +8 -0
  3. package/lib/ChunkGroup.js +1 -0
  4. package/lib/CompatibilityPlugin.js +31 -29
  5. package/lib/Compilation.js +50 -8
  6. package/lib/ConstPlugin.js +11 -11
  7. package/lib/ContextModule.js +239 -6
  8. package/lib/ContextModuleFactory.js +139 -14
  9. package/lib/DefinePlugin.js +396 -101
  10. package/lib/Entrypoint.js +1 -1
  11. package/lib/ExportsInfoApiPlugin.js +2 -2
  12. package/lib/ExternalModule.js +107 -5
  13. package/lib/ExternalModuleFactoryPlugin.js +20 -1
  14. package/lib/FileSystemInfo.js +130 -74
  15. package/lib/FlagDependencyExportsPlugin.js +55 -8
  16. package/lib/HotModuleReplacementPlugin.js +18 -15
  17. package/lib/LazyBarrel.js +30 -3
  18. package/lib/Module.js +3 -0
  19. package/lib/ModuleFilenameHelpers.js +2 -2
  20. package/lib/ModuleTypeConstants.js +8 -1
  21. package/lib/MultiCompiler.js +13 -2
  22. package/lib/MultiWatching.js +1 -0
  23. package/lib/NodeStuffPlugin.js +51 -33
  24. package/lib/NormalModule.js +169 -405
  25. package/lib/NormalModuleFactory.js +76 -2
  26. package/lib/ProgressPlugin.js +211 -119
  27. package/lib/ProvidePlugin.js +2 -2
  28. package/lib/RuntimeGlobals.js +47 -0
  29. package/lib/RuntimePlugin.js +33 -0
  30. package/lib/RuntimeTemplate.js +304 -16
  31. package/lib/UseStrictPlugin.js +1 -1
  32. package/lib/WebpackIsIncludedPlugin.js +1 -1
  33. package/lib/WebpackOptionsApply.js +24 -19
  34. package/lib/asset/AssetGenerator.js +110 -28
  35. package/lib/asset/AssetModulesPlugin.js +92 -2
  36. package/lib/asset/WebManifestGenerator.js +164 -0
  37. package/lib/asset/WebManifestParser.js +130 -0
  38. package/lib/async-modules/AwaitDependenciesInitFragment.js +14 -4
  39. package/lib/async-modules/isGeneratorLowered.js +26 -0
  40. package/lib/cache/PackFileCacheStrategy.js +9 -3
  41. package/lib/config/browserslistTargetHandler.js +40 -0
  42. package/lib/config/defaults.js +222 -32
  43. package/lib/config/normalization.js +26 -0
  44. package/lib/config/target.js +9 -1
  45. package/lib/css/CssGenerator.js +73 -73
  46. package/lib/css/CssLoadingRuntimeModule.js +5 -1
  47. package/lib/css/CssModulesPlugin.js +92 -53
  48. package/lib/css/CssParser.js +843 -302
  49. package/lib/css/syntax.js +1219 -483
  50. package/lib/dependencies/AMDDefineDependencyParserPlugin.js +6 -6
  51. package/lib/dependencies/AMDPlugin.js +2 -2
  52. package/lib/dependencies/AMDRequireDependenciesBlockParserPlugin.js +13 -13
  53. package/lib/dependencies/CommonJsDependencyHelpers.js +39 -0
  54. package/lib/dependencies/CommonJsExportsDependency.js +47 -2
  55. package/lib/dependencies/CommonJsExportsParserPlugin.js +10 -10
  56. package/lib/dependencies/CommonJsImportsParserPlugin.js +18 -18
  57. package/lib/dependencies/CommonJsPlugin.js +2 -2
  58. package/lib/dependencies/CommonJsRequireDependency.js +26 -1
  59. package/lib/dependencies/CommonJsSelfReferenceDependency.js +32 -1
  60. package/lib/dependencies/ContextDependencyHelpers.js +3 -3
  61. package/lib/dependencies/CreateRequireParserPlugin.js +6 -6
  62. package/lib/dependencies/CssIcssExportDependency.js +190 -87
  63. package/lib/dependencies/CssUrlDependency.js +35 -4
  64. package/lib/dependencies/ExternalModuleInitFragmentDependency.js +2 -2
  65. package/lib/dependencies/HarmonyCompatibilityDependency.js +32 -14
  66. package/lib/dependencies/HarmonyDetectionParserPlugin.js +43 -13
  67. package/lib/dependencies/HarmonyExportDependencyParserPlugin.js +7 -19
  68. package/lib/dependencies/HarmonyImportDependency.js +12 -1
  69. package/lib/dependencies/HarmonyImportDependencyParserPlugin.js +34 -107
  70. package/lib/dependencies/HarmonyImportGuard.js +192 -20
  71. package/lib/dependencies/HarmonyModulesPlugin.js +6 -0
  72. package/lib/dependencies/HarmonyTopLevelThisParserPlugin.js +1 -1
  73. package/lib/dependencies/HtmlEntryDependency.js +797 -172
  74. package/lib/dependencies/HtmlSourceDependency.js +26 -0
  75. package/lib/dependencies/ImportDependency.js +43 -1
  76. package/lib/dependencies/ImportMetaContextDependencyParserPlugin.js +31 -18
  77. package/lib/dependencies/ImportMetaContextPlugin.js +38 -7
  78. package/lib/dependencies/ImportMetaGlobDependency.js +81 -0
  79. package/lib/dependencies/ImportMetaGlobDependencyParserPlugin.js +78 -0
  80. package/lib/dependencies/ImportMetaGlobHelpers.js +660 -0
  81. package/lib/dependencies/ImportMetaPlugin.js +532 -180
  82. package/lib/dependencies/ImportMetaResolveDependency.js +90 -0
  83. package/lib/dependencies/ImportParserPlugin.js +87 -26
  84. package/lib/dependencies/ImportPhase.js +10 -8
  85. package/lib/dependencies/LoaderPlugin.js +5 -1
  86. package/lib/dependencies/RequireContextDependencyParserPlugin.js +1 -1
  87. package/lib/dependencies/RequireEnsureDependenciesBlockParserPlugin.js +3 -6
  88. package/lib/dependencies/RequireIncludeDependencyParserPlugin.js +4 -11
  89. package/lib/dependencies/SystemPlugin.js +2 -4
  90. package/lib/dependencies/TopLevelAwaitDependency.js +93 -0
  91. package/lib/dependencies/URLDependency.js +141 -21
  92. package/lib/dependencies/{WorkerPlugin.js → WorkerAndWorkletPlugin.js} +324 -112
  93. package/lib/dependencies/WorkerDependency.js +86 -18
  94. package/lib/dependencies/WorkletDependency.js +278 -0
  95. package/lib/dll/DelegatedModuleFactoryPlugin.js +19 -0
  96. package/lib/errors/EnvironmentNotSupportAsyncWarning.js +1 -1
  97. package/lib/errors/LoaderLoadingError.js +20 -0
  98. package/lib/errors/ModuleParseError.js +39 -22
  99. package/lib/errors/NodeStuffInWebError.js +1 -1
  100. package/lib/esm/ModuleChunkFormatPlugin.js +47 -14
  101. package/lib/esm/ModuleChunkLoadingPlugin.js +8 -1
  102. package/lib/esm/ModuleChunkLoadingRuntimeModule.js +29 -1
  103. package/lib/hmr/lazyCompilationBackend.js +32 -8
  104. package/lib/html/HtmlGenerator.js +940 -91
  105. package/lib/html/HtmlModule.js +3 -2
  106. package/lib/html/HtmlModulesPlugin.js +625 -170
  107. package/lib/html/HtmlParser.js +812 -340
  108. package/lib/html/favicon.svg +1 -0
  109. package/lib/html/syntax.js +2116 -834
  110. package/lib/ids/IdHelpers.js +1 -1
  111. package/lib/index.js +6 -0
  112. package/lib/javascript/ArrayPushCallbackChunkFormatPlugin.js +4 -5
  113. package/lib/javascript/BasicEvaluatedExpression.js +366 -48
  114. package/lib/javascript/JavascriptGenerator.js +197 -49
  115. package/lib/javascript/JavascriptModulesPlugin.js +178 -24
  116. package/lib/javascript/JavascriptParser.js +711 -355
  117. package/lib/javascript/JavascriptParserHelpers.js +3 -6
  118. package/lib/javascript/syntax.js +4380 -0
  119. package/lib/library/ModuleLibraryPlugin.js +38 -1
  120. package/lib/loaders/LoaderRunner.js +714 -0
  121. package/lib/loaders/loadLoader.js +100 -0
  122. package/lib/node/ReadFileCompileAsyncWasmPlugin.js +19 -0
  123. package/lib/node/ReadFileCompileWasmPlugin.js +14 -0
  124. package/lib/optimize/ConcatenatedModule.js +120 -16
  125. package/lib/optimize/FlagIncludedChunksPlugin.js +13 -3
  126. package/lib/optimize/InnerGraphPlugin.js +50 -30
  127. package/lib/optimize/ModuleConcatenationPlugin.js +69 -3
  128. package/lib/optimize/SideEffectsFlagPlugin.js +86 -43
  129. package/lib/prefetch/ChunkPrefetchPreloadPlugin.js +15 -0
  130. package/lib/prefetch/ChunkPreloadTriggerRuntimeModule.js +24 -13
  131. package/lib/prefetch/ResourceHintPlugin.js +663 -0
  132. package/lib/prefetch/ResourceHintRuntimeModule.js +149 -0
  133. package/lib/prefetch/StartupAssetHintRuntimeModule.js +210 -0
  134. package/lib/prefetch/parseResourceHintOptions.js +103 -0
  135. package/lib/rules/RuleSetCompiler.js +114 -0
  136. package/lib/runtime/AsyncModuleGeneratorRuntimeModule.js +56 -0
  137. package/lib/runtime/AsyncModuleRuntimeModule.js +24 -8
  138. package/lib/runtime/AutoPublicPathRuntimeModule.js +7 -1
  139. package/lib/runtime/CommonJsWrapRuntimeModule.js +37 -0
  140. package/lib/runtime/GetWorkletBootstrapRuntimeModule.js +59 -0
  141. package/lib/runtime/MakeDeferredNamespaceObjectRuntime.js +204 -8
  142. package/lib/runtime/SetAnonymousDefaultNameRuntimeModule.js +4 -1
  143. package/lib/schemes/HttpUriPlugin.js +40 -1
  144. package/lib/schemes/VirtualUrlPlugin.js +1 -2
  145. package/lib/serialization/FileMiddleware.js +29 -3
  146. package/lib/stats/DefaultStatsFactoryPlugin.js +32 -3
  147. package/lib/stats/DefaultStatsPresetPlugin.js +2 -0
  148. package/lib/stats/DefaultStatsPrinterPlugin.js +12 -2
  149. package/lib/stats/StatsFactory.js +15 -8
  150. package/lib/url/URLParserPlugin.js +98 -19
  151. package/lib/util/AsyncQueue.js +122 -72
  152. package/lib/util/LocConverter.js +9 -8
  153. package/lib/util/SetHelpers.js +1 -1
  154. package/lib/util/SourceProcessor.js +41 -28
  155. package/lib/util/StackedMap.js +122 -48
  156. package/lib/util/binarySearchBounds.js +2 -2
  157. package/lib/util/concatenate.js +40 -14
  158. package/lib/util/createHooksRegistry.js +13 -5
  159. package/lib/util/dataURL.js +2 -2
  160. package/lib/util/deterministicGrouping.js +7 -5
  161. package/lib/util/formatSize.js +6 -2
  162. package/lib/util/globUtils.js +710 -0
  163. package/lib/util/identifier.js +23 -6
  164. package/lib/util/implicitTypeLoaderFallback.js +62 -0
  165. package/lib/util/internalSerializables.js +88 -71
  166. package/lib/util/magicComment.js +121 -25
  167. package/lib/util/numberHash.js +2 -2
  168. package/lib/wasm-async/AsyncWasmCompileRuntimeModule.js +24 -18
  169. package/lib/wasm-async/AsyncWasmLoadingRuntimeModule.js +33 -22
  170. package/lib/wasm-sync/WasmChunkLoadingRuntimeModule.js +79 -16
  171. package/lib/web/FetchCompileAsyncWasmPlugin.js +19 -0
  172. package/lib/web/FetchCompileWasmPlugin.js +14 -0
  173. package/lib/web/JsonpChunkLoadingRuntimeModule.js +2 -0
  174. package/lib/webpack.js +65 -0
  175. package/lib/webworker/ImportScriptsChunkLoadingRuntimeModule.js +3 -0
  176. package/module.d.ts +21 -0
  177. package/package.json +14 -13
  178. package/schemas/WebpackOptions.check.js +1 -1
  179. package/schemas/WebpackOptions.json +759 -36
  180. package/schemas/plugins/HtmlParserOptions.check.js +1 -1
  181. package/schemas/plugins/ProgressPlugin.check.js +1 -1
  182. package/schemas/plugins/ProgressPlugin.json +17 -1
  183. package/schemas/plugins/container/ContainerReferencePlugin.check.js +1 -1
  184. package/schemas/plugins/container/ContainerReferencePlugin.json +1 -0
  185. package/schemas/plugins/container/ExternalsType.check.js +1 -1
  186. package/schemas/plugins/container/ModuleFederationPlugin.check.js +1 -1
  187. package/schemas/plugins/container/ModuleFederationPlugin.json +1 -0
  188. package/schemas/plugins/css/CssAutoOrModuleParserOptions.check.js +1 -1
  189. package/schemas/plugins/css/CssModuleParserOptions.check.js +1 -1
  190. package/schemas/plugins/css/CssParserOptions.check.js +1 -1
  191. package/types.d.ts +1300 -325
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);
@@ -297,6 +298,15 @@ const _isSpace = (cc) => cc === CC_SPACE || cc === CC_TAB;
297
298
  // rarer tab / newline tests.
298
299
  const _isWhiteSpace = (cc) => _isSpace(cc) || _isNewline(cc);
299
300
 
301
+ // Whitespace membership table for the run-consumption loop — one load instead
302
+ // of up to five compares per char. EOF (NaN) / non-ASCII index to undefined.
303
+ const _wsTable = new Uint8Array(128);
304
+ _wsTable[CC_SPACE] = 1;
305
+ _wsTable[CC_TAB] = 1;
306
+ _wsTable[CC_LINE_FEED] = 1;
307
+ _wsTable[CC_CARRIAGE_RETURN] = 1;
308
+ _wsTable[CC_FORM_FEED] = 1;
309
+
300
310
  /**
301
311
  * @param {number} cc char code
302
312
  * @returns {boolean} true, if cc is a digit
@@ -332,11 +342,15 @@ const _isIdentStartCodePointCC = (cc) =>
332
342
  /**
333
343
  * Spec: ident-code = ident-start / digit / hyphen-minus.
334
344
  */
335
- // ASCII lookup built from the predicate so behaviour is identical; non-ASCII
336
- // (>= 128) keeps the inline check. `_consumeAnIdentSequence` hits this per code
337
- // point, and class/property names (hyphen + digit heavy) are the longest
338
- // comparison-chain path — a table load is constant-time instead.
339
- 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);
340
354
  for (let i = 0; i < 128; i++) {
341
355
  _identCharTable[i] =
342
356
  _isLetter(i) || i === CC_LOW_LINE || _isDigit(i) || i === CC_HYPHEN_MINUS
@@ -347,8 +361,7 @@ for (let i = 0; i < 128; i++) {
347
361
  * @param {number} cc char code
348
362
  * @returns {boolean} true, if cc is an ident-sequence code point
349
363
  */
350
- const _isIdentCodePoint = (cc) =>
351
- cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc);
364
+ const _isIdentCodePoint = (cc) => _identCharTable[cc | 0] === 1;
352
365
 
353
366
  /**
354
367
  * ASCII case-insensitive equality against a lowercase literal — avoids the
@@ -368,6 +381,58 @@ const equalsLowerCase = (s, lit) => {
368
381
  return true;
369
382
  };
370
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
+
371
436
  /**
372
437
  * Consume an escaped code point.
373
438
  * @param {string} input input
@@ -385,7 +450,8 @@ const _consumeAnEscapedCodePoint = (input, pos) => {
385
450
  if (pos === input.length) return pos;
386
451
  if (_isHexDigit(cc)) {
387
452
  for (let i = 0; i < 5; i++) {
388
- if (_isHexDigit(input.charCodeAt(pos))) pos++;
453
+ if (!_isHexDigit(input.charCodeAt(pos))) break;
454
+ pos++;
389
455
  }
390
456
  const trail = input.charCodeAt(pos);
391
457
  if (_isWhiteSpace(trail)) {
@@ -471,12 +537,13 @@ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
471
537
  const _consumeAnIdentSequence = (input, pos) => {
472
538
  // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
473
539
  // are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the
474
- // ASCII ident test is a single table load, and the escape test reads the
475
- // 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.
476
543
  for (;;) {
477
- const cc = input.charCodeAt(pos);
544
+ const cc = input.charCodeAt(pos) | 0;
478
545
  pos++;
479
- if (cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc)) {
546
+ if (_identCharTable[cc] === 1) {
480
547
  continue;
481
548
  }
482
549
  if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
@@ -612,7 +679,7 @@ const fill = (out, type, start, end) => {
612
679
  */
613
680
  function consumeSpace(input, pos, out) {
614
681
  const start = pos - 1;
615
- while (_isWhiteSpace(input.charCodeAt(pos))) pos++;
682
+ while (_wsTable[input.charCodeAt(pos)] === 1) pos++;
616
683
  return fill(out, TT_WHITESPACE, start, pos);
617
684
  }
618
685
 
@@ -691,17 +758,33 @@ function consumeNumberSign(input, pos, out) {
691
758
  * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
692
759
  */
693
760
  function consumeHyphenMinus(input, pos, out) {
694
- if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
761
+ // Read the two lookahead code points once; the lead is the known `-`.
762
+ const second = input.charCodeAt(pos);
763
+ const third = input.charCodeAt(pos + 1);
764
+ if (
765
+ _ifThreeCodePointsWouldStartANumber(
766
+ input,
767
+ pos,
768
+ CC_HYPHEN_MINUS,
769
+ second,
770
+ third
771
+ )
772
+ ) {
695
773
  pos--;
696
774
  return consumeANumericToken(input, pos, out);
697
775
  }
698
- if (
699
- input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
700
- input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN
701
- ) {
776
+ if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) {
702
777
  return fill(out, TT_CDC, pos - 1, pos + 2);
703
778
  }
704
- if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) {
779
+ if (
780
+ _ifThreeCodePointsWouldStartAnIdentSequence(
781
+ input,
782
+ pos,
783
+ CC_HYPHEN_MINUS,
784
+ second,
785
+ third
786
+ )
787
+ ) {
705
788
  pos--;
706
789
  return consumeAnIdentLikeToken(input, pos, out);
707
790
  }
@@ -792,7 +875,7 @@ function consumeAUrlToken(input, pos, fnStart, out) {
792
875
  out.contentStart = contentStart;
793
876
  for (;;) {
794
877
  if (pos === input.length) {
795
- out.contentEnd = pos - 1;
878
+ out.contentEnd = pos;
796
879
  return fill(out, TT_URL, fnStart, pos);
797
880
  }
798
881
  const cc = input.charCodeAt(pos);
@@ -990,7 +1073,11 @@ const HC_AT_SIGN = 9;
990
1073
  const HC_REVERSE_SOLIDUS = 10;
991
1074
  const HC_DIGIT = 11;
992
1075
  const HC_IDENT = 12;
993
- const _charClass = new Uint8Array(128);
1076
+ // Full `charCodeAt` range so `consumeAToken` dispatches with one table load and
1077
+ // no `cc < 128` branch. Every non-ASCII code point (>= 0x80) is an ident-start
1078
+ // lead per §4, so those rows are seeded to `HC_IDENT`; the ASCII rows below
1079
+ // overwrite 0..127 with their real class.
1080
+ const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128);
994
1081
  const _singleTT = new Uint8Array(128);
995
1082
  _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
996
1083
  _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
@@ -1045,7 +1132,7 @@ for (let i = 0; i < 128; i++) {
1045
1132
  function consumeAToken(input, pos, cc, out) {
1046
1133
  // `u` / `U` would start a unicode-range token in the spec; those are not
1047
1134
  // produced, so they map to HC_IDENT and fall through to ident-like.
1048
- switch (cc < 128 ? _charClass[cc] : HC_IDENT) {
1135
+ switch (_charClass[cc]) {
1049
1136
  // Run of whitespace → one <whitespace-token>.
1050
1137
  case HC_WHITESPACE:
1051
1138
  return consumeSpace(input, pos, out);
@@ -1166,7 +1253,10 @@ const NodeType = {
1166
1253
  Declaration: 23,
1167
1254
  AtRule: 24,
1168
1255
  QualifiedRule: 25,
1169
- Stylesheet: 26
1256
+ Stylesheet: 26,
1257
+ // Comments are never tree nodes; this type exists only so a `NodeType.Comment`
1258
+ // visitor can be registered (fired during tokenization — see `grammar`).
1259
+ Comment: 27
1170
1260
  };
1171
1261
  const {
1172
1262
  Ident: T_IDENT,
@@ -1194,7 +1284,8 @@ const {
1194
1284
  Declaration: T_DECLARATION,
1195
1285
  AtRule: T_AT_RULE,
1196
1286
  QualifiedRule: T_QUALIFIED_RULE,
1197
- Stylesheet: T_STYLESHEET
1287
+ Stylesheet: T_STYLESHEET,
1288
+ Comment: T_COMMENT
1198
1289
  } = NodeType;
1199
1290
 
1200
1291
  /**
@@ -1592,17 +1683,46 @@ _ttToNodeType[TT_CDC] = T_CDC;
1592
1683
  // field access differs, so only those ops are swapped.
1593
1684
 
1594
1685
  // Current loc converter for the active parse (object backend reads it).
1595
- let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1686
+ let _objLocConverter = /** @type {LocConverter} */ (
1687
+ /** @type {unknown} */ (null)
1688
+ );
1689
+
1690
+ // Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
1691
+ // `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
1692
+ // leaf / container from declaration value and function-arg lists. The two
1693
+ // prelude flags scan a rule's prelude without materializing its tree (url tokens
1694
+ // / functions kept, so `url()` in a selector or `@import url(…)` still resolves).
1695
+ // A skipped node is still tokenized (positions stay correct) but never pushed,
1696
+ // so it is never walked or read — the caller must only skip what nothing reads.
1697
+ // `useObjectBackend` restores the defaults so `parseA*` build the full tree.
1698
+ const _NO_SKIP_TYPES = new Uint8Array(32);
1699
+ // Shared frozen empty list for block bodies with no decls / no child rules (the
1700
+ // common case — most rules carry only declarations). Every consumer reads these
1701
+ // lists read-only and null-guards, so one immutable instance replaces ~one empty
1702
+ // array allocation per rule; frozen so any errant push fails loud.
1703
+ const _EMPTY_LIST = /** @type {Rule[]} */ (
1704
+ /** @type {unknown} */ (Object.freeze([]))
1705
+ );
1706
+ /** @type {Uint8Array} */
1707
+ let _skipTypes = _NO_SKIP_TYPES;
1708
+ // Fast-path flag: true only when a real skip set is active, so the (dominant)
1709
+ // no-skip parses pay one boolean test instead of a node-type lookup per value.
1710
+ let _skipActive = false;
1711
+ let _skipSelectorPrelude = false;
1712
+ let _skipAtRulePrelude = false;
1596
1713
 
1597
1714
  /** @type {(type: number, start: number, end: number) => Node} */
1598
- let _mkLeaf;
1715
+ let _makeLeaf;
1599
1716
  /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1600
- let _mkUrl;
1717
+ let _makeUrl;
1601
1718
  /** @type {(type: number, start: number, end: number) => Node} */
1602
- let _mkContainer;
1719
+ let _makeContainer;
1603
1720
  /** @type {(start: number) => Node} */
1604
- let _mkStylesheet;
1605
- /** @type {(r: Node, v: string, nameStart: number, nameEnd: number) => void} */
1721
+ let _makeStylesheet;
1722
+ // Offsets only — the object backend derives the name string itself (an
1723
+ // at-rule's name skips its `@`), so the SoA backend never pays for a slice
1724
+ // it would immediately discard (it re-derives names from offsets on read).
1725
+ /** @type {(r: Node, nameStart: number, nameEnd: number) => void} */
1606
1726
  let _setName;
1607
1727
  /** @type {(r: Node, v: number) => void} */
1608
1728
  let _setEnd;
@@ -1621,170 +1741,265 @@ let _setBody;
1621
1741
  /** @type {(r: Node, list: Node[]) => void} */
1622
1742
  let _setRules;
1623
1743
  /** @type {(r: Node) => number} */
1624
- let _nType;
1744
+ let _nodeTypeOf;
1625
1745
  /** @type {(r: Node) => number} */
1626
- let _nStart;
1746
+ let _nodeStartOf;
1627
1747
  /** @type {(r: Node) => string} */
1628
- let _nValue;
1748
+ let _nodeValueOf;
1629
1749
  /** @type {(r: Node) => SimpleBlockToken} */
1630
- let _nToken;
1750
+ let _nodeTokenOf;
1631
1751
 
1632
1752
  // Child lists are plain arrays in every backend; refs are pushed by value.
1633
- const _list = () => /** @type {Node[]} */ ([]);
1753
+ // Content-list allocation: the object backend uses a per-site `[]` literal
1754
+ // (each site keeps its own V8 allocation site — a shared helper collapses them
1755
+ // and defeats escape analysis / pretenuring), the SoA backend recycles scratch
1756
+ // arrays through a pool — a sealed list is copied into the flat value buffer
1757
+ // and its array returned to the pool by `_soaSetValue`, so the streaming parse
1758
+ // allocates no per-list array. An abandoned (never-sealed) list simply falls
1759
+ // out of the pool. `_soaActive` gates the pool at each site.
1760
+ let _soaActive = false;
1761
+ /** @type {Node[][]} */
1762
+ const _listPool = [];
1763
+ const _soaList = () =>
1764
+ _listPool.length > 0
1765
+ ? /** @type {Node[]} */ (_listPool.pop())
1766
+ : /** @type {Node[]} */ ([]);
1634
1767
 
1635
1768
  // -- object backend: builds the retainable class-instance tree --
1636
- /** @type {typeof _mkLeaf} */
1637
- const _objLeaf = (type, start, end) => new Token(type, start, end, _lc);
1638
- /** @type {typeof _mkUrl} */
1769
+ /** @type {typeof _makeLeaf} */
1770
+ const _objLeaf = (type, start, end) =>
1771
+ new Token(type, start, end, _objLocConverter);
1772
+ /** @type {typeof _makeUrl} */
1639
1773
  const _objUrl = (start, end, cs, ce) => {
1640
- const u = /** @type {UrlToken} */ (new Token(T_URL, start, end, _lc));
1774
+ const u = /** @type {UrlToken} */ (
1775
+ new Token(T_URL, start, end, _objLocConverter)
1776
+ );
1641
1777
  u.contentStart = cs;
1642
1778
  u.contentEnd = ce;
1643
1779
  return u;
1644
1780
  };
1645
- /** @type {typeof _mkContainer} */
1781
+ /** @type {typeof _makeContainer} */
1646
1782
  const _objContainer = (type, start, end) =>
1647
- new Container(type, start, end, _lc);
1648
- /** @type {typeof _mkStylesheet} */
1783
+ new Container(type, start, end, _objLocConverter);
1784
+ /** @type {typeof _makeStylesheet} */
1649
1785
  const _objStylesheet = (start) => {
1650
1786
  const s = /** @type {Stylesheet} */ (
1651
- new Node(T_STYLESHEET, start, start, _lc)
1787
+ new Node(T_STYLESHEET, start, start, _objLocConverter)
1652
1788
  );
1653
1789
  s.rules = [];
1654
1790
  return s;
1655
1791
  };
1792
+ // The backend function sets are module-level constants (not closures rebuilt
1793
+ // per parse): the dispatch slots keep one function identity forever, so the
1794
+ // per-node call sites in the consume algorithms stay monomorphic instead of
1795
+ // seeing a fresh closure per parse. They capture only module-level state.
1796
+ /** @type {typeof _setName} */
1797
+ const _objSetName = (r, ns, ne) => {
1798
+ const c = /** @type {Container} */ (r);
1799
+ // An at-rule's `nameStart` points at its `@`, which the name excludes.
1800
+ c.name = _objLocConverter._input.slice(
1801
+ r.type === T_AT_RULE ? ns + 1 : ns,
1802
+ ne
1803
+ );
1804
+ c.nameStart = ns;
1805
+ c.nameEnd = ne;
1806
+ };
1807
+ /** @type {typeof _setEnd} */
1808
+ const _objSetEnd = (r, v) => {
1809
+ r.end = v;
1810
+ };
1811
+ /** @type {typeof _setBlock} */
1812
+ const _objSetBlock = (r, bs, be) => {
1813
+ const c = /** @type {Container} */ (r);
1814
+ c.blockStart = bs;
1815
+ c.blockEnd = be;
1816
+ };
1817
+ /** @type {typeof _setImportant} */
1818
+ const _objSetImportant = (r) => {
1819
+ /** @type {Container} */ (r).important = true;
1820
+ };
1821
+ /** @type {typeof _setToken} */
1822
+ const _objSetToken = (r, ch) => {
1823
+ /** @type {Container} */ (r).token = ch;
1824
+ };
1825
+ /** @type {typeof _setValue} */
1826
+ const _objSetValue = (r, list) => {
1827
+ /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1828
+ };
1829
+ /** @type {typeof _setPrelude} */
1830
+ const _objSetPrelude = (r, list) => {
1831
+ /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (list);
1832
+ };
1833
+ /** @type {typeof _setBody} */
1834
+ const _objSetBody = (r, decls, childRules) => {
1835
+ const c = /** @type {Container} */ (r);
1836
+ c.declarations = /** @type {Declaration[]} */ (decls);
1837
+ c.childRules = /** @type {Rule[]} */ (childRules);
1838
+ };
1839
+ /** @type {typeof _setRules} */
1840
+ const _objSetRules = (r, list) => {
1841
+ /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1842
+ };
1843
+ /** @type {typeof _nodeTypeOf} */
1844
+ const _objNodeTypeOf = (r) => r.type;
1845
+ /** @type {typeof _nodeStartOf} */
1846
+ const _objNodeStartOf = (r) => r.start;
1847
+ /** @type {typeof _nodeValueOf} */
1848
+ const _objNodeValueOf = (r) => /** @type {Token} */ (r).value;
1849
+ /** @type {typeof _nodeTokenOf} */
1850
+ const _objNodeTokenOf = (r) =>
1851
+ /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1656
1852
  /** @param {LocConverter} lc loc converter for this parse */
1657
1853
  const useObjectBackend = (lc) => {
1658
- _lc = lc;
1659
- _mkLeaf = _objLeaf;
1660
- _mkUrl = _objUrl;
1661
- _mkContainer = _objContainer;
1662
- _mkStylesheet = _objStylesheet;
1663
- _setName = (r, v, ns, ne) => {
1664
- const c = /** @type {Container} */ (r);
1665
- c.name = v;
1666
- c.nameStart = ns;
1667
- c.nameEnd = ne;
1668
- };
1669
- _setEnd = (r, v) => {
1670
- r.end = v;
1671
- };
1672
- _setBlock = (r, bs, be) => {
1673
- const c = /** @type {Container} */ (r);
1674
- c.blockStart = bs;
1675
- c.blockEnd = be;
1676
- };
1677
- _setImportant = (r) => {
1678
- /** @type {Container} */ (r).important = true;
1679
- };
1680
- _setToken = (r, ch) => {
1681
- /** @type {Container} */ (r).token = ch;
1682
- };
1683
- _setValue = (r, list) => {
1684
- /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1685
- };
1686
- _setPrelude = (r, list) => {
1687
- /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (
1688
- list
1689
- );
1690
- };
1691
- _setBody = (r, decls, childRules) => {
1692
- const c = /** @type {Container} */ (r);
1693
- c.declarations = /** @type {Declaration[]} */ (decls);
1694
- c.childRules = /** @type {Rule[]} */ (childRules);
1695
- };
1696
- _setRules = (r, list) => {
1697
- /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1698
- };
1699
- _nType = (r) => r.type;
1700
- _nStart = (r) => r.start;
1701
- _nValue = (r) => /** @type {Token} */ (r).value;
1702
- _nToken = (r) =>
1703
- /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1854
+ _objLocConverter = lc;
1855
+ _soaActive = false;
1856
+ // `parseA*` return the full tree, so nothing is skipped.
1857
+ _skipTypes = _NO_SKIP_TYPES;
1858
+ _skipActive = false;
1859
+ _skipSelectorPrelude = false;
1860
+ _skipAtRulePrelude = false;
1861
+ _makeLeaf = _objLeaf;
1862
+ _makeUrl = _objUrl;
1863
+ _makeContainer = _objContainer;
1864
+ _makeStylesheet = _objStylesheet;
1865
+ _setName = _objSetName;
1866
+ _setEnd = _objSetEnd;
1867
+ _setBlock = _objSetBlock;
1868
+ _setImportant = _objSetImportant;
1869
+ _setToken = _objSetToken;
1870
+ _setValue = _objSetValue;
1871
+ _setPrelude = _objSetPrelude;
1872
+ _setBody = _objSetBody;
1873
+ _setRules = _objSetRules;
1874
+ _nodeTypeOf = _objNodeTypeOf;
1875
+ _nodeStartOf = _objNodeStartOf;
1876
+ _nodeValueOf = _objNodeValueOf;
1877
+ _nodeTokenOf = _objNodeTokenOf;
1704
1878
  };
1705
1879
 
1706
1880
  // -- struct-of-arrays backend: writes nodes into reused typed arrays --
1707
1881
  // A node ref is its integer id; fields live in parallel arrays indexed by id.
1708
- // Three reused int slots (`_soaA0/1/2`) plus a flags byte carry the per-type
1709
- // extras; child lists hang off three object arrays. Slot meaning by type:
1710
- // url: a0 contentStart, a1 contentEnd
1711
- // function: a0 nameEnd
1712
- // declaration: a0 nameEnd, flags bit0 important
1713
- // at-rule: a0 nameEnd, a1 blockStart, a2 blockEnd
1714
- // qualified: a1 blockStart, a2 blockEnd
1882
+ // Three reused int slots (`_soaAux0/1/2`) plus a flags byte carry the per-type
1883
+ // extras; child lists hang off three object arrays. Aux slot meaning by type:
1884
+ // url: aux0 contentStart, aux1 contentEnd
1885
+ // function: aux0 nameEnd
1886
+ // declaration: aux0 nameEnd, flags bit0 important
1887
+ // at-rule: aux0 nameEnd, aux1 blockStart, aux2 blockEnd
1888
+ // qualified: aux1 blockStart, aux2 blockEnd
1715
1889
  // `name` / `nameStart` / a simple block's `token` are derived from the source
1716
- // on read (see the SoA accessors), so they need no slot. Lists: l0 =
1717
- // value | prelude | stylesheet rules, l1 = declarations, l2 = childRules.
1718
- // `grammar` resets `_soaN` to 0 after each top-level rule's walk, so the
1890
+ // on read (see the SoA accessors), so they need no slot. A node's main content
1891
+ // (value | prelude | stylesheet rules) is a `_soaFlat` span (see below).
1892
+ // `grammar` resets `_soaNodeCount` to 0 after each top-level rule's walk, so the
1719
1893
  // buffers are reused across rules and the parse allocates almost nothing.
1720
- let _soaCap = 0;
1721
- let _soaN = 0;
1722
- let _soaTy = new Uint8Array(0);
1723
- let _soaSt = new Int32Array(0);
1724
- let _soaEn = new Int32Array(0);
1725
- let _soaA0 = new Int32Array(0);
1726
- let _soaA1 = new Int32Array(0);
1727
- let _soaA2 = new Int32Array(0);
1728
- let _soaFl = new Uint8Array(0);
1729
- /** @type {(Node[] | null)[]} */
1730
- const _soaL0 = [];
1894
+ let _soaCapacity = 0;
1895
+ let _soaNodeCount = 0;
1896
+ let _soaTypes = new Uint8Array(0);
1897
+ let _soaStarts = new Int32Array(0);
1898
+ let _soaEnds = new Int32Array(0);
1899
+ let _soaAux0 = new Int32Array(0);
1900
+ let _soaAux1 = new Int32Array(0);
1901
+ let _soaAux2 = new Int32Array(0);
1902
+ let _soaFlags = new Uint8Array(0);
1903
+ // Content-list spans: a container's value / prelude is `_soaFlat[start, start+len)`
1904
+ // (node refs), recycled per top-level rule like the node columns.
1905
+ let _soaListStarts = new Int32Array(0);
1906
+ let _soaListLens = new Int32Array(0);
1907
+ let _soaFlat = new Int32Array(0);
1908
+ let _soaFlatTop = 0;
1909
+ // Peak usage of the current parse, and use-once regrow hints: after an
1910
+ // over-capacity shrink the next grow jumps straight back to the previous
1911
+ // parse's peak (one exact-fit allocation instead of re-doubling up).
1912
+ let _soaPeak = 0;
1913
+ let _soaFlatPeak = 0;
1914
+ let _soaGrowHint = 0;
1915
+ let _soaFlatGrowHint = 0;
1916
+
1917
+ /** @param {number} need minimum flat-buffer capacity */
1918
+ const _soaFlatGrow = (need) => {
1919
+ let cap = _soaFlat.length || 4096;
1920
+ if (_soaFlatGrowHint > cap) cap = _soaFlatGrowHint;
1921
+ _soaFlatGrowHint = 0;
1922
+ while (cap < need) cap *= 2;
1923
+ const next = new Int32Array(cap);
1924
+ next.set(_soaFlat);
1925
+ _soaFlat = next;
1926
+ };
1731
1927
  /** @type {(Node[] | null)[]} */
1732
- const _soaL1 = [];
1928
+ const _soaDeclarationLists = [];
1733
1929
  /** @type {(Node[] | null)[]} */
1734
- const _soaL2 = [];
1930
+ const _soaChildRuleLists = [];
1735
1931
  let _soaInput = "";
1736
- let _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1932
+ let _soaLocConverter = /** @type {LocConverter} */ (
1933
+ /** @type {unknown} */ (null)
1934
+ );
1737
1935
 
1738
1936
  // Node refs are integers here but typed `Node` across the parser; these are
1739
1937
  // identity casts that just satisfy the type system at the boundary.
1740
1938
  /** @type {(n: Node) => number} */
1741
- const _ix = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1939
+ const _nodeIndex = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1742
1940
  /** @type {(i: number) => Node} */
1743
- const _ref = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1941
+ const _nodeRef = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1744
1942
 
1745
1943
  /** @param {number} need minimum capacity */
1746
1944
  const _soaGrow = (need) => {
1747
- let cap = _soaCap || 4096;
1945
+ let cap = _soaCapacity || 4096;
1946
+ if (_soaGrowHint > cap) cap = _soaGrowHint;
1947
+ _soaGrowHint = 0;
1748
1948
  while (cap < need) cap *= 2;
1749
1949
  const ty = new Uint8Array(cap);
1750
- ty.set(_soaTy);
1751
- _soaTy = ty;
1950
+ ty.set(_soaTypes);
1951
+ _soaTypes = ty;
1752
1952
  const st = new Int32Array(cap);
1753
- st.set(_soaSt);
1754
- _soaSt = st;
1953
+ st.set(_soaStarts);
1954
+ _soaStarts = st;
1755
1955
  const en = new Int32Array(cap);
1756
- en.set(_soaEn);
1757
- _soaEn = en;
1956
+ en.set(_soaEnds);
1957
+ _soaEnds = en;
1758
1958
  const a0 = new Int32Array(cap);
1759
- a0.set(_soaA0);
1760
- _soaA0 = a0;
1959
+ a0.set(_soaAux0);
1960
+ _soaAux0 = a0;
1761
1961
  const a1 = new Int32Array(cap);
1762
- a1.set(_soaA1);
1763
- _soaA1 = a1;
1962
+ a1.set(_soaAux1);
1963
+ _soaAux1 = a1;
1764
1964
  const a2 = new Int32Array(cap);
1765
- a2.set(_soaA2);
1766
- _soaA2 = a2;
1965
+ a2.set(_soaAux2);
1966
+ _soaAux2 = a2;
1767
1967
  const fl = new Uint8Array(cap);
1768
- fl.set(_soaFl);
1769
- _soaFl = fl;
1770
- _soaCap = cap;
1968
+ fl.set(_soaFlags);
1969
+ _soaFlags = fl;
1970
+ const ls = new Int32Array(cap);
1971
+ ls.set(_soaListStarts);
1972
+ _soaListStarts = ls;
1973
+ const ll = new Int32Array(cap);
1974
+ ll.set(_soaListLens);
1975
+ _soaListLens = ll;
1976
+ _soaCapacity = cap;
1771
1977
  };
1772
1978
  /** @type {(type: number, start: number, end: number) => Node} */
1773
- const _soaAlloc = (type, start, end) => {
1979
+ const _soaAllocNode = (type, start, end) => {
1774
1980
  // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1775
1981
  // so 0 must stay reserved for "no node".
1776
- const i = _soaN + 1;
1777
- if (i >= _soaCap) _soaGrow(i + 1);
1778
- _soaTy[i] = type;
1779
- _soaSt[i] = start;
1780
- _soaEn[i] = end;
1781
- _soaFl[i] = 0;
1782
- // Clear list slots so a reused id never exposes a previous node's children.
1783
- _soaL0[i] = null;
1784
- _soaL1[i] = null;
1785
- _soaL2[i] = null;
1786
- _soaN = i;
1787
- return _ref(i);
1982
+ // Leaves never read the flag / list slots — `_soaAllocContainer` clears
1983
+ // them instead, keeping the dominant leaf allocation at three writes.
1984
+ const i = _soaNodeCount + 1;
1985
+ if (i >= _soaCapacity) _soaGrow(i + 1);
1986
+ _soaTypes[i] = type;
1987
+ _soaStarts[i] = start;
1988
+ _soaEnds[i] = end;
1989
+ _soaNodeCount = i;
1990
+ return _nodeRef(i);
1991
+ };
1992
+ /** @type {(type: number, start: number, end: number) => Node} */
1993
+ const _soaAllocContainer = (type, start, end) => {
1994
+ const r = _soaAllocNode(type, start, end);
1995
+ const i = _nodeIndex(r);
1996
+ _soaFlags[i] = 0;
1997
+ // Clear list slots so a reused id never exposes a previous node's children
1998
+ // (content lists are flat spans, so zeroing the length suffices).
1999
+ _soaListLens[i] = 0;
2000
+ _soaDeclarationLists[i] = null;
2001
+ _soaChildRuleLists[i] = null;
2002
+ return r;
1788
2003
  };
1789
2004
  // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
1790
2005
  // one-char prefix, url uses its content range. Shared by the parser's
@@ -1794,65 +2009,107 @@ const _soaAlloc = (type, start, end) => {
1794
2009
  * @returns {string} raw token value
1795
2010
  */
1796
2011
  const _soaValueOf = (i) => {
1797
- const ty = _soaTy[i];
2012
+ const ty = _soaTypes[i];
1798
2013
  if (ty === T_HASH || ty === T_AT_KEYWORD) {
1799
- return _soaInput.slice(_soaSt[i] + 1, _soaEn[i]);
2014
+ return _soaInput.slice(_soaStarts[i] + 1, _soaEnds[i]);
1800
2015
  }
1801
- if (ty === T_URL) return _soaInput.slice(_soaA0[i], _soaA1[i]);
1802
- return _soaInput.slice(_soaSt[i], _soaEn[i]);
2016
+ if (ty === T_URL) return _soaInput.slice(_soaAux0[i], _soaAux1[i]);
2017
+ return _soaInput.slice(_soaStarts[i], _soaEnds[i]);
2018
+ };
2019
+ // Module-level constants for the same reason as the `_objSet*` set above.
2020
+ /** @type {typeof _makeUrl} */
2021
+ const _soaMakeUrl = (start, end, cs, ce) => {
2022
+ const r = _soaAllocNode(T_URL, start, end);
2023
+ _soaAux0[_nodeIndex(r)] = cs;
2024
+ _soaAux1[_nodeIndex(r)] = ce;
2025
+ return r;
2026
+ };
2027
+ /** @type {typeof _makeStylesheet} */
2028
+ const _soaMakeStylesheet = (start) =>
2029
+ _soaAllocContainer(T_STYLESHEET, start, start);
2030
+ // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
2031
+ /** @type {typeof _setName} */
2032
+ const _soaSetName = (r, ns, ne) => {
2033
+ _soaAux0[_nodeIndex(r)] = ne;
2034
+ };
2035
+ /** @type {typeof _setEnd} */
2036
+ const _soaSetEnd = (r, v) => {
2037
+ _soaEnds[_nodeIndex(r)] = v;
2038
+ };
2039
+ /** @type {typeof _setBlock} */
2040
+ const _soaSetBlock = (r, bs, be) => {
2041
+ const i = _nodeIndex(r);
2042
+ _soaAux1[i] = bs;
2043
+ _soaAux2[i] = be;
2044
+ };
2045
+ /** @type {typeof _setImportant} */
2046
+ const _soaSetImportant = (r) => {
2047
+ _soaFlags[_nodeIndex(r)] |= 1;
1803
2048
  };
2049
+ // A simple block's token is derived from its opening char on read.
2050
+ /** @type {typeof _setToken} */
2051
+ const _soaSetToken = (r, ch) => {};
2052
+ /** @type {typeof _setValue} */
2053
+ const _soaSetValue = (r, list) => {
2054
+ // Seal the finished list: copy its refs into the flat buffer and hand the
2055
+ // scratch array back to the pool. The caller never touches `list` again.
2056
+ const i = _nodeIndex(r);
2057
+ const len = list.length;
2058
+ const start = _soaFlatTop;
2059
+ if (start + len > _soaFlat.length) _soaFlatGrow(start + len);
2060
+ for (let k = 0; k < len; k++) {
2061
+ _soaFlat[start + k] = _nodeIndex(list[k]);
2062
+ }
2063
+ _soaFlatTop = start + len;
2064
+ _soaListStarts[i] = start;
2065
+ _soaListLens[i] = len;
2066
+ list.length = 0;
2067
+ _listPool.push(list);
2068
+ };
2069
+ /** @type {typeof _setBody} */
2070
+ const _soaSetBody = (r, decls, childRules) => {
2071
+ const i = _nodeIndex(r);
2072
+ _soaDeclarationLists[i] = decls;
2073
+ _soaChildRuleLists[i] = childRules;
2074
+ };
2075
+ /** @type {typeof _nodeTypeOf} */
2076
+ const _soaNodeTypeOf = (r) => _soaTypes[_nodeIndex(r)];
2077
+ /** @type {typeof _nodeStartOf} */
2078
+ const _soaNodeStartOf = (r) => _soaStarts[_nodeIndex(r)];
2079
+ /** @type {typeof _nodeValueOf} */
2080
+ const _soaNodeValueOf = (r) => _soaValueOf(_nodeIndex(r));
2081
+ /** @type {typeof _nodeTokenOf} */
2082
+ const _soaNodeTokenOf = (r) =>
2083
+ /** @type {SimpleBlockToken} */ (_soaInput[_soaStarts[_nodeIndex(r)]]);
1804
2084
  /**
1805
2085
  * @param {string} input source
1806
2086
  * @param {LocConverter} lc loc converter
1807
2087
  */
1808
2088
  const useSoaBackend = (input, lc) => {
1809
2089
  _soaInput = input;
1810
- _soaLC = lc;
1811
- _soaN = 0;
1812
- _mkLeaf = (type, start, end) => _soaAlloc(type, start, end);
1813
- _mkUrl = (start, end, cs, ce) => {
1814
- const r = _soaAlloc(T_URL, start, end);
1815
- _soaA0[_ix(r)] = cs;
1816
- _soaA1[_ix(r)] = ce;
1817
- return r;
1818
- };
1819
- _mkContainer = (type, start, end) => _soaAlloc(type, start, end);
1820
- _mkStylesheet = (start) => _soaAlloc(T_STYLESHEET, start, start);
1821
- // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
1822
- _setName = (r, v, ns, ne) => {
1823
- _soaA0[_ix(r)] = ne;
1824
- };
1825
- _setEnd = (r, v) => {
1826
- _soaEn[_ix(r)] = v;
1827
- };
1828
- _setBlock = (r, bs, be) => {
1829
- const i = _ix(r);
1830
- _soaA1[i] = bs;
1831
- _soaA2[i] = be;
1832
- };
1833
- _setImportant = (r) => {
1834
- _soaFl[_ix(r)] |= 1;
1835
- };
1836
- // A simple block's token is derived from its opening char on read.
1837
- _setToken = (r, ch) => {};
1838
- _setValue = (r, list) => {
1839
- _soaL0[_ix(r)] = list;
1840
- };
1841
- _setPrelude = (r, list) => {
1842
- _soaL0[_ix(r)] = list;
1843
- };
1844
- _setBody = (r, decls, childRules) => {
1845
- const i = _ix(r);
1846
- _soaL1[i] = decls;
1847
- _soaL2[i] = childRules;
1848
- };
1849
- _setRules = (r, list) => {
1850
- _soaL0[_ix(r)] = list;
1851
- };
1852
- _nType = (r) => _soaTy[_ix(r)];
1853
- _nStart = (r) => _soaSt[_ix(r)];
1854
- _nValue = (r) => _soaValueOf(_ix(r));
1855
- _nToken = (r) => /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(r)]]);
2090
+ _soaLocConverter = lc;
2091
+ _soaNodeCount = 0;
2092
+ _soaFlatTop = 0;
2093
+ _soaActive = true;
2094
+ // The alloc primitives are the slot functions directly — no wrapper hop.
2095
+ _makeLeaf = _soaAllocNode;
2096
+ _makeUrl = _soaMakeUrl;
2097
+ _makeContainer = _soaAllocContainer;
2098
+ _makeStylesheet = _soaMakeStylesheet;
2099
+ _setName = _soaSetName;
2100
+ _setEnd = _soaSetEnd;
2101
+ _setBlock = _soaSetBlock;
2102
+ _setImportant = _soaSetImportant;
2103
+ _setToken = _soaSetToken;
2104
+ _setValue = _soaSetValue;
2105
+ // value / prelude / rules all land in the one content-list slot.
2106
+ _setPrelude = _soaSetValue;
2107
+ _setBody = _soaSetBody;
2108
+ _setRules = _soaSetValue;
2109
+ _nodeTypeOf = _soaNodeTypeOf;
2110
+ _nodeStartOf = _soaNodeStartOf;
2111
+ _nodeValueOf = _soaNodeValueOf;
2112
+ _nodeTokenOf = _soaNodeTokenOf;
1856
2113
  };
1857
2114
 
1858
2115
  /**
@@ -1866,9 +2123,9 @@ const tokenToNode = (t) => {
1866
2123
  // plain leaf whose node type comes from the map.
1867
2124
  if (tt === TT_URL) {
1868
2125
  const ut = /** @type {CssUrlToken} */ (t);
1869
- return _mkUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
2126
+ return _makeUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
1870
2127
  }
1871
- return _mkLeaf(_ttToNodeType[tt], t.start, t.end);
2128
+ return _makeLeaf(_ttToNodeType[tt], t.start, t.end);
1872
2129
  };
1873
2130
 
1874
2131
  /**
@@ -1918,12 +2175,12 @@ class TokenStream {
1918
2175
  /** @type {number} */
1919
2176
  this._commentHigh = pos;
1920
2177
  // Single reused token the lexer writes into on the `next` path — see
1921
- // `MutableToken`. `_next` points at it when a token is cached, else
1922
- // `undefined` (re-tokenize on next read).
2178
+ // `MutableToken`. `_hasNext` marks it cached — a boolean instead of an
2179
+ // object slot, so caching a token never pays a GC write barrier.
1923
2180
  /** @type {MutableToken} */
1924
2181
  this._tok = createToken();
1925
- /** @type {MutableToken | undefined} the next token, lazily tokenized */
1926
- this._next = undefined;
2182
+ /** @type {boolean} whether `_tok` holds the (lazily tokenized) next token */
2183
+ this._hasNext = false;
1927
2184
  /** @type {number[]} byte offsets to rewind to */
1928
2185
  this._marks = [];
1929
2186
  }
@@ -1937,14 +2194,14 @@ class TokenStream {
1937
2194
  * @returns {MutableToken} the next token
1938
2195
  */
1939
2196
  next() {
1940
- if (this._next === undefined) {
2197
+ if (!this._hasNext) {
1941
2198
  const input = this.input;
1942
2199
  const tok = this._tok;
1943
2200
  let pos = this._pos;
1944
2201
  for (;;) {
1945
2202
  const t = readToken(input, pos, tok);
1946
2203
  if (t === undefined) {
1947
- this._next = fill(tok, TT_EOF, input.length, input.length);
2204
+ fill(tok, TT_EOF, input.length, input.length);
1948
2205
  break;
1949
2206
  }
1950
2207
  if (t.type === TT_COMMENT) {
@@ -1955,11 +2212,11 @@ class TokenStream {
1955
2212
  pos = t.end;
1956
2213
  continue;
1957
2214
  }
1958
- this._next = t;
1959
2215
  break;
1960
2216
  }
2217
+ this._hasNext = true;
1961
2218
  }
1962
- return /** @type {MutableToken} */ (this._next);
2219
+ return this._tok;
1963
2220
  }
1964
2221
 
1965
2222
  /**
@@ -1972,7 +2229,7 @@ class TokenStream {
1972
2229
  const t = this.next();
1973
2230
  if (t.type !== TT_EOF) {
1974
2231
  this._pos = t.end;
1975
- this._next = undefined;
2232
+ this._hasNext = false;
1976
2233
  }
1977
2234
  return t;
1978
2235
  }
@@ -1986,7 +2243,7 @@ class TokenStream {
1986
2243
  const t = this.next();
1987
2244
  if (t.type !== TT_EOF) {
1988
2245
  this._pos = t.end;
1989
- this._next = undefined;
2246
+ this._hasNext = false;
1990
2247
  }
1991
2248
  }
1992
2249
 
@@ -2006,7 +2263,7 @@ class TokenStream {
2006
2263
  */
2007
2264
  restoreMark() {
2008
2265
  this._pos = /** @type {number} */ (this._marks.pop());
2009
- this._next = undefined;
2266
+ this._hasNext = false;
2010
2267
  }
2011
2268
 
2012
2269
  /**
@@ -2045,7 +2302,7 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
2045
2302
  // algorithms that drive the tokenizer.
2046
2303
 
2047
2304
  /**
2048
- * @typedef {object} ParseListOptions
2305
+ * @typedef {object} ParseOptions
2049
2306
  * @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
2050
2307
  */
2051
2308
 
@@ -2053,17 +2310,18 @@ const normalizeIntoTokenStream = (input, pos, onComment) =>
2053
2310
  * Parse a stylesheet, CSS Syntax Level 3
2054
2311
  * [§5.3.4](https://drafts.csswg.org/css-syntax/#parse-stylesheet).
2055
2312
  * @param {string | TokenStream} input source string or an existing token stream
2056
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2313
+ * @param {number=} pos start position (string input only)
2314
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2057
2315
  * @returns {Stylesheet} the parsed stylesheet
2058
2316
  */
2059
- const parseAStylesheet = (input, comment) => {
2317
+ const parseAStylesheet = (input, pos = 0, options = {}) => {
2060
2318
  // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
2061
2319
  // 2. Normalize input, and set input to the result.
2062
- const ts = normalizeIntoTokenStream(input, 0, comment);
2320
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2063
2321
  useObjectBackend(ts.locConverter);
2064
2322
  // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
2065
2323
  const start = ts.next().start;
2066
- const stylesheet = /** @type {Stylesheet} */ (_mkStylesheet(start));
2324
+ const stylesheet = /** @type {Stylesheet} */ (_makeStylesheet(start));
2067
2325
  // 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
2068
2326
  _setRules(stylesheet, consumeAStylesheetsContents(ts));
2069
2327
  _setEnd(stylesheet, ts.next().start);
@@ -2078,12 +2336,13 @@ const parseAStylesheet = (input, comment) => {
2078
2336
  * declarations are parse errors (never produced) and top-level CDO (`<!--`) /
2079
2337
  * CDC (`-->`) tokens are discarded.
2080
2338
  * @param {string | TokenStream} input source string or an existing token stream
2081
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2339
+ * @param {number=} pos start position (string input only)
2340
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2082
2341
  * @returns {Rule[]} top-level rules
2083
2342
  */
2084
- const parseAStylesheetsContents = (input, comment) => {
2343
+ const parseAStylesheetsContents = (input, pos = 0, options = {}) => {
2085
2344
  // 1. Normalize input, and set input to the result.
2086
- const ts = normalizeIntoTokenStream(input, 0, comment);
2345
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2087
2346
  useObjectBackend(ts.locConverter);
2088
2347
  // 2. Consume a stylesheet’s contents from input, and return the result.
2089
2348
  return consumeAStylesheetsContents(ts);
@@ -2094,12 +2353,12 @@ const parseAStylesheetsContents = (input, comment) => {
2094
2353
  * [§5.3.6](https://drafts.csswg.org/css-syntax/#parse-block-contents).
2095
2354
  * @param {string | TokenStream} input source string or an existing token stream
2096
2355
  * @param {number=} pos start position (string input only; just past the opening `{`, or 0)
2097
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2356
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2098
2357
  * @returns {{ decls: Declaration[], rules: Rule[] }} block decls + rules
2099
2358
  */
2100
- const parseABlocksContents = (input, pos, comment) => {
2359
+ const parseABlocksContents = (input, pos = 0, options = {}) => {
2101
2360
  // 1. Normalize input, and set input to the result.
2102
- const ts = normalizeIntoTokenStream(input, pos, comment);
2361
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2103
2362
  useObjectBackend(ts.locConverter);
2104
2363
  // 2. Consume a block’s contents from input, and return the result.
2105
2364
  return consumeABlocksContents(ts);
@@ -2112,12 +2371,12 @@ const parseABlocksContents = (input, pos, comment) => {
2112
2371
  * whitespace; `undefined` (syntax error) otherwise.
2113
2372
  * @param {string | TokenStream} input source string or an existing token stream
2114
2373
  * @param {number=} pos start position (string input only)
2115
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2374
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2116
2375
  * @returns {Rule | undefined} the parsed rule
2117
2376
  */
2118
- const parseARule = (input, pos, comment) => {
2377
+ const parseARule = (input, pos = 0, options = {}) => {
2119
2378
  // 1. Normalize input, and set input to the result.
2120
- const ts = normalizeIntoTokenStream(input, pos, comment);
2379
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2121
2380
  useObjectBackend(ts.locConverter);
2122
2381
  // 2. Discard whitespace from input.
2123
2382
  while (ts.next().type === TT_WHITESPACE) ts.discard();
@@ -2143,12 +2402,12 @@ const parseARule = (input, pos, comment) => {
2143
2402
  * [§5.3.8](https://drafts.csswg.org/css-syntax/#parse-declaration).
2144
2403
  * @param {string | TokenStream} input source string or an existing token stream
2145
2404
  * @param {number=} pos start position (string input only)
2146
- * @param {((input: string, start: number, end: number) => number)=} comment optional comment-token callback (string input only)
2405
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2147
2406
  * @returns {Declaration | undefined} the parsed declaration, or undefined
2148
2407
  */
2149
- const parseADeclaration = (input, pos, comment) => {
2408
+ const parseADeclaration = (input, pos = 0, options = {}) => {
2150
2409
  // 1. Normalize input, and set input to the result.
2151
- const ts = normalizeIntoTokenStream(input, pos, comment);
2410
+ const ts = normalizeIntoTokenStream(input, pos, options.comment);
2152
2411
  useObjectBackend(ts.locConverter);
2153
2412
  // 2. Discard whitespace from input.
2154
2413
  while (ts.next().type === TT_WHITESPACE) ts.discard();
@@ -2160,10 +2419,10 @@ const parseADeclaration = (input, pos, comment) => {
2160
2419
  * 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").
2161
2420
  * @param {string | TokenStream} input source string or an existing token stream
2162
2421
  * @param {number=} pos start position (string input only)
2163
- * @param {{ comment?: (input: string, start: number, end: number) => number }=} options optional comment-token callback (string input only)
2422
+ * @param {ParseOptions=} options optional comment-token callback (string input only)
2164
2423
  * @returns {ComponentValue | undefined} the parsed component value, or `undefined` on empty / trailing-garbage input
2165
2424
  */
2166
- const parseAComponentValue = (input, pos, options = {}) => {
2425
+ const parseAComponentValue = (input, pos = 0, options = {}) => {
2167
2426
  // 1. Normalize input, and set input to the result.
2168
2427
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2169
2428
  useObjectBackend(ts.locConverter);
@@ -2185,27 +2444,28 @@ const parseAComponentValue = (input, pos, options = {}) => {
2185
2444
  * [§5.3.10](https://drafts.csswg.org/css-syntax/#parse-list-of-components).
2186
2445
  * @param {string | TokenStream} input source string or an existing token stream
2187
2446
  * @param {number=} pos start position (string input only)
2188
- * @param {ParseListOptions=} options comment callback
2447
+ * @param {ParseOptions=} options comment callback
2189
2448
  * @returns {ComponentValue[]} component values
2190
2449
  */
2191
- const parseAListOfComponentValues = (input, pos, options = {}) => {
2450
+ const parseAListOfComponentValues = (input, pos = 0, options = {}) => {
2192
2451
  // 1. Normalize input, and set input to the result.
2193
2452
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2194
2453
  useObjectBackend(ts.locConverter);
2195
2454
  // 2. Consume a list of component values from input, and return the result.
2196
- return consumeAListOfComponentValues(ts);
2455
+ // (`null` needs `bailOnCurly`, which is not passed here.)
2456
+ return /** @type {ComponentValue[]} */ (consumeAListOfComponentValues(ts));
2197
2457
  };
2198
2458
 
2199
2459
  /**
2200
2460
  * 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.
2201
2461
  * @param {string | TokenStream} input source string or an existing token stream
2202
2462
  * @param {number=} pos start position (string input only)
2203
- * @param {ParseListOptions=} options comment callback
2463
+ * @param {ParseOptions=} options comment callback
2204
2464
  * @returns {ComponentValue[][]} comma-separated groups of component values
2205
2465
  */
2206
2466
  const parseACommaSeparatedListOfComponentValues = (
2207
2467
  input,
2208
- pos,
2468
+ pos = 0,
2209
2469
  options = {}
2210
2470
  ) => {
2211
2471
  // 1. Normalize input, and set input to the result.
@@ -2217,7 +2477,11 @@ const parseACommaSeparatedListOfComponentValues = (
2217
2477
  // 3. While input is not empty:
2218
2478
  while (ts.next().type !== TT_EOF) {
2219
2479
  // 3.1. Consume a list of component values from input, with <comma-token> as the stop token, and append the result to groups.
2220
- groups.push(consumeAListOfComponentValues(ts, TT_COMMA));
2480
+ groups.push(
2481
+ /** @type {ComponentValue[]} */ (
2482
+ consumeAListOfComponentValues(ts, TT_COMMA)
2483
+ )
2484
+ );
2221
2485
  // 3.2 Discard a token from input.
2222
2486
  ts.discard();
2223
2487
  }
@@ -2292,19 +2556,20 @@ const consumeAnAtRule = (ts, nested = false) => {
2292
2556
  // 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.
2293
2557
  const head = ts.consume();
2294
2558
  const rule = /** @type {AtRule} */ (
2295
- _mkContainer(T_AT_RULE, head.start, head.end)
2296
- );
2297
- _setName(
2298
- rule,
2299
- ts.input.slice(head.start + 1, head.end),
2300
- head.start,
2301
- head.end
2559
+ _makeContainer(T_AT_RULE, head.start, head.end)
2302
2560
  );
2303
- const prelude = _list();
2304
- _setPrelude(rule, prelude);
2561
+ _setName(rule, head.start, head.end);
2562
+ // Sealed (`_setPrelude`) at each return — the SoA backend consumes the
2563
+ // scratch array when sealing, so it must be complete by then.
2564
+ const prelude = _soaActive ? _soaList() : [];
2305
2565
  // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2306
2566
  // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
2307
2567
 
2568
+ // Like `consumeAQualifiedRule`: skip mode scans the prelude without
2569
+ // materializing it (url tokens / functions kept so `@import url(…)` still
2570
+ // resolves); the block boundary is found by scanning, not the prelude nodes.
2571
+ const skip = _skipAtRulePrelude;
2572
+
2308
2573
  // Process input
2309
2574
  for (;;) {
2310
2575
  const t = ts.next();
@@ -2314,6 +2579,7 @@ const consumeAnAtRule = (ts, nested = false) => {
2314
2579
  // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
2315
2580
  if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
2316
2581
  ts.discard();
2582
+ _setPrelude(rule, prelude);
2317
2583
  _setEnd(rule, t.start);
2318
2584
  return rule;
2319
2585
  }
@@ -2322,15 +2588,18 @@ const consumeAnAtRule = (ts, nested = false) => {
2322
2588
  // Otherwise, consume a token and append the result to rule’s prelude.
2323
2589
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2324
2590
  if (nested) {
2591
+ _setPrelude(rule, prelude);
2325
2592
  _setEnd(rule, t.start);
2326
2593
  return rule;
2327
2594
  }
2328
- prelude.push(consumeATokenAsNode(ts));
2595
+ const node = consumeATokenAsNode(ts);
2596
+ if (!skip) prelude.push(node);
2329
2597
  continue;
2330
2598
  }
2331
2599
  // <{-token>
2332
2600
  // Consume a block from input, and assign the result to rule's declarations and child rules.
2333
2601
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2602
+ _setPrelude(rule, prelude);
2334
2603
  const block = consumeABlock(ts);
2335
2604
  _setBody(rule, block.decls, block.rules);
2336
2605
  _setBlock(rule, block.blockStart, block.blockEnd);
@@ -2340,7 +2609,15 @@ const consumeAnAtRule = (ts, nested = false) => {
2340
2609
 
2341
2610
  // anything else
2342
2611
  // Consume a component value from input and append the returned value to rule’s prelude.
2343
- prelude.push(consumeAComponentValue(ts, t));
2612
+ const node = consumeAComponentValue(ts, t);
2613
+ if (!skip) {
2614
+ prelude.push(node);
2615
+ } else if (
2616
+ _nodeTypeOf(node) === T_FUNCTION ||
2617
+ _nodeTypeOf(node) === T_URL
2618
+ ) {
2619
+ prelude.push(node);
2620
+ }
2344
2621
  }
2345
2622
  };
2346
2623
 
@@ -2369,13 +2646,21 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2369
2646
  const start = ts.next().start;
2370
2647
  // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
2371
2648
  const rule = /** @type {QualifiedRule} */ (
2372
- _mkContainer(T_QUALIFIED_RULE, start, start)
2649
+ _makeContainer(T_QUALIFIED_RULE, start, start)
2373
2650
  );
2374
- const prelude = _list();
2375
- _setPrelude(rule, prelude);
2651
+ // Sealed (`_setPrelude`) at the `{` exit — the only path that returns the
2652
+ // rule; the parse-error exits abandon the scratch unsealed.
2653
+ const prelude = _soaActive ? _soaList() : [];
2376
2654
  // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2377
2655
  // defaults (no block until a `{` is reached).
2378
2656
 
2657
+ // Skip mode leaves `prelude` empty (selector text is recovered from the
2658
+ // rule's byte range, not its nodes); `first`/`second` still track the first
2659
+ // two non-whitespace tokens the `--foo: {` disambiguation below needs.
2660
+ const skip = _skipSelectorPrelude;
2661
+ let first = /** @type {Node} */ (/** @type {unknown} */ (0));
2662
+ let second = /** @type {Node} */ (/** @type {unknown} */ (0));
2663
+
2379
2664
  // Process input
2380
2665
  for (;;) {
2381
2666
  const t = ts.next();
@@ -2389,7 +2674,13 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2389
2674
  // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
2390
2675
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2391
2676
  if (nested) return undefined;
2392
- prelude.push(consumeATokenAsNode(ts));
2677
+ const node = consumeATokenAsNode(ts);
2678
+ if (skip) {
2679
+ if (!first) first = node;
2680
+ else if (!second) second = node;
2681
+ } else {
2682
+ prelude.push(node);
2683
+ }
2393
2684
  continue;
2394
2685
  }
2395
2686
  // <{-token>
@@ -2399,31 +2690,33 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2399
2690
  // (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
2400
2691
  // Otherwise, consume a block from input, and let child rules be the result.
2401
2692
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2402
- let firstIdx = 0;
2403
- /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2404
- while (
2405
- firstIdx < prelude.length &&
2406
- _nType(prelude[firstIdx]) === T_WHITESPACE
2407
- ) {
2408
- firstIdx++;
2409
- }
2410
- let secondIdx = firstIdx + 1;
2411
- while (
2412
- secondIdx < prelude.length &&
2413
- _nType(prelude[secondIdx]) === T_WHITESPACE
2414
- ) {
2415
- secondIdx++;
2693
+ if (!skip) {
2694
+ let firstIdx = 0;
2695
+ /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2696
+ while (
2697
+ firstIdx < prelude.length &&
2698
+ _nodeTypeOf(prelude[firstIdx]) === T_WHITESPACE
2699
+ ) {
2700
+ firstIdx++;
2701
+ }
2702
+ let secondIdx = firstIdx + 1;
2703
+ while (
2704
+ secondIdx < prelude.length &&
2705
+ _nodeTypeOf(prelude[secondIdx]) === T_WHITESPACE
2706
+ ) {
2707
+ secondIdx++;
2708
+ }
2709
+ first = prelude[firstIdx];
2710
+ second = prelude[secondIdx];
2416
2711
  }
2417
- const first = prelude[firstIdx];
2418
- const second = prelude[secondIdx];
2419
2712
  if (
2420
2713
  first &&
2421
- _nType(first) === T_IDENT &&
2714
+ _nodeTypeOf(first) === T_IDENT &&
2422
2715
  // Test the source bytes directly — avoids forcing the lazy `value`
2423
2716
  // slice just to check the `--` custom-property prefix.
2424
- ts.input.startsWith("--", _nStart(first)) &&
2717
+ ts.input.startsWith("--", _nodeStartOf(first)) &&
2425
2718
  second &&
2426
- _nType(second) === T_COLON
2719
+ _nodeTypeOf(second) === T_COLON
2427
2720
  ) {
2428
2721
  /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
2429
2722
  if (nested) {
@@ -2433,6 +2726,7 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2433
2726
  }
2434
2727
  return undefined;
2435
2728
  }
2729
+ _setPrelude(rule, prelude);
2436
2730
  const block = consumeABlock(ts);
2437
2731
  _setBody(rule, block.decls, block.rules);
2438
2732
  _setBlock(rule, block.blockStart, block.blockEnd);
@@ -2442,7 +2736,21 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2442
2736
 
2443
2737
  // anything else
2444
2738
  // Consume a component value from input and append the result to rule’s prelude.
2445
- prelude.push(consumeAComponentValue(ts, t));
2739
+ const node = consumeAComponentValue(ts, t);
2740
+ if (skip) {
2741
+ // Keep only url-bearing nodes (url tokens, or functions that may hold a
2742
+ // url like `:unknown(url(x))`) so the url visitor still rewrites them;
2743
+ // other selector tokens have no non-modules consumer, so drop them.
2744
+ const ty = _nodeTypeOf(node);
2745
+ if (ty === T_FUNCTION || ty === T_URL) prelude.push(node);
2746
+ // Track the first two non-whitespace tokens for the disambiguation above.
2747
+ if (t.type !== TT_WHITESPACE) {
2748
+ if (!first) first = node;
2749
+ else if (!second) second = node;
2750
+ }
2751
+ } else {
2752
+ prelude.push(node);
2753
+ }
2446
2754
  }
2447
2755
  };
2448
2756
 
@@ -2466,7 +2774,8 @@ const consumeABlock = (ts) => {
2466
2774
  };
2467
2775
 
2468
2776
  /**
2469
- * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>` (the prerequisite for consume-a-declaration steps 1 + 3)? Used by `consumeABlocksContents` to skip a declaration attempt that would otherwise call consume-the-remnants-of-a-bad-declaration and be undone by `restoreMark`. A webpack fast-path, not a spec algorithm — so the implementation is free to peek cheaply. It scans raw code points after the cached ident for the next significant one (skipping whitespace + comments) rather than tokenizing them, and never advances the stream: the ident stays cached in `_next` for `consumeADeclaration` to reuse instead of re-tokenizing the property name. Comments skipped here fire `onComment` later, when the chosen consume algorithm tokenizes past them (once, in source order, as before).
2777
+ * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>`?
2778
+ * Peeks raw code points without advancing; comments still fire `onComment` later.
2470
2779
  * @param {TokenStream} ts token stream
2471
2780
  * @returns {boolean} true if consume-a-declaration's step 1 + step 3 would both succeed on the current input
2472
2781
  */
@@ -2517,8 +2826,12 @@ const declarationStartLikely = (ts) => {
2517
2826
  const consumeABlocksContents = (ts, onNode) => {
2518
2827
  /** @type {Declaration[]} */
2519
2828
  const decls = [];
2520
- /** @type {Rule[]} */
2521
- const rules = [];
2829
+ // Child rules are the common empty case (most rules carry only declarations),
2830
+ // so `rules` is allocated lazily and returned as the shared frozen
2831
+ // `_EMPTY_LIST` when nothing was appended — one fewer array per rule. `decls`
2832
+ // stays eager so the hot declaration append keeps a branch-free `push`.
2833
+ /** @type {Rule[] | null} */
2834
+ let rules = null;
2522
2835
 
2523
2836
  // Process input:
2524
2837
  for (;;) {
@@ -2532,7 +2845,7 @@ const consumeABlocksContents = (ts, onNode) => {
2532
2845
  // <EOF-token> / <}-token>
2533
2846
  // Return decls and rules.
2534
2847
  else if (t.type === TT_EOF || t.type === TT_RIGHT_CURLY_BRACKET) {
2535
- return { decls, rules };
2848
+ return { decls, rules: rules || _EMPTY_LIST };
2536
2849
  }
2537
2850
  // <at-keyword-token>
2538
2851
  // Consume an at-rule from input, with nested set to true. If a rule was returned, append it to rules.
@@ -2540,7 +2853,7 @@ const consumeABlocksContents = (ts, onNode) => {
2540
2853
  const atRule = consumeAnAtRule(ts, true);
2541
2854
  if (atRule) {
2542
2855
  if (onNode) onNode(atRule);
2543
- else rules.push(atRule);
2856
+ else (rules || (rules = [])).push(atRule);
2544
2857
  }
2545
2858
  }
2546
2859
  // anything else
@@ -2563,7 +2876,7 @@ const consumeABlocksContents = (ts, onNode) => {
2563
2876
  const rule = consumeAQualifiedRule(ts, TT_SEMICOLON, true);
2564
2877
  if (rule) {
2565
2878
  if (onNode) onNode(rule);
2566
- else rules.push(rule);
2879
+ else (rules || (rules = [])).push(rule);
2567
2880
  }
2568
2881
  }
2569
2882
  }
@@ -2611,14 +2924,14 @@ const consumeADeclaration = (ts, nested = false) => {
2611
2924
  // name "" / nameStart / nameEnd (= start) / important (false) keep their
2612
2925
  // `Container` defaults; `value` is set unconditionally at step 5 below.
2613
2926
  const decl = /** @type {Declaration} */ (
2614
- _mkContainer(T_DECLARATION, start, start)
2927
+ _makeContainer(T_DECLARATION, start, start)
2615
2928
  );
2616
2929
 
2617
2930
  // 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.
2618
2931
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2619
2932
  if (ts.next().type === TT_IDENTIFIER) {
2620
2933
  const head = ts.consume();
2621
- _setName(decl, input.slice(head.start, head.end), head.start, head.end);
2934
+ _setName(decl, head.start, head.end);
2622
2935
  } else {
2623
2936
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2624
2937
  return undefined;
@@ -2639,23 +2952,39 @@ const consumeADeclaration = (ts, nested = false) => {
2639
2952
  // 4. Discard whitespace from input.
2640
2953
  while (ts.next().type === TT_WHITESPACE) ts.discard();
2641
2954
 
2955
+ // Step 8's custom-property test, computed early so the value parse can bail.
2956
+ const isCustomProperty = input.startsWith("--", start);
2957
+
2642
2958
  // 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.
2643
- const value = consumeAListOfComponentValues(ts, TT_SEMICOLON, nested);
2644
- _setValue(decl, value);
2959
+ // A nested non-custom declaration bails on a top-level `{` — step 8 would
2960
+ // reject it and the caller restores its mark, so parsing the block (the
2961
+ // entire nested-rule body, re-parsed as a qualified rule after the
2962
+ // restore) would be pure waste.
2963
+ const value = consumeAListOfComponentValues(
2964
+ ts,
2965
+ TT_SEMICOLON,
2966
+ nested,
2967
+ nested && !isCustomProperty
2968
+ );
2969
+ if (value === null) return undefined;
2970
+ // `_setValue` waits until step 9: steps 6-8 still trim / scan the scratch,
2971
+ // and the SoA backend consumes it when sealing.
2645
2972
  _setEnd(decl, ts.next().start);
2646
2973
 
2647
2974
  // 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.
2648
2975
  {
2649
2976
  let last = value.length - 1;
2650
- while (last >= 0 && _nType(value[last]) === T_WHITESPACE) last--;
2977
+ while (last >= 0 && _nodeTypeOf(value[last]) === T_WHITESPACE) last--;
2651
2978
  let prev = last - 1;
2652
- while (prev >= 0 && _nType(value[prev]) === T_WHITESPACE) prev--;
2979
+ while (prev >= 0 && _nodeTypeOf(value[prev]) === T_WHITESPACE) prev--;
2980
+ // `!` delim first: it's almost always absent, and `_nodeValueOf` allocates
2981
+ // a slice — this order pays it only for genuine `!important` candidates.
2653
2982
  if (
2654
2983
  prev >= 0 &&
2655
- _nType(value[last]) === T_IDENT &&
2656
- equalsLowerCase(_nValue(value[last]), "important") &&
2657
- _nType(value[prev]) === T_DELIM &&
2658
- input.charCodeAt(_nStart(value[prev])) === CC_EXCLAMATION
2984
+ _nodeTypeOf(value[prev]) === T_DELIM &&
2985
+ input.charCodeAt(_nodeStartOf(value[prev])) === CC_EXCLAMATION &&
2986
+ _nodeTypeOf(value[last]) === T_IDENT &&
2987
+ equalsLowerCase(_nodeValueOf(value[last]), "important")
2659
2988
  ) {
2660
2989
  _setImportant(decl);
2661
2990
  value.length = prev;
@@ -2663,7 +2992,10 @@ const consumeADeclaration = (ts, nested = false) => {
2663
2992
  }
2664
2993
 
2665
2994
  // 7. While the last item in decl's value is a <whitespace-token>, remove that token.
2666
- while (value.length > 0 && _nType(value[value.length - 1]) === T_WHITESPACE) {
2995
+ while (
2996
+ value.length > 0 &&
2997
+ _nodeTypeOf(value[value.length - 1]) === T_WHITESPACE
2998
+ ) {
2667
2999
  value.pop();
2668
3000
  }
2669
3001
 
@@ -2671,17 +3003,17 @@ const consumeADeclaration = (ts, nested = false) => {
2671
3003
  // Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, return nothing.
2672
3004
  // (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.)
2673
3005
  // 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.)
2674
- const isCustomProperty = input.startsWith("--", start);
2675
3006
  if (!isCustomProperty) {
2676
3007
  for (let i = 0; i < value.length; i++) {
2677
3008
  const v = value[i];
2678
- if (_nType(v) === T_SIMPLE_BLOCK && _nToken(v) === "{") {
3009
+ if (_nodeTypeOf(v) === T_SIMPLE_BLOCK && _nodeTokenOf(v) === "{") {
2679
3010
  return undefined;
2680
3011
  }
2681
3012
  }
2682
3013
  }
2683
3014
 
2684
3015
  // 9. Return decl.
3016
+ _setValue(decl, value);
2685
3017
  return decl;
2686
3018
  };
2687
3019
 
@@ -2690,11 +3022,16 @@ const consumeADeclaration = (ts, nested = false) => {
2690
3022
  * @param {TokenStream} ts token stream
2691
3023
  * @param {number=} stopToken token type that terminates the list (left unconsumed)
2692
3024
  * @param {boolean=} nested true inside a `{}` block — a top-level `}` ends the list (left unconsumed)
2693
- * @returns {ComponentValue[]} consumed component values
2694
- */
2695
- const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2696
- /** @type {ComponentValue[]} */
2697
- const values = [];
3025
+ * @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
3026
+ * @returns {ComponentValue[] | null} consumed component values, or `null` when `bailOnCurly` hit
3027
+ */
3028
+ const consumeAListOfComponentValues = (
3029
+ ts,
3030
+ stopToken,
3031
+ nested = false,
3032
+ bailOnCurly = false
3033
+ ) => {
3034
+ const values = /** @type {ComponentValue[]} */ (_soaActive ? _soaList() : []);
2698
3035
  // Process input
2699
3036
  for (;;) {
2700
3037
  const t = ts.next();
@@ -2710,12 +3047,33 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2710
3047
  // Otherwise, this is a parse error. Consume a token from input and append the result to values.
2711
3048
  if (t.type === TT_RIGHT_CURLY_BRACKET) {
2712
3049
  if (nested) return values;
2713
- values.push(consumeATokenAsNode(ts));
3050
+ const closer = consumeATokenAsNode(ts);
3051
+ // Keep unless the type is explicitly marked skip (1); an out-of-range
3052
+ // lookup on a short `skip.types` yields `undefined`, which must not drop.
3053
+ if (!_skipActive || _skipTypes[_nodeTypeOf(closer)] !== 1) {
3054
+ values.push(closer);
3055
+ }
2714
3056
  continue;
2715
3057
  }
3058
+ // A top-level `{` dooms the list for a bailing caller — stop before the
3059
+ // whole block is parsed only to be thrown away on the caller's restore.
3060
+ if (bailOnCurly && t.type === TT_LEFT_CURLY_BRACKET) return null;
2716
3061
  // anything else
2717
3062
  // Consume a component value from input, and append the result to values.
2718
- values.push(consumeAComponentValue(ts, t));
3063
+ // Skipped leaf types short-circuit before materializing: no SoA slot is
3064
+ // written and no node is built (blocks / functions never skip here).
3065
+ const tt = t.type;
3066
+ if (
3067
+ _skipActive &&
3068
+ tt !== TT_FUNCTION &&
3069
+ !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
3070
+ _skipTypes[_ttToNodeType[tt]] === 1
3071
+ ) {
3072
+ ts.consume();
3073
+ continue;
3074
+ }
3075
+ const node = consumeAComponentValue(ts, t);
3076
+ if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) values.push(node);
2719
3077
  }
2720
3078
  };
2721
3079
 
@@ -2761,11 +3119,11 @@ const consumeASimpleBlock = (ts) => {
2761
3119
 
2762
3120
  // 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.
2763
3121
  const block = /** @type {SimpleBlock} */ (
2764
- _mkContainer(T_SIMPLE_BLOCK, open.start, open.end)
3122
+ _makeContainer(T_SIMPLE_BLOCK, open.start, open.end)
2765
3123
  );
2766
3124
  _setToken(block, token);
2767
- const val = _list();
2768
- _setValue(block, val);
3125
+ // Sealed (`_setValue`) at the return, once complete.
3126
+ const val = _soaActive ? _soaList() : [];
2769
3127
 
2770
3128
  // Discard a token from input.
2771
3129
  ts.discard();
@@ -2779,6 +3137,7 @@ const consumeASimpleBlock = (ts) => {
2779
3137
  // Discard a token from input. Return block.
2780
3138
  if (t.type === TT_EOF || t.type === ending) {
2781
3139
  ts.discard();
3140
+ _setValue(block, val);
2782
3141
  _setEnd(block, t.end);
2783
3142
  return block;
2784
3143
  }
@@ -2795,16 +3154,15 @@ const consumeASimpleBlock = (ts) => {
2795
3154
  * @returns {FunctionNode | undefined} the consumed function node
2796
3155
  */
2797
3156
  const consumeAFunction = (ts) => {
2798
- const { input } = ts;
2799
3157
  // Assert (spec): the next token is a <function-token>.
2800
3158
  // 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.
2801
3159
  const tFn = ts.consume();
2802
3160
  const fn = /** @type {FunctionNode} */ (
2803
- _mkContainer(T_FUNCTION, tFn.start, tFn.end)
3161
+ _makeContainer(T_FUNCTION, tFn.start, tFn.end)
2804
3162
  );
2805
- _setName(fn, input.slice(tFn.start, tFn.end - 1), tFn.start, tFn.end - 1);
2806
- const val = _list();
2807
- _setValue(fn, val);
3163
+ _setName(fn, tFn.start, tFn.end - 1);
3164
+ // Sealed (`_setValue`) at the return, once complete.
3165
+ const val = _soaActive ? _soaList() : [];
2808
3166
 
2809
3167
  // Process input
2810
3168
  for (;;) {
@@ -2815,13 +3173,26 @@ const consumeAFunction = (ts) => {
2815
3173
  // <)-token>
2816
3174
  // Discard a token from input. Return function.
2817
3175
  ts.discard();
3176
+ _setValue(fn, val);
2818
3177
  _setEnd(fn, t.end);
2819
3178
  return fn;
2820
3179
  }
2821
3180
 
2822
3181
  // anything else
2823
3182
  // Consume a component value from input and append the result to function’s value.
2824
- val.push(consumeAComponentValue(ts, t));
3183
+ // Same pre-materialization skip as `consumeAListOfComponentValues`.
3184
+ const tt = t.type;
3185
+ if (
3186
+ _skipActive &&
3187
+ tt !== TT_FUNCTION &&
3188
+ !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
3189
+ _skipTypes[_ttToNodeType[tt]] === 1
3190
+ ) {
3191
+ ts.consume();
3192
+ continue;
3193
+ }
3194
+ const node = consumeAComponentValue(ts, t);
3195
+ if (!_skipActive || _skipTypes[_nodeTypeOf(node)] !== 1) val.push(node);
2825
3196
  }
2826
3197
  };
2827
3198
 
@@ -2879,13 +3250,18 @@ const _escapeIdentifier = (str) => {
2879
3250
  }
2880
3251
  output = lastFlush === 0 ? str : output + str.slice(lastFlush);
2881
3252
 
2882
- const firstChar = str.charAt(0);
2883
-
2884
- if (/^-[-\d]/.test(output)) {
3253
+ // `-` and digits are class 0 (never escaped above), so testing `str`'s lead
3254
+ // char codes is equivalent to regexes over `output` — and keeps the common
3255
+ // nothing-to-do call regex-free.
3256
+ const first = str.charCodeAt(0);
3257
+ if (
3258
+ first === CC_HYPHEN_MINUS &&
3259
+ (str.charCodeAt(1) === CC_HYPHEN_MINUS || _isDigit(str.charCodeAt(1)))
3260
+ ) {
2885
3261
  output = `\\-${output.slice(1)}`;
2886
- } else if (/\d/.test(firstChar)) {
3262
+ } else if (_isDigit(first)) {
2887
3263
  // A leading digit becomes `\3<digit> `, another `\HEX ` run to clean up.
2888
- output = `\\3${firstChar} ${output.slice(1)}`;
3264
+ output = `\\3${str.charAt(0)} ${output.slice(1)}`;
2889
3265
  needSpaceFix = true;
2890
3266
  }
2891
3267
 
@@ -2919,7 +3295,6 @@ const _escapeIdentifier = (str) => {
2919
3295
  */
2920
3296
  const gobbleHex = (str, start) => {
2921
3297
  let hex = "";
2922
- let spaceTerminated = false;
2923
3298
 
2924
3299
  for (let i = 0; i < 6; i++) {
2925
3300
  const code = str.charCodeAt(start + i);
@@ -2928,8 +3303,6 @@ const gobbleHex = (str, start) => {
2928
3303
  (code >= 48 && code <= 57) ||
2929
3304
  (code >= 65 && code <= 70) ||
2930
3305
  (code >= 97 && code <= 102);
2931
- // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
2932
- spaceTerminated = code === 32;
2933
3306
  if (!valid) break;
2934
3307
  // parseInt below is case-insensitive, so keep the original char.
2935
3308
  hex += str[start + i];
@@ -2937,6 +3310,16 @@ const gobbleHex = (str, start) => {
2937
3310
 
2938
3311
  if (hex.length === 0) return undefined;
2939
3312
 
3313
+ // One trailing whitespace terminates the escape, matching the tokenizer's
3314
+ // `_consumeAnEscapedCodePoint` — including after a full 6-digit escape, for
3315
+ // any CSS whitespace (not just space), plus the extra LF of a CRLF pair.
3316
+ // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
3317
+ let consumed = hex.length;
3318
+ const trail = str.charCodeAt(start + hex.length);
3319
+ if (_isWhiteSpace(trail)) {
3320
+ consumed = consumeExtraNewline(trail, str, start + hex.length + 1) - start;
3321
+ }
3322
+
2940
3323
  const codePoint = Number.parseInt(hex, 16);
2941
3324
  const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
2942
3325
 
@@ -2944,13 +3327,10 @@ const gobbleHex = (str, start) => {
2944
3327
  // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
2945
3328
  // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
2946
3329
  if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
2947
- return ["�", hex.length + (spaceTerminated ? 1 : 0)];
3330
+ return ["�", consumed];
2948
3331
  }
2949
3332
 
2950
- return [
2951
- String.fromCodePoint(codePoint),
2952
- hex.length + (spaceTerminated ? 1 : 0)
2953
- ];
3333
+ return [String.fromCodePoint(codePoint), consumed];
2954
3334
  };
2955
3335
 
2956
3336
  /**
@@ -2996,14 +3376,74 @@ const _unescapeIdentifier = (str) => {
2996
3376
  const escapeIdentifier = makeCacheable(_escapeIdentifier);
2997
3377
  const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
2998
3378
 
3379
+ // A url-token / url-string value's escaped newlines (`url("im\<newline>g.png")`).
3380
+ const STRING_MULTILINE = /\\[\n\r\f]/g;
3381
+ // Leading / trailing CSS whitespace inside a quoted url value.
3382
+ const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
3383
+ // One CSS escape: `\` + up to 6 hex digits (+ optional whitespace) or any char.
3384
+ const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
3385
+
3386
+ /**
3387
+ * Normalize a url value (a url-token's content or a url string's body) into
3388
+ * the form requests are resolved from: escaped newlines removed (string form),
3389
+ * edge whitespace trimmed, CSS escapes and percent-encoding decoded
3390
+ * (`data:` URIs excepted).
3391
+ * @param {string} str url string
3392
+ * @param {boolean} isString is url wrapped in quotes
3393
+ * @returns {string} normalized url
3394
+ */
3395
+ const normalizeUrl = (str, isString) => {
3396
+ // Fast paths: skip the regex engine for the common URL with no escape and
3397
+ // no edge whitespace (e.g. `./img.png`). Each guard is equivalent to the
3398
+ // regex being a no-op.
3399
+ // Remove escaped newlines from a string-token url like `url("im\<newline>g.png")`.
3400
+ if (isString && str.includes("\\")) {
3401
+ str = str.replace(STRING_MULTILINE, "");
3402
+ }
3403
+
3404
+ // Remove unnecessary spaces from `url(" img.png ")`
3405
+ if (
3406
+ str.length !== 0 &&
3407
+ (_isWhiteSpace(str.charCodeAt(0)) ||
3408
+ _isWhiteSpace(str.charCodeAt(str.length - 1)))
3409
+ ) {
3410
+ str = str.replace(TRIM_WHITE_SPACES, "");
3411
+ }
3412
+
3413
+ // Unescape
3414
+ if (str.includes("\\")) {
3415
+ str = str.replace(UNESCAPE, (match) => {
3416
+ if (match.length > 2) {
3417
+ return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
3418
+ }
3419
+ return match[1];
3420
+ });
3421
+ }
3422
+
3423
+ // Char-code gate so the dominant non-`data:` url skips the regex test.
3424
+ if ((str.charCodeAt(0) | 0x20) === CC_LOWER_D && /^data:/i.test(str)) {
3425
+ return str;
3426
+ }
3427
+
3428
+ if (str.includes("%")) {
3429
+ // Convert `url('%2E/img.png')` -> `url('./img.png')`
3430
+ try {
3431
+ str = decodeURIComponent(str);
3432
+ } catch (_err) {
3433
+ // Ignore
3434
+ }
3435
+ }
3436
+
3437
+ return str;
3438
+ };
3439
+
2999
3440
  // CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
3000
3441
  // re-exported so consumers keep importing them from this module.
3001
3442
  /**
3002
- * @typedef {import("../util/SourceProcessor").VisitorContext} VisitorContext
3003
- * @typedef {import("../util/SourceProcessor").VisitorFn<Node>} VisitorFn
3004
- * @typedef {import("../util/SourceProcessor").VisitorBucket<Node>} VisitorBucket
3005
- * @typedef {import("../util/SourceProcessor").VisitorMap<Node>} VisitorMap
3006
- * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<Node>} CompiledVisitorMap
3443
+ * @typedef {import("../util/SourceProcessor").VisitorFn<CssPath>} VisitorFn
3444
+ * @typedef {import("../util/SourceProcessor").VisitorBucket<CssPath>} VisitorBucket
3445
+ * @typedef {import("../util/SourceProcessor").VisitorMap<CssPath>} VisitorMap
3446
+ * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<CssPath>} CompiledVisitorMap
3007
3447
  */
3008
3448
 
3009
3449
  /**
@@ -3027,10 +3467,149 @@ const TOP_LEVEL_CONSUMERS = {
3027
3467
  /**
3028
3468
  * @typedef {object} CssProcessOptions
3029
3469
  * @property {LocConverter=} locConverter shared loc converter (default a fresh one over the input)
3030
- * @property {((input: string, start: number, end: number) => number)=} comment comment-token callback
3031
3470
  * @property {boolean=} recurseBlocks walk into block bodies' nested rules (default true)
3032
3471
  * @property {("stylesheet" | "block-contents")=} as which top-level production to consume the source as (see `TOP_LEVEL_CONSUMERS`): `"stylesheet"` (default) or `"block-contents"` (a block's contents, e.g. an HTML `style` attribute)
3472
+ * @property {SkipOptions=} skip what the grammar may leave un-materialized to go faster — safe only for parts nothing reads in the active parse; default skip nothing
3473
+ */
3474
+
3475
+ /**
3476
+ * `CssProcessOptions.skip`: two independent axes, so each reads unambiguously.
3477
+ * @typedef {object} SkipOptions
3478
+ * @property {Uint8Array=} types component-value node types to drop from declaration value / function-arg lists (indexed by `NodeType`, 1 = skip; build with `buildSkipSet`)
3479
+ * @property {boolean=} selectorPrelude drop qualified-rule (selector) preludes — the rule and its block are still produced (default false)
3480
+ * @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
3481
+ */
3482
+
3483
+ // Per-parse walk state in module slots (same pattern as `_skip*`) so the walk
3484
+ // functions below are module-level constants: one function identity across
3485
+ // parses keeps the recursive per-node call sites monomorphic and drops the
3486
+ // per-parse closure allocations.
3487
+ /** @typedef {import("../util/SourceProcessor").CompiledVisitorBucket<CssPath>} CompiledVisitorBucket */
3488
+ /** @type {CompiledVisitorMap} */
3489
+ let _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3490
+ let _recurseBlocks = true;
3491
+ /** @type {CompiledVisitorBucket | undefined} */
3492
+ let _commentBucket;
3493
+
3494
+ // Comments reach the visitor map through `NodeType.Comment` instead of a
3495
+ // side callback. They fire during tokenization — in source order among
3496
+ // comments, not interleaved with the node walk — on a transient SoA node so
3497
+ // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3498
+ // the tokenizer skips comments with zero overhead.
3499
+ /** @type {(input: string, start: number, end: number) => number} */
3500
+ const _grammarOnComment = (_input, start, end) => {
3501
+ const node = _soaAllocNode(T_COMMENT, start, end);
3502
+ _currentNode = node;
3503
+ _currentParent = null;
3504
+ const bucket = /** @type {CompiledVisitorBucket} */ (_commentBucket);
3505
+ const e = bucket.enter;
3506
+ for (let i = 0; i < e.length; i++) e[i](A);
3507
+ const x = bucket.exit;
3508
+ for (let i = 0; i < x.length; i++) x[i](A);
3509
+ return end;
3510
+ };
3511
+
3512
+ /**
3513
+ * Walk a component-value subtree; children are already materialized. Fetches
3514
+ * the node's visitor bucket once (reused for enter + exit) and uses index
3515
+ * loops — `for…of` would allocate an iterator per node on this hot path.
3516
+ * @param {Node} node component-value root
3517
+ * @param {Node | null} parent enclosing node
3518
+ */
3519
+ const _walkValue = (node, parent) => {
3520
+ const ty = _soaTypes[_nodeIndex(node)];
3521
+ const b = _visitors[ty];
3522
+ let skip = false;
3523
+ if (b !== undefined && b.enter.length !== 0) {
3524
+ _walkSkip = false;
3525
+ _currentNode = node;
3526
+ _currentParent = parent;
3527
+ const e = b.enter;
3528
+ for (let i = 0; i < e.length; i++) e[i](A);
3529
+ skip = _walkSkip;
3530
+ _walkSkip = false;
3531
+ }
3532
+ if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3533
+ const i0 = _nodeIndex(node);
3534
+ const vs = _soaListStarts[i0];
3535
+ const ve = vs + _soaListLens[i0];
3536
+ for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3537
+ }
3538
+ if (b !== undefined) {
3539
+ // Rebind: descending into children moved the path.
3540
+ _currentNode = node;
3541
+ _currentParent = parent;
3542
+ const x = b.exit;
3543
+ for (let i = 0; i < x.length; i++) x[i](A);
3544
+ }
3545
+ };
3546
+
3547
+ /**
3548
+ * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
3549
+ * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3550
+ * @param {Node} node structural-tree root
3551
+ * @param {Node | null} parent enclosing node
3552
+ */
3553
+ const _walkRule = (node, parent) => {
3554
+ const i0 = _nodeIndex(node);
3555
+ const ty = _soaTypes[i0];
3556
+ const b = _visitors[ty];
3557
+ let skip = false;
3558
+ if (b !== undefined && b.enter.length !== 0) {
3559
+ _walkSkip = false;
3560
+ _currentNode = node;
3561
+ _currentParent = parent;
3562
+ const e = b.enter;
3563
+ for (let i = 0; i < e.length; i++) e[i](A);
3564
+ skip = _walkSkip;
3565
+ _walkSkip = false;
3566
+ }
3567
+ if (!skip) {
3568
+ if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3569
+ const ps = _soaListStarts[i0];
3570
+ const pe = ps + _soaListLens[i0];
3571
+ for (let i = ps; i < pe; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3572
+ if (_recurseBlocks) {
3573
+ // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3574
+ const decls = _soaDeclarationLists[i0];
3575
+ if (decls) {
3576
+ for (let i = 0; i < decls.length; i++) _walkRule(decls[i], node);
3577
+ }
3578
+ const ch = _soaChildRuleLists[i0];
3579
+ if (ch) for (let i = 0; i < ch.length; i++) _walkRule(ch[i], node);
3580
+ }
3581
+ } else if (ty === T_DECLARATION) {
3582
+ const vs = _soaListStarts[i0];
3583
+ const ve = vs + _soaListLens[i0];
3584
+ for (let i = vs; i < ve; i++) _walkValue(_nodeRef(_soaFlat[i]), node);
3585
+ }
3586
+ }
3587
+ if (b !== undefined) {
3588
+ // Rebind: descending into children moved the path.
3589
+ _currentNode = node;
3590
+ _currentParent = parent;
3591
+ const x = b.exit;
3592
+ for (let i = 0; i < x.length; i++) x[i](A);
3593
+ }
3594
+ };
3595
+
3596
+ /**
3597
+ * The `grammar` streaming sink: walk one top-level node, then recycle the SoA
3598
+ * buffers for the next.
3599
+ * @param {Rule | Declaration} node top-level node
3033
3600
  */
3601
+ const _walkTopLevel = (node) => {
3602
+ _walkRule(node, null);
3603
+ if (_soaNodeCount > _soaPeak) _soaPeak = _soaNodeCount;
3604
+ if (_soaFlatTop > _soaFlatPeak) _soaFlatPeak = _soaFlatTop;
3605
+ _soaNodeCount = 0;
3606
+ _soaFlatTop = 0;
3607
+ };
3608
+
3609
+ // The SoA buffers grow to the largest single top-level rule ever parsed and
3610
+ // live at module level; above this capacity they are re-shrunk after a parse
3611
+ // so one pathological rule can't pin megabytes for the process lifetime.
3612
+ const _SOA_SHRINK_CAPACITY = 65536;
3034
3613
 
3035
3614
  /**
3036
3615
  * The CSS `SourceProcessor` grammar: consume top-level rules one at a time
@@ -3043,100 +3622,66 @@ const TOP_LEVEL_CONSUMERS = {
3043
3622
  * @param {CssProcessOptions} options process options
3044
3623
  */
3045
3624
  const grammar = (input, visitors, options) => {
3046
- const { comment } = options;
3047
3625
  const locConverter = options.locConverter || new LocConverter(input);
3048
3626
  useSoaBackend(input, locConverter);
3049
- const recurseBlocks = options.recurseBlocks !== false;
3050
-
3051
- // Babel's `path.skip()`, children-only. Reset per `enter` dispatch.
3052
- let skipFlag = false;
3053
- const visitorCtx = {
3054
- skipChildren() {
3055
- skipFlag = true;
3056
- }
3057
- };
3058
-
3059
- /**
3060
- * Walk a component-value subtree; children are already materialized. Fetches
3061
- * the node's visitor bucket once (reused for enter + exit) and uses index
3062
- * loops — `for…of` would allocate an iterator per node on this hot path.
3063
- * @param {Node} node component-value root
3064
- * @param {Node | null} parent enclosing node
3065
- */
3066
- const walkValue = (node, parent) => {
3067
- const ty = _soaTy[_ix(node)];
3068
- const b = visitors[ty];
3069
- let skip = false;
3070
- if (b !== undefined && b.enter.length !== 0) {
3071
- skipFlag = false;
3072
- const e = b.enter;
3073
- for (let i = 0; i < e.length; i++) e[i](node, parent, visitorCtx);
3074
- skip = skipFlag;
3075
- skipFlag = false;
3076
- }
3077
- if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3078
- const v = /** @type {Node[]} */ (_soaL0[_ix(node)]);
3079
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3080
- }
3081
- if (b !== undefined) {
3082
- const x = b.exit;
3083
- for (let i = 0; i < x.length; i++) x[i](node, parent, visitorCtx);
3084
- }
3085
- };
3086
-
3087
- /**
3088
- * Walk a structural subtree; an at-rule / qualified-rule's block was parsed
3089
- * eagerly (§5.4.4), so its `value` holds the nested rules / declarations.
3090
- * @param {Node} node structural-tree root
3091
- * @param {Node | null} parent enclosing node
3092
- */
3093
- const walkRule = (node, parent) => {
3094
- const i0 = _ix(node);
3095
- const ty = _soaTy[i0];
3096
- const b = visitors[ty];
3097
- let skip = false;
3098
- if (b !== undefined && b.enter.length !== 0) {
3099
- skipFlag = false;
3100
- const e = b.enter;
3101
- for (let i = 0; i < e.length; i++) e[i](node, parent, visitorCtx);
3102
- skip = skipFlag;
3103
- skipFlag = false;
3104
- }
3105
- if (!skip) {
3106
- if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3107
- const p = /** @type {Node[]} */ (_soaL0[i0]);
3108
- for (let i = 0; i < p.length; i++) walkValue(p[i], node);
3109
- if (recurseBlocks) {
3110
- // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3111
- const decls = _soaL1[i0];
3112
- if (decls) {
3113
- for (let i = 0; i < decls.length; i++) walkRule(decls[i], node);
3114
- }
3115
- const ch = _soaL2[i0];
3116
- if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
3117
- }
3118
- } else if (ty === T_DECLARATION) {
3119
- const v = /** @type {Node[]} */ (_soaL0[i0]);
3120
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3121
- }
3122
- }
3123
- if (b !== undefined) {
3124
- const x = b.exit;
3125
- for (let i = 0; i < x.length; i++) x[i](node, parent, visitorCtx);
3126
- }
3127
- };
3627
+ const skip = options.skip;
3628
+ _skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
3629
+ _skipActive = _skipTypes !== _NO_SKIP_TYPES;
3630
+ _skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
3631
+ _skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
3632
+ _recurseBlocks = options.recurseBlocks !== false;
3633
+ _visitors = visitors;
3634
+ _commentBucket = visitors[T_COMMENT];
3128
3635
 
3129
3636
  // Stream each top-level node (selected by `as`) to the walker the moment it's
3130
3637
  // consumed, rather than collecting them first — so the whole AST is never
3131
3638
  // held at once; peak heap is ~one top-level node's subtree.
3132
- const ts = new TokenStream(input, 0, locConverter, comment);
3639
+ const ts = new TokenStream(
3640
+ input,
3641
+ 0,
3642
+ locConverter,
3643
+ _commentBucket === undefined ? undefined : _grammarOnComment
3644
+ );
3133
3645
  const consume =
3134
3646
  TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
3135
3647
  consumeAStylesheetsContents;
3136
- consume(ts, (node) => {
3137
- walkRule(node, null);
3138
- _soaN = 0;
3139
- });
3648
+ try {
3649
+ consume(ts, _walkTopLevel);
3650
+ } finally {
3651
+ // Drop the module-level SoA references so the last parsed source (and
3652
+ // its LocConverter / child lists / visitors) don't stay alive between
3653
+ // parses.
3654
+ _soaInput = "";
3655
+ _soaLocConverter = /** @type {LocConverter} */ (
3656
+ /** @type {unknown} */ (null)
3657
+ );
3658
+ _soaDeclarationLists.length = 0;
3659
+ _soaChildRuleLists.length = 0;
3660
+ _soaFlatTop = 0;
3661
+ _listPool.length = 0;
3662
+ if (_soaFlat.length > _SOA_SHRINK_CAPACITY) {
3663
+ _soaFlatGrowHint = _soaFlatPeak;
3664
+ _soaFlat = new Int32Array(0);
3665
+ }
3666
+ _visitors = /** @type {CompiledVisitorMap} */ (/** @type {unknown} */ ([]));
3667
+ _commentBucket = undefined;
3668
+ if (_soaCapacity > _SOA_SHRINK_CAPACITY) {
3669
+ // +1: node ids are 1-based and grow fires at `id >= capacity`.
3670
+ _soaGrowHint = _soaPeak + 1;
3671
+ _soaCapacity = 0;
3672
+ _soaTypes = new Uint8Array(0);
3673
+ _soaStarts = new Int32Array(0);
3674
+ _soaEnds = new Int32Array(0);
3675
+ _soaAux0 = new Int32Array(0);
3676
+ _soaAux1 = new Int32Array(0);
3677
+ _soaAux2 = new Int32Array(0);
3678
+ _soaFlags = new Uint8Array(0);
3679
+ _soaListStarts = new Int32Array(0);
3680
+ _soaListLens = new Int32Array(0);
3681
+ }
3682
+ _soaPeak = 0;
3683
+ _soaFlatPeak = 0;
3684
+ }
3140
3685
  };
3141
3686
 
3142
3687
  /**
@@ -3144,17 +3689,68 @@ const grammar = (input, visitors, options) => {
3144
3689
  * `grammar`. Babel-style usage:
3145
3690
  *
3146
3691
  * ```
3147
- * processor.use({ [NodeType.AtRule]: (at) => {}, [NodeType.Declaration]: { enter, exit } });
3148
- * processor.process(source);
3692
+ * new SourceProcessor({ skip }).use({ [NodeType.AtRule]: (path) => {} }).process(source);
3149
3693
  * ```
3150
- * @extends {GenericSourceProcessor<Node, CssProcessOptions>}
3694
+ * @extends {GenericSourceProcessor<CssPath, CssProcessOptions>}
3151
3695
  */
3152
3696
  class SourceProcessor extends GenericSourceProcessor {
3153
- constructor() {
3154
- super(grammar);
3697
+ /**
3698
+ * @param {CssProcessOptions=} options default process options (`skip`, `as`, …) for every `process` call
3699
+ */
3700
+ constructor(options) {
3701
+ super(grammar, options);
3155
3702
  }
3156
3703
  }
3157
3704
 
3705
+ /**
3706
+ * Build a `SkipOptions.types` set (drop these component-value node types from
3707
+ * value / function-arg lists) from a list of `NodeType`s. Preludes are separate
3708
+ * (`SkipOptions.selectorPrelude` / `atRulePrelude`). The caller owns the safety
3709
+ * contract: only pass types nothing reads in the intended parse. Two
3710
+ * grammar-internal caveats beyond consumer needs: dropping both `Delim` and
3711
+ * `Ident` loses `!important` detection, and dropping `SimpleBlock` loses the
3712
+ * custom-property `{}`-value check (and its subtree). Precompute once per
3713
+ * configuration and reuse across parses.
3714
+ * @param {number[]} nodeTypes component-value node types to drop
3715
+ * @returns {Uint8Array} skip-types set indexed by `NodeType`
3716
+ */
3717
+ const buildSkipSet = (nodeTypes) => {
3718
+ const set = new Uint8Array(32);
3719
+ for (let i = 0; i < nodeTypes.length; i++) set[nodeTypes[i]] = 1;
3720
+ return set;
3721
+ };
3722
+
3723
+ /* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
3724
+ /**
3725
+ * The CSS path (Babel's `path` shape): the AST accessor with the walk's
3726
+ * current position on it — the single argument every visitor receives.
3727
+ * @typedef {typeof A} CssPath
3728
+ */
3729
+ /* eslint-enable jsdoc/require-template */
3730
+
3731
+ // A fresh (safely retainable) array view of a node's flat content span —
3732
+ // visitors that read `A.children` / `A.prelude` may keep the result.
3733
+ /** @type {(n: Node) => Node[]} */
3734
+ const _materializeList = (n) => {
3735
+ const i = _nodeIndex(n);
3736
+ const start = _soaListStarts[i];
3737
+ const len = _soaListLens[i];
3738
+ /** @type {Node[]} */
3739
+ const out = [];
3740
+ for (let k = 0; k < len; k++) out.push(_nodeRef(_soaFlat[start + k]));
3741
+ return out;
3742
+ };
3743
+
3744
+ // Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
3745
+ // `enter` dispatch, consumed by the walk.
3746
+ let _walkSkip = false;
3747
+ // The walk's current position (`A.node` / `A.parent` read these; module-level
3748
+ // so the accessor methods' defaults avoid self-referential `this` typing).
3749
+ /** @type {Node} */
3750
+ let _currentNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3751
+ /** @type {Node | null} */
3752
+ let _currentParent = null;
3753
+
3158
3754
  // AST field-access seam. Every AST-node field read by `CssParser` goes through
3159
3755
  // one of these accessors so the node representation can change underneath the
3160
3756
  // consumer without touching it. Today they are backed by the `Node` / `Token` /
@@ -3163,51 +3759,104 @@ class SourceProcessor extends GenericSourceProcessor {
3163
3759
  // consumer edit. `value` is the leaf-token string; container child lists are
3164
3760
  // `children` / `prelude` / `declarations` / `childRules`.
3165
3761
  const A = {
3166
- /** @type {(n: Node) => number} */
3167
- type: (n) => _soaTy[_ix(n)],
3168
- /** @type {(n: Node) => number} */
3169
- start: (n) => _soaSt[_ix(n)],
3170
- /** @type {(n: Node) => number} */
3171
- end: (n) => _soaEn[_ix(n)],
3172
- /** @type {(n: Node) => [number, number]} */
3173
- range: (n) => {
3174
- const i = _ix(n);
3175
- return [_soaSt[i], _soaEn[i]];
3762
+ // === path position (rebound by the walk before every visitor call) ===
3763
+ /**
3764
+ * @returns {Node} current node — only valid during a visitor callback
3765
+ */
3766
+ get node() {
3767
+ return _currentNode;
3768
+ },
3769
+ /**
3770
+ * @returns {Node | null} enclosing node (null = a top-level node)
3771
+ */
3772
+ get parent() {
3773
+ return _currentParent;
3774
+ },
3775
+ /** Stop the walk descending into the current node (enter only). */
3776
+ skipChildren() {
3777
+ _walkSkip = true;
3176
3778
  },
3177
- /** @type {(n: Node) => { start: { line: number, column: number }, end: { line: number, column: number } }} */
3178
- loc: (n) => {
3179
- const i = _ix(n);
3180
- const lc = _soaLC;
3181
- const s = lc.get(_soaSt[i]);
3779
+ // === field reads — `n` defaults to the current node ===
3780
+ /**
3781
+ * @param {Node=} n node
3782
+ * @returns {number} node type
3783
+ */
3784
+ type(n = _currentNode) {
3785
+ return _soaTypes[_nodeIndex(n)];
3786
+ },
3787
+ /**
3788
+ * @param {Node=} n node
3789
+ * @returns {number} start offset
3790
+ */
3791
+ start(n = _currentNode) {
3792
+ return _soaStarts[_nodeIndex(n)];
3793
+ },
3794
+ /**
3795
+ * @param {Node=} n node
3796
+ * @returns {number} end offset
3797
+ */
3798
+ end(n = _currentNode) {
3799
+ return _soaEnds[_nodeIndex(n)];
3800
+ },
3801
+ /**
3802
+ * @param {Node=} n node
3803
+ * @returns {[number, number]} start / end offsets
3804
+ */
3805
+ range(n = _currentNode) {
3806
+ const i = _nodeIndex(n);
3807
+ return [_soaStarts[i], _soaEnds[i]];
3808
+ },
3809
+ /**
3810
+ * @param {Node=} n node
3811
+ * @returns {{ start: { line: number, column: number }, end: { line: number, column: number } }} source location
3812
+ */
3813
+ loc(n = _currentNode) {
3814
+ const i = _nodeIndex(n);
3815
+ const lc = _soaLocConverter;
3816
+ const s = lc.get(_soaStarts[i]);
3182
3817
  const sl = s.line;
3183
3818
  const sc = s.column;
3184
- const e = lc.get(_soaEn[i]);
3819
+ const e = lc.get(_soaEnds[i]);
3185
3820
  return {
3186
3821
  start: { line: sl, column: sc },
3187
3822
  end: { line: e.line, column: e.column }
3188
3823
  };
3189
3824
  },
3190
- /** @type {(n: Node) => string} */
3191
- source: (n) => {
3192
- const i = _ix(n);
3193
- return _soaInput.slice(_soaSt[i], _soaEn[i]);
3825
+ /**
3826
+ * @param {Node=} n node
3827
+ * @returns {string} raw source slice
3828
+ */
3829
+ source(n = _currentNode) {
3830
+ const i = _nodeIndex(n);
3831
+ return _soaInput.slice(_soaStarts[i], _soaEnds[i]);
3832
+ },
3833
+ /**
3834
+ * @param {Node=} n node
3835
+ * @returns {string} raw token value
3836
+ */
3837
+ value(n = _currentNode) {
3838
+ return _soaValueOf(_nodeIndex(n));
3194
3839
  },
3195
- /** @type {(n: Node) => string} */
3196
- value: (n) => _soaValueOf(_ix(n)),
3197
- /** @type {(n: Node) => string} */
3198
- unescaped: (n) => {
3199
- const i = _ix(n);
3840
+ /**
3841
+ * @param {Node=} n node
3842
+ * @returns {string} unescaped token value
3843
+ */
3844
+ unescaped(n = _currentNode) {
3845
+ const i = _nodeIndex(n);
3200
3846
  const v = _soaValueOf(i);
3201
- return _soaTy[i] === T_STRING
3847
+ return _soaTypes[i] === T_STRING
3202
3848
  ? unescapeIdentifier(v.slice(1, -1))
3203
3849
  : unescapeIdentifier(v);
3204
3850
  },
3205
- /** @type {(n: Node) => string} */
3206
- typeFlag: (n) => {
3207
- const i = _ix(n);
3208
- if (_soaTy[i] === T_HASH) {
3851
+ /**
3852
+ * @param {Node=} n node
3853
+ * @returns {string} hash / numeric type flag
3854
+ */
3855
+ typeFlag(n = _currentNode) {
3856
+ const i = _nodeIndex(n);
3857
+ if (_soaTypes[i] === T_HASH) {
3209
3858
  const input = _soaInput;
3210
- const p = _soaSt[i] + 1;
3859
+ const p = _soaStarts[i] + 1;
3211
3860
  return _ifThreeCodePointsWouldStartAnIdentSequence(
3212
3861
  input,
3213
3862
  p,
@@ -3220,52 +3869,130 @@ const A = {
3220
3869
  }
3221
3870
  const v = _soaValueOf(i);
3222
3871
  return _typeFlagOf(
3223
- _soaTy[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3872
+ _soaTypes[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3873
+ );
3874
+ },
3875
+ /**
3876
+ * @param {Node=} n node
3877
+ * @returns {number} url content start offset
3878
+ */
3879
+ contentStart(n = _currentNode) {
3880
+ return _soaAux0[_nodeIndex(n)];
3881
+ },
3882
+ /**
3883
+ * @param {Node=} n node
3884
+ * @returns {number} url content end offset
3885
+ */
3886
+ contentEnd(n = _currentNode) {
3887
+ return _soaAux1[_nodeIndex(n)];
3888
+ },
3889
+ /**
3890
+ * @param {Node=} n node
3891
+ * @returns {string} rule / declaration / function name
3892
+ */
3893
+ name(n = _currentNode) {
3894
+ const i = _nodeIndex(n);
3895
+ return _soaTypes[i] === T_AT_RULE
3896
+ ? _soaInput.slice(_soaStarts[i] + 1, _soaAux0[i])
3897
+ : _soaInput.slice(_soaStarts[i], _soaAux0[i]);
3898
+ },
3899
+ /**
3900
+ * @param {Node=} n node
3901
+ * @returns {number} name start offset
3902
+ */
3903
+ nameStart(n = _currentNode) {
3904
+ return _soaStarts[_nodeIndex(n)];
3905
+ },
3906
+ /**
3907
+ * @param {Node=} n node
3908
+ * @returns {number} name end offset
3909
+ */
3910
+ nameEnd(n = _currentNode) {
3911
+ return _soaAux0[_nodeIndex(n)];
3912
+ },
3913
+ /**
3914
+ * @param {Node=} n node
3915
+ * @returns {string} unescaped name
3916
+ */
3917
+ unescapedName(n = _currentNode) {
3918
+ return unescapeIdentifier(A.name(n));
3919
+ },
3920
+ /**
3921
+ * @param {Node=} n node
3922
+ * @returns {ComponentValue[]} function / block children
3923
+ */
3924
+ children(n = _currentNode) {
3925
+ return /** @type {ComponentValue[]} */ (_materializeList(n));
3926
+ },
3927
+ /**
3928
+ * @param {Node=} n node
3929
+ * @returns {ComponentValue[]} rule prelude
3930
+ */
3931
+ prelude(n = _currentNode) {
3932
+ return /** @type {ComponentValue[]} */ (_materializeList(n));
3933
+ },
3934
+ /**
3935
+ * @param {Node=} n node
3936
+ * @returns {Declaration[] | null} block declarations
3937
+ */
3938
+ declarations(n = _currentNode) {
3939
+ return /** @type {Declaration[] | null} */ (
3940
+ _soaDeclarationLists[_nodeIndex(n)]
3224
3941
  );
3225
3942
  },
3226
- /** @type {(n: Node) => number} */
3227
- contentStart: (n) => _soaA0[_ix(n)],
3228
- /** @type {(n: Node) => number} */
3229
- contentEnd: (n) => _soaA1[_ix(n)],
3230
- /** @type {(n: Node) => string} */
3231
- name: (n) => {
3232
- const i = _ix(n);
3233
- return _soaTy[i] === T_AT_RULE
3234
- ? _soaInput.slice(_soaSt[i] + 1, _soaA0[i])
3235
- : _soaInput.slice(_soaSt[i], _soaA0[i]);
3943
+ /**
3944
+ * @param {Node=} n node
3945
+ * @returns {Rule[] | null} block child rules
3946
+ */
3947
+ childRules(n = _currentNode) {
3948
+ return /** @type {Rule[] | null} */ (_soaChildRuleLists[_nodeIndex(n)]);
3949
+ },
3950
+ /**
3951
+ * @param {Node=} n node
3952
+ * @returns {number} block start offset
3953
+ */
3954
+ blockStart(n = _currentNode) {
3955
+ return _soaAux1[_nodeIndex(n)];
3956
+ },
3957
+ /**
3958
+ * @param {Node=} n node
3959
+ * @returns {number} block end offset
3960
+ */
3961
+ blockEnd(n = _currentNode) {
3962
+ return _soaAux2[_nodeIndex(n)];
3963
+ },
3964
+ /**
3965
+ * @param {Node=} n node
3966
+ * @returns {boolean} `!important` flag
3967
+ */
3968
+ important(n = _currentNode) {
3969
+ return (_soaFlags[_nodeIndex(n)] & 1) !== 0;
3970
+ },
3971
+ /**
3972
+ * @param {Node=} n node
3973
+ * @returns {SimpleBlockToken} block opening token
3974
+ */
3975
+ blockToken(n = _currentNode) {
3976
+ return /** @type {SimpleBlockToken} */ (
3977
+ _soaInput[_soaStarts[_nodeIndex(n)]]
3978
+ );
3236
3979
  },
3237
- /** @type {(n: Node) => number} */
3238
- nameStart: (n) => _soaSt[_ix(n)],
3239
- /** @type {(n: Node) => number} */
3240
- nameEnd: (n) => _soaA0[_ix(n)],
3241
- /** @type {(n: Node) => string} */
3242
- unescapedName: (n) => unescapeIdentifier(A.name(n)),
3243
- /** @type {(n: Node) => ComponentValue[]} */
3244
- children: (n) => /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]),
3245
- /** @type {(n: Node) => ComponentValue[]} */
3246
- prelude: (n) => /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]),
3247
- /** @type {(n: Node) => Declaration[] | null} */
3248
- declarations: (n) => /** @type {Declaration[] | null} */ (_soaL1[_ix(n)]),
3249
- /** @type {(n: Node) => Rule[] | null} */
3250
- childRules: (n) => /** @type {Rule[] | null} */ (_soaL2[_ix(n)]),
3251
- /** @type {(n: Node) => number} */
3252
- blockStart: (n) => _soaA1[_ix(n)],
3253
- /** @type {(n: Node) => number} */
3254
- blockEnd: (n) => _soaA2[_ix(n)],
3255
- /** @type {(n: Node) => boolean} */
3256
- important: (n) => (_soaFl[_ix(n)] & 1) !== 0,
3257
- /** @type {(n: Node) => SimpleBlockToken} */
3258
- blockToken: (n) =>
3259
- /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(n)]]),
3260
3980
  // Writers — `CssParser` rewrites a rule's end / block-end when it folds an
3261
- // inline ICSS `:import` / `:export` body into a single dependency.
3262
- /** @type {(n: Node, v: number) => void} */
3263
- setEnd: (n, v) => {
3264
- _soaEn[_ix(n)] = v;
3981
+ // inline ICSS `:import` / `:export` body into a single dependency. The
3982
+ // node stays explicit here: writes should never be implicit on position.
3983
+ /**
3984
+ * @param {Node} n node
3985
+ * @param {number} v new end offset
3986
+ */
3987
+ setEnd(n, v) {
3988
+ _soaEnds[_nodeIndex(n)] = v;
3265
3989
  },
3266
- /** @type {(n: Node, v: number) => void} */
3267
- setBlockEnd: (n, v) => {
3268
- _soaA2[_ix(n)] = v;
3990
+ /**
3991
+ * @param {Node} n node
3992
+ * @param {number} v new block end offset
3993
+ */
3994
+ setBlockEnd(n, v) {
3995
+ _soaAux2[_nodeIndex(n)] = v;
3269
3996
  }
3270
3997
  };
3271
3998
 
@@ -3307,8 +4034,14 @@ module.exports.TT_URL = TT_URL;
3307
4034
  module.exports.TT_WHITESPACE = TT_WHITESPACE;
3308
4035
  module.exports.Token = Token;
3309
4036
  module.exports.TokenStream = TokenStream;
4037
+ module.exports.buildSkipSet = buildSkipSet;
3310
4038
  module.exports.equalsLowerCase = equalsLowerCase;
3311
4039
  module.exports.escapeIdentifier = escapeIdentifier;
4040
+ module.exports.isDashedIdentifier = isDashedIdentifier;
4041
+ // CSS Syntax §4.2 "whitespace" (space / tab / newline / CR / FF) — the
4042
+ // tokenizer's whitespace class, exported under the spec's name.
4043
+ module.exports.isWhitespace = _isWhiteSpace;
4044
+ module.exports.normalizeUrl = normalizeUrl;
3312
4045
  module.exports.parseABlocksContents = parseABlocksContents;
3313
4046
  module.exports.parseACommaSeparatedListOfComponentValues =
3314
4047
  parseACommaSeparatedListOfComponentValues;
@@ -3318,5 +4051,8 @@ module.exports.parseAListOfComponentValues = parseAListOfComponentValues;
3318
4051
  module.exports.parseARule = parseARule;
3319
4052
  module.exports.parseAStylesheet = parseAStylesheet;
3320
4053
  module.exports.parseAStylesheetsContents = parseAStylesheetsContents;
4054
+ module.exports.rangeEquals = rangeEquals;
4055
+ module.exports.rangeEqualsLowerCase = rangeEqualsLowerCase;
3321
4056
  module.exports.readToken = readToken;
4057
+ module.exports.toLowerCaseIfNeeded = toLowerCaseIfNeeded;
3322
4058
  module.exports.unescapeIdentifier = unescapeIdentifier;