webpack 5.108.2 → 5.108.4

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.
package/lib/css/syntax.js CHANGED
@@ -209,7 +209,6 @@ const CC_EXCLAMATION = "!".charCodeAt(0);
209
209
  const CC_UPPER_A = "A".charCodeAt(0);
210
210
  const CC_UPPER_F = "F".charCodeAt(0);
211
211
  const CC_UPPER_E = "E".charCodeAt(0);
212
- const CC_UPPER_U = "U".charCodeAt(0);
213
212
  const CC_UPPER_Z = "Z".charCodeAt(0);
214
213
  const CC_0 = "0".charCodeAt(0);
215
214
  const CC_9 = "9".charCodeAt(0);
@@ -298,17 +297,14 @@ const _isSpace = (cc) => cc === CC_SPACE || cc === CC_TAB;
298
297
  // rarer tab / newline tests.
299
298
  const _isWhiteSpace = (cc) => _isSpace(cc) || _isNewline(cc);
300
299
 
301
- /**
302
- * ident-start code point per the spec: a letter, a non-ASCII code point,
303
- * or U+005F LOW LINE (`_`).
304
- * @param {number} cc char code
305
- * @returns {boolean} true, if cc is an ident-start code point
306
- */
307
- const isIdentStartCodePoint = (cc) =>
308
- (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
309
- (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
310
- cc === CC_LOW_LINE ||
311
- cc >= 0x80;
300
+ // Whitespace membership table for the run-consumption loop — one load instead
301
+ // of up to five compares per char. EOF (NaN) / non-ASCII index to undefined.
302
+ const _wsTable = new Uint8Array(128);
303
+ _wsTable[CC_SPACE] = 1;
304
+ _wsTable[CC_TAB] = 1;
305
+ _wsTable[CC_LINE_FEED] = 1;
306
+ _wsTable[CC_CARRIAGE_RETURN] = 1;
307
+ _wsTable[CC_FORM_FEED] = 1;
312
308
 
313
309
  /**
314
310
  * @param {number} cc char code
@@ -482,11 +478,17 @@ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
482
478
  * @returns {number} position just past the last ident-sequence code point
483
479
  */
484
480
  const _consumeAnIdentSequence = (input, pos) => {
481
+ // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
482
+ // are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the
483
+ // ASCII ident test is a single table load, and the escape test reads the
484
+ // following code point only when `cc` is a `\` (rare) instead of eagerly.
485
485
  for (;;) {
486
486
  const cc = input.charCodeAt(pos);
487
487
  pos++;
488
- if (_isIdentCodePoint(cc)) continue;
489
- if (_ifTwoCodePointsAreValidEscape(input, pos, cc)) {
488
+ if (cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc)) {
489
+ continue;
490
+ }
491
+ if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
490
492
  pos = _consumeAnEscapedCodePoint(input, pos);
491
493
  continue;
492
494
  }
@@ -619,7 +621,7 @@ const fill = (out, type, start, end) => {
619
621
  */
620
622
  function consumeSpace(input, pos, out) {
621
623
  const start = pos - 1;
622
- while (_isWhiteSpace(input.charCodeAt(pos))) pos++;
624
+ while (_wsTable[input.charCodeAt(pos)] === 1) pos++;
623
625
  return fill(out, TT_WHITESPACE, start, pos);
624
626
  }
625
627
 
@@ -950,6 +952,96 @@ function consumeReverseSolidus(input, pos, out) {
950
952
  return fill(out, TT_DELIM, pos - 1, pos);
951
953
  }
952
954
 
955
+ // `consumeAToken` dispatch: the §4 token rules keyed by the lead code point are
956
+ // === Tokenizer lead-character dispatch (CSS Syntax Level 3 §4 "consume a token") ===
957
+ //
958
+ // `consumeAToken` selects a sub-routine from the first ("lead") code point of each
959
+ // token. The §4 rules are keyed on specific code points (`"` `#` `(` digit
960
+ // ident-start …) that sit SPARSELY across the ASCII range, so a plain `switch (cc)`
961
+ // compiles to a jump table spanning U+0009..U+007D in which the most common lead —
962
+ // an ident-start letter — is not a case and reaches its handler only after the
963
+ // digit/whitespace tests miss. `_charClass` precomputes, for every ASCII code
964
+ // point, a dense handler id (`HC_*`, 0..12) so `consumeAToken` is one array load +
965
+ // a compact 13-entry jump table and idents dispatch directly. Non-ASCII
966
+ // (cc >= 128) is always ident-start per §4, so it skips the table.
967
+ //
968
+ // Extending for a spec change: repoint the code point in the build loop below; if
969
+ // it needs a new sub-routine, add an `HC_*` id, a `case` in `consumeAToken`, and a
970
+ // row here. This list is the authoritative "which lead code point dispatches
971
+ // where" map (§4 "consume a token", step by lead code point):
972
+ //
973
+ // HC_WHITESPACE whitespace U+0009 TAB U+000A LF U+000C FF U+000D CR U+0020 SPACE
974
+ // HC_STRING string start U+0022 " U+0027 '
975
+ // HC_SINGLE one-char token ( ) , : ; [ ] { } (its token type comes from `_singleTT`)
976
+ // HC_NUMBER_SIGN hash / delim U+0023 #
977
+ // HC_PLUS_SIGN number / delim U+002B +
978
+ // HC_HYPHEN_MINUS number / CDC / ident / delim U+002D -
979
+ // HC_FULL_STOP number / delim U+002E .
980
+ // HC_LESS_THAN CDO / delim U+003C <
981
+ // HC_AT_SIGN at-keyword / delim U+0040 @
982
+ // HC_REVERSE_SOLIDUS escape / delim U+005C \
983
+ // HC_DIGIT number U+0030..U+0039 0-9
984
+ // HC_IDENT ident-like U+0041..U+005A A-Z U+0061..U+007A a-z U+005F _ (plus cc >= 128)
985
+ // HC_DELIM anything else -> a single <delim-token>
986
+ //
987
+ // `_singleTT[cc]` is the token type for the HC_SINGLE code points (a second table
988
+ // so they share one handler instead of one `case` each). The default class 0 is
989
+ // the delim handler (anything not matched below), so it needs no named constant.
990
+ const HC_WHITESPACE = 1;
991
+ const HC_STRING = 2;
992
+ const HC_SINGLE = 3;
993
+ const HC_NUMBER_SIGN = 4;
994
+ const HC_PLUS_SIGN = 5;
995
+ const HC_HYPHEN_MINUS = 6;
996
+ const HC_FULL_STOP = 7;
997
+ const HC_LESS_THAN = 8;
998
+ const HC_AT_SIGN = 9;
999
+ const HC_REVERSE_SOLIDUS = 10;
1000
+ const HC_DIGIT = 11;
1001
+ const HC_IDENT = 12;
1002
+ const _charClass = new Uint8Array(128);
1003
+ const _singleTT = new Uint8Array(128);
1004
+ _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
1005
+ _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
1006
+ _singleTT[CC_COMMA] = TT_COMMA;
1007
+ _singleTT[CC_COLON] = TT_COLON;
1008
+ _singleTT[CC_SEMICOLON] = TT_SEMICOLON;
1009
+ _singleTT[CC_LEFT_SQUARE] = TT_LEFT_SQUARE_BRACKET;
1010
+ _singleTT[CC_RIGHT_SQUARE] = TT_RIGHT_SQUARE_BRACKET;
1011
+ _singleTT[CC_LEFT_CURLY] = TT_LEFT_CURLY_BRACKET;
1012
+ _singleTT[CC_RIGHT_CURLY] = TT_RIGHT_CURLY_BRACKET;
1013
+ // Each ASCII code point belongs to exactly one class; HC_SINGLE is seeded from
1014
+ // `_singleTT` above, the rest follow §4's lead-code-point rules, and everything
1015
+ // unmatched stays the delim class (0). Keep this in sync with the table above.
1016
+ for (let i = 0; i < 128; i++) {
1017
+ if (_singleTT[i] !== 0) {
1018
+ _charClass[i] = HC_SINGLE;
1019
+ } else if (_isWhiteSpace(i)) {
1020
+ _charClass[i] = HC_WHITESPACE;
1021
+ } else if (i === CC_QUOTATION_MARK || i === CC_APOSTROPHE) {
1022
+ _charClass[i] = HC_STRING;
1023
+ } else if (i === CC_NUMBER_SIGN) {
1024
+ _charClass[i] = HC_NUMBER_SIGN;
1025
+ } else if (i === CC_PLUS_SIGN) {
1026
+ _charClass[i] = HC_PLUS_SIGN;
1027
+ } else if (i === CC_HYPHEN_MINUS) {
1028
+ _charClass[i] = HC_HYPHEN_MINUS;
1029
+ } else if (i === CC_FULL_STOP) {
1030
+ _charClass[i] = HC_FULL_STOP;
1031
+ } else if (i === CC_LESS_THAN_SIGN) {
1032
+ _charClass[i] = HC_LESS_THAN;
1033
+ } else if (i === CC_AT_SIGN) {
1034
+ _charClass[i] = HC_AT_SIGN;
1035
+ } else if (i === CC_REVERSE_SOLIDUS) {
1036
+ _charClass[i] = HC_REVERSE_SOLIDUS;
1037
+ } else if (_isDigit(i)) {
1038
+ _charClass[i] = HC_DIGIT;
1039
+ } else if (_isIdentStartCodePointCC(i)) {
1040
+ _charClass[i] = HC_IDENT;
1041
+ }
1042
+ // else stays the delim class (0)
1043
+ }
1044
+
953
1045
  /**
954
1046
  * Per-character dispatcher. The outer loop has already advanced past
955
1047
  * the lead code point (`pos - 1` is the lead).
@@ -960,65 +1052,50 @@ function consumeReverseSolidus(input, pos, out) {
960
1052
  * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
961
1053
  */
962
1054
  function consumeAToken(input, pos, cc, out) {
963
- switch (cc) {
964
- case CC_LINE_FEED:
965
- case CC_CARRIAGE_RETURN:
966
- case CC_FORM_FEED:
967
- case CC_TAB:
968
- case CC_SPACE:
1055
+ // `u` / `U` would start a unicode-range token in the spec; those are not
1056
+ // produced, so they map to HC_IDENT and fall through to ident-like.
1057
+ switch (cc < 128 ? _charClass[cc] : HC_IDENT) {
1058
+ // Run of whitespace → one <whitespace-token>.
1059
+ case HC_WHITESPACE:
969
1060
  return consumeSpace(input, pos, out);
970
- case CC_QUOTATION_MARK:
971
- case CC_APOSTROPHE:
1061
+ // `"` / `'` → <string-token> (or <bad-string-token> on a raw newline).
1062
+ case HC_STRING:
972
1063
  return consumeAStringToken(input, pos, out);
973
- case CC_NUMBER_SIGN:
1064
+ // One-code-point token: its type is looked up in `_singleTT` (the `(` `)`
1065
+ // `,` `:` `;` `[` `]` `{` `}` set), so all of them share this arm.
1066
+ case HC_SINGLE:
1067
+ return fill(out, _singleTT[cc], pos - 1, pos);
1068
+ // `#` → <hash-token> if an ident/escape follows, else a <delim-token>.
1069
+ case HC_NUMBER_SIGN:
974
1070
  return consumeNumberSign(input, pos, out);
975
- case CC_LEFT_PARENTHESIS:
976
- return fill(out, TT_LEFT_PARENTHESIS, pos - 1, pos);
977
- case CC_RIGHT_PARENTHESIS:
978
- return fill(out, TT_RIGHT_PARENTHESIS, pos - 1, pos);
979
- case CC_PLUS_SIGN:
1071
+ // `+` → <number-token> if it starts a number, else a <delim-token>.
1072
+ case HC_PLUS_SIGN:
980
1073
  return consumePlusSign(input, pos, out);
981
- case CC_COMMA:
982
- return fill(out, TT_COMMA, pos - 1, pos);
983
- case CC_HYPHEN_MINUS:
1074
+ // `-` → number / <CDC-token> (`-->`) / ident / <delim-token>.
1075
+ case HC_HYPHEN_MINUS:
984
1076
  return consumeHyphenMinus(input, pos, out);
985
- case CC_FULL_STOP:
1077
+ // `.` → <number-token> if a digit follows, else a <delim-token>.
1078
+ case HC_FULL_STOP:
986
1079
  return consumeFullStop(input, pos, out);
987
- case CC_COLON:
988
- return fill(out, TT_COLON, pos - 1, pos);
989
- case CC_SEMICOLON:
990
- return fill(out, TT_SEMICOLON, pos - 1, pos);
991
- case CC_LESS_THAN_SIGN:
1080
+ // `<` → <CDO-token> (`<!--`), else a <delim-token>.
1081
+ case HC_LESS_THAN:
992
1082
  return consumeLessThan(input, pos, out);
993
- case CC_AT_SIGN:
1083
+ // `@` → <at-keyword-token> if an ident follows, else a <delim-token>.
1084
+ case HC_AT_SIGN:
994
1085
  return consumeCommercialAt(input, pos, out);
995
- case CC_LEFT_SQUARE:
996
- return fill(out, TT_LEFT_SQUARE_BRACKET, pos - 1, pos);
997
- case CC_REVERSE_SOLIDUS:
1086
+ // `\` → ident-like token if it's a valid escape, else a <delim-token>.
1087
+ case HC_REVERSE_SOLIDUS:
998
1088
  return consumeReverseSolidus(input, pos, out);
999
- case CC_RIGHT_SQUARE:
1000
- return fill(out, TT_RIGHT_SQUARE_BRACKET, pos - 1, pos);
1001
- case CC_LEFT_CURLY:
1002
- return fill(out, TT_LEFT_CURLY_BRACKET, pos - 1, pos);
1003
- case CC_RIGHT_CURLY:
1004
- return fill(out, TT_RIGHT_CURLY_BRACKET, pos - 1, pos);
1089
+ // Digit → numeric token; `pos - 1` re-includes the digit the caller passed.
1090
+ case HC_DIGIT:
1091
+ return consumeANumericToken(input, pos - 1, out);
1092
+ // Ident-start (letter / `_` / non-ASCII, incl. `u`/`U`) → ident / function /
1093
+ // url token; `pos - 1` re-includes the lead code point.
1094
+ case HC_IDENT:
1095
+ return consumeAnIdentLikeToken(input, pos - 1, out);
1005
1096
  default:
1006
- if (_isDigit(cc)) {
1007
- pos--;
1008
- return consumeANumericToken(input, pos, out);
1009
- }
1010
- if (cc === CC_LOWER_U || cc === CC_UPPER_U) {
1011
- // Unicode-range tokens are not produced — fall back to
1012
- // ident-like to match the existing tokenizer's behaviour.
1013
- pos--;
1014
- return consumeAnIdentLikeToken(input, pos, out);
1015
- }
1016
- if (isIdentStartCodePoint(cc)) {
1017
- pos--;
1018
- return consumeAnIdentLikeToken(input, pos, out);
1019
- }
1020
- // EOF is impossible here (caller guarded with the outer
1021
- // loop's `pos < input.length` check). Anything else: delim.
1097
+ // HC_DELIM. EOF is impossible here (caller guarded with the outer
1098
+ // loop's `pos < input.length` check). Anything else: a <delim-token>.
1022
1099
  return fill(out, TT_DELIM, pos - 1, pos);
1023
1100
  }
1024
1101
  }
@@ -1043,18 +1120,16 @@ function readToken(input, pos, out) {
1043
1120
  // Comment: `/*…*/` is yielded as a token (filtered by `next`).
1044
1121
  if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) {
1045
1122
  const start = pos;
1046
- pos += 2;
1047
- for (;;) {
1048
- // EOF in comment: emit the unterminated token so ranges cover all input.
1049
- if (pos === input.length) return fill(out, TT_COMMENT, start, pos);
1050
- if (
1051
- input.charCodeAt(pos) === CC_ASTERISK &&
1052
- input.charCodeAt(pos + 1) === CC_SOLIDUS
1053
- ) {
1054
- return fill(out, TT_COMMENT, start, pos + 2);
1055
- }
1056
- pos++;
1057
- }
1123
+ // Jump to the closing `*/` in one native scan instead of a per-character
1124
+ // loop — comment bodies (license banners, source comments) can be long.
1125
+ // No close: unterminated comment runs to EOF so ranges cover all input.
1126
+ const close = input.indexOf("*/", pos + 2);
1127
+ return fill(
1128
+ out,
1129
+ TT_COMMENT,
1130
+ start,
1131
+ close === -1 ? input.length : close + 2
1132
+ );
1058
1133
  }
1059
1134
  // `consumeAToken` dispatches on the lead code point at `pos` (it expects the
1060
1135
  // position just past the lead and the already-read lead code point).
@@ -1100,7 +1175,10 @@ const NodeType = {
1100
1175
  Declaration: 23,
1101
1176
  AtRule: 24,
1102
1177
  QualifiedRule: 25,
1103
- Stylesheet: 26
1178
+ Stylesheet: 26,
1179
+ // Comments are never tree nodes; this type exists only so a `NodeType.Comment`
1180
+ // visitor can be registered (fired during tokenization — see `grammar`).
1181
+ Comment: 27
1104
1182
  };
1105
1183
  const {
1106
1184
  Ident: T_IDENT,
@@ -1128,7 +1206,8 @@ const {
1128
1206
  Declaration: T_DECLARATION,
1129
1207
  AtRule: T_AT_RULE,
1130
1208
  QualifiedRule: T_QUALIFIED_RULE,
1131
- Stylesheet: T_STYLESHEET
1209
+ Stylesheet: T_STYLESHEET,
1210
+ Comment: T_COMMENT
1132
1211
  } = NodeType;
1133
1212
 
1134
1213
  /**
@@ -1240,26 +1319,28 @@ class Token extends Node {
1240
1319
  * @param {number} end byte offset just past the token's last code point
1241
1320
  * @param {LocConverter} locConverter shared loc converter
1242
1321
  */
1243
- constructor(type, start, end, locConverter) {
1244
- super(type, start, end, locConverter);
1245
- // Lazily sliced from `range` on first `value` read (the common case), or
1246
- // pre-set when the value isn't the raw range slice (hash / at-keyword /
1247
- // url strip a prefix or use the content range). Deferring avoids slicing
1248
- // the many tokens whitespace especially whose value is never read.
1249
- /** @type {string | undefined} */
1250
- this._value = undefined;
1251
- }
1322
+ // No own fields: a leaf token is exactly a `Node` plus the value getters
1323
+ // below. `value` is derived from the byte range on read instead of cached in
1324
+ // a `_value` slot most tokens (whitespace, punctuation) never read it, and
1325
+ // dropping the slot is ~8 bytes saved on every token (the bulk of all nodes).
1326
+ // hash / at-keyword strip their `#` / `@` prefix; url uses its content range
1327
+ // (`contentStart` / `contentEnd`, the token's only own fields).
1252
1328
 
1253
1329
  /**
1254
1330
  * @returns {string} the token's value (raw source slice unless overridden)
1255
1331
  */
1256
1332
  get value() {
1257
- const v = this._value;
1258
- if (v !== undefined) return v;
1259
- return (this._value = this._locConverter._input.slice(
1260
- this.start,
1261
- this.end
1262
- ));
1333
+ const input = this._locConverter._input;
1334
+ const type = this.type;
1335
+ // hash (`#name` `name`) and at-keyword (`@name` → `name`) drop one char.
1336
+ if (type === T_HASH || type === T_AT_KEYWORD) {
1337
+ return input.slice(this.start + 1, this.end);
1338
+ }
1339
+ if (type === T_URL) {
1340
+ const u = /** @type {UrlToken} */ (/** @type {unknown} */ (this));
1341
+ return input.slice(u.contentStart, u.contentEnd);
1342
+ }
1343
+ return input.slice(this.start, this.end);
1263
1344
  }
1264
1345
 
1265
1346
  /**
@@ -1297,19 +1378,28 @@ class Token extends Node {
1297
1378
 
1298
1379
  /**
1299
1380
  * Spec type flag. For number / dimension tokens it's "integer" / "number"
1300
- * (derived from `value`); for hash tokens it's "id" / "unrestricted" (from the
1301
- * stored `_isId`). The two senses share the name in the spec; the getter keeps
1302
- * hash tokens the same object shape as numeric tokens (no own `typeFlag`).
1381
+ * (derived from `value`); for hash tokens it's "id" / "unrestricted" (re-derived
1382
+ * from the source). The two senses share the name in the spec; both are computed
1383
+ * on read so every leaf token keeps the same object shape (no own `typeFlag`).
1303
1384
  * @returns {"integer" | "number" | "id" | "unrestricted"} the spec type flag
1304
1385
  */
1305
1386
  get typeFlag() {
1306
1387
  if (this.type === T_HASH) {
1307
- // `_isId` is set only on hash tokens (see `tokenToNode`), kept off
1308
- // `Token`'s declared fields so numeric / plain tokens share one shape.
1309
- const isId = /** @type {{ _isId: boolean }} */ (
1310
- /** @type {unknown} */ (this)
1311
- )._isId;
1312
- return isId ? "id" : "unrestricted";
1388
+ // Re-derive id-ness from the source (whether the name after `#` starts an
1389
+ // ident sequence) rather than storing an `_isId` slot keeps hash tokens
1390
+ // the same shape as every other leaf token. Matches the lexer's
1391
+ // `consumeNumberSign`, which sets `isId` from the same check.
1392
+ const input = this._locConverter._input;
1393
+ const p = this.start + 1;
1394
+ return _ifThreeCodePointsWouldStartAnIdentSequence(
1395
+ input,
1396
+ p,
1397
+ input.charCodeAt(p),
1398
+ input.charCodeAt(p + 1),
1399
+ input.charCodeAt(p + 2)
1400
+ )
1401
+ ? "id"
1402
+ : "unrestricted";
1313
1403
  }
1314
1404
  const v = this.value;
1315
1405
  return _typeFlagOf(
@@ -1333,6 +1423,54 @@ class Token extends Node {
1333
1423
  }
1334
1424
  }
1335
1425
 
1426
+ /**
1427
+ * The non-leaf `Node` subclass: functions, simple blocks, declarations, at-rules
1428
+ * and qualified rules. It declares the union of every container field up front so
1429
+ * all five share **one** hidden class — the consume algorithms only overwrite the
1430
+ * slots relevant to their type, never adding a property, so the shape never
1431
+ * transitions. This caps the shape count: the walker's hot `.type` / `.value` /
1432
+ * `.prelude` loads otherwise see eight distinct node maps (five container types
1433
+ * plus the token / hash / url token maps), tipping V8's inline cache into the
1434
+ * slow megamorphic path; folding the five containers into one leaves four maps
1435
+ * (token, hash, url, container) — within the polymorphic limit, so those loads
1436
+ * stay inline-cached. Unused-for-the-type slots keep their defaults (the field
1437
+ * typedefs below document which fields each type uses). The stylesheet node is
1438
+ * rare (one per parse) and stays a bare `Node`.
1439
+ */
1440
+ class Container extends Node {
1441
+ /**
1442
+ * @param {number} type node type
1443
+ * @param {number} start byte offset of the node's first code point
1444
+ * @param {number} end byte offset just past the node's last code point
1445
+ * @param {LocConverter} locConverter shared loc converter
1446
+ */
1447
+ constructor(type, start, end, locConverter) {
1448
+ super(type, start, end, locConverter);
1449
+ /** @type {string} name (function / at-rule / declaration) */
1450
+ this.name = "";
1451
+ /** @type {number} */
1452
+ this.nameStart = start;
1453
+ /** @type {number} */
1454
+ this.nameEnd = start;
1455
+ /** @type {ComponentValue[] | null} component values (function / block / declaration) */
1456
+ this.value = null;
1457
+ /** @type {ComponentValue[] | null} prelude (at-rule / qualified rule) */
1458
+ this.prelude = null;
1459
+ /** @type {Declaration[] | null} */
1460
+ this.declarations = null;
1461
+ /** @type {Rule[] | null} */
1462
+ this.childRules = null;
1463
+ /** @type {number} `{` start offset, or -1 (at-rule / qualified rule) */
1464
+ this.blockStart = -1;
1465
+ /** @type {number} `}` end offset, or -1 (at-rule / qualified rule) */
1466
+ this.blockEnd = -1;
1467
+ /** @type {boolean} stripped `!important` (declaration) */
1468
+ this.important = false;
1469
+ /** @type {SimpleBlockToken | undefined} opening char (simple block) */
1470
+ this.token = undefined;
1471
+ }
1472
+ }
1473
+
1336
1474
  /**
1337
1475
  * Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`).
1338
1476
  * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "" }} NumberToken
@@ -1430,82 +1568,350 @@ class Token extends Node {
1430
1568
  * @typedef {Node & { rules: Rule[] }} Stylesheet
1431
1569
  */
1432
1570
 
1571
+ // Lexer-token-type → AST-node-type map. A single \`new Token\` construct site
1572
+ // (vs a ~20-case switch with a \`new Token\` in each arm) keeps V8 on the fast
1573
+ // monomorphic allocation path — the switch form showed up as generic construct
1574
+ // stubs in profiles. URL is the one type with extra own state, handled first.
1575
+ const _ttToNodeType = new Uint8Array(27);
1576
+ _ttToNodeType[TT_WHITESPACE] = T_WHITESPACE;
1577
+ _ttToNodeType[TT_IDENTIFIER] = T_IDENT;
1578
+ _ttToNodeType[TT_STRING] = T_STRING;
1579
+ _ttToNodeType[TT_DELIM] = T_DELIM;
1580
+ _ttToNodeType[TT_NUMBER] = T_NUMBER;
1581
+ _ttToNodeType[TT_PERCENTAGE] = T_PERCENTAGE;
1582
+ _ttToNodeType[TT_DIMENSION] = T_DIMENSION;
1583
+ _ttToNodeType[TT_HASH] = T_HASH;
1584
+ _ttToNodeType[TT_AT_KEYWORD] = T_AT_KEYWORD;
1585
+ _ttToNodeType[TT_BAD_STRING_TOKEN] = T_BAD_STRING;
1586
+ _ttToNodeType[TT_BAD_URL_TOKEN] = T_BAD_URL;
1587
+ _ttToNodeType[TT_COLON] = T_COLON;
1588
+ _ttToNodeType[TT_COMMA] = T_COMMA;
1589
+ _ttToNodeType[TT_SEMICOLON] = T_SEMICOLON;
1590
+ _ttToNodeType[TT_RIGHT_PARENTHESIS] = T_RIGHT_PARENTHESIS;
1591
+ _ttToNodeType[TT_RIGHT_SQUARE_BRACKET] = T_RIGHT_SQUARE_BRACKET;
1592
+ _ttToNodeType[TT_RIGHT_CURLY_BRACKET] = T_RIGHT_CURLY_BRACKET;
1593
+ _ttToNodeType[TT_CDO] = T_CDO;
1594
+ _ttToNodeType[TT_CDC] = T_CDC;
1595
+
1596
+ // === AST construction backend ===
1597
+ // The consume algorithms build nodes through these module-level primitives
1598
+ // rather than `new Token` / `new Container` directly, so the node
1599
+ // representation can be swapped under the parser. The object backend below
1600
+ // builds the retainable `Node` / `Token` / `Container` tree the `parseA*`
1601
+ // entry points return; the Struct-of-Arrays backend (added separately) writes
1602
+ // the same nodes into reused typed arrays for the streaming `grammar`, where
1603
+ // per-node allocation dominates cost. Child lists are plain arrays in both
1604
+ // backends (a node ref is an object or an integer index); only node creation /
1605
+ // field access differs, so only those ops are swapped.
1606
+
1607
+ // Current loc converter for the active parse (object backend reads it).
1608
+ let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1609
+
1610
+ // Active skip state (from `CssProcessOptions.skip`), applied by the grammar.
1611
+ // `_skipTypes` is indexed by `NodeType` (1 = skip): drop that component-value
1612
+ // leaf / container from declaration value and function-arg lists. The two
1613
+ // prelude flags scan a rule's prelude without materializing its tree (url tokens
1614
+ // / functions kept, so `url()` in a selector or `@import url(…)` still resolves).
1615
+ // A skipped node is still tokenized (positions stay correct) but never pushed,
1616
+ // so it is never walked or read — the caller must only skip what nothing reads.
1617
+ // `useObjectBackend` restores the defaults so `parseA*` build the full tree.
1618
+ const _NO_SKIP_TYPES = new Uint8Array(32);
1619
+ /** @type {Uint8Array} */
1620
+ let _skipTypes = _NO_SKIP_TYPES;
1621
+ // Fast-path flag: true only when a real skip set is active, so the (dominant)
1622
+ // no-skip parses pay one boolean test instead of a node-type lookup per value.
1623
+ let _skipActive = false;
1624
+ let _skipSelectorPrelude = false;
1625
+ let _skipAtRulePrelude = false;
1626
+
1627
+ /** @type {(type: number, start: number, end: number) => Node} */
1628
+ let _mkLeaf;
1629
+ /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1630
+ let _mkUrl;
1631
+ /** @type {(type: number, start: number, end: number) => Node} */
1632
+ let _mkContainer;
1633
+ /** @type {(start: number) => Node} */
1634
+ let _mkStylesheet;
1635
+ /** @type {(r: Node, v: string, nameStart: number, nameEnd: number) => void} */
1636
+ let _setName;
1637
+ /** @type {(r: Node, v: number) => void} */
1638
+ let _setEnd;
1639
+ /** @type {(r: Node, blockStart: number, blockEnd: number) => void} */
1640
+ let _setBlock;
1641
+ /** @type {(r: Node) => void} */
1642
+ let _setImportant;
1643
+ /** @type {(r: Node, ch: SimpleBlockToken) => void} */
1644
+ let _setToken;
1645
+ /** @type {(r: Node, list: Node[]) => void} */
1646
+ let _setValue;
1647
+ /** @type {(r: Node, list: Node[]) => void} */
1648
+ let _setPrelude;
1649
+ /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
1650
+ let _setBody;
1651
+ /** @type {(r: Node, list: Node[]) => void} */
1652
+ let _setRules;
1653
+ /** @type {(r: Node) => number} */
1654
+ let _nType;
1655
+ /** @type {(r: Node) => number} */
1656
+ let _nStart;
1657
+ /** @type {(r: Node) => string} */
1658
+ let _nValue;
1659
+ /** @type {(r: Node) => SimpleBlockToken} */
1660
+ let _nToken;
1661
+
1662
+ // Child lists are plain arrays in every backend; refs are pushed by value.
1663
+ const _list = () => /** @type {Node[]} */ ([]);
1664
+
1665
+ // -- object backend: builds the retainable class-instance tree --
1666
+ /** @type {typeof _mkLeaf} */
1667
+ const _objLeaf = (type, start, end) => new Token(type, start, end, _lc);
1668
+ /** @type {typeof _mkUrl} */
1669
+ const _objUrl = (start, end, cs, ce) => {
1670
+ const u = /** @type {UrlToken} */ (new Token(T_URL, start, end, _lc));
1671
+ u.contentStart = cs;
1672
+ u.contentEnd = ce;
1673
+ return u;
1674
+ };
1675
+ /** @type {typeof _mkContainer} */
1676
+ const _objContainer = (type, start, end) =>
1677
+ new Container(type, start, end, _lc);
1678
+ /** @type {typeof _mkStylesheet} */
1679
+ const _objStylesheet = (start) => {
1680
+ const s = /** @type {Stylesheet} */ (
1681
+ new Node(T_STYLESHEET, start, start, _lc)
1682
+ );
1683
+ s.rules = [];
1684
+ return s;
1685
+ };
1686
+ /** @param {LocConverter} lc loc converter for this parse */
1687
+ const useObjectBackend = (lc) => {
1688
+ _lc = lc;
1689
+ // `parseA*` return the full tree, so nothing is skipped.
1690
+ _skipTypes = _NO_SKIP_TYPES;
1691
+ _skipActive = false;
1692
+ _skipSelectorPrelude = false;
1693
+ _skipAtRulePrelude = false;
1694
+ _mkLeaf = _objLeaf;
1695
+ _mkUrl = _objUrl;
1696
+ _mkContainer = _objContainer;
1697
+ _mkStylesheet = _objStylesheet;
1698
+ _setName = (r, v, ns, ne) => {
1699
+ const c = /** @type {Container} */ (r);
1700
+ c.name = v;
1701
+ c.nameStart = ns;
1702
+ c.nameEnd = ne;
1703
+ };
1704
+ _setEnd = (r, v) => {
1705
+ r.end = v;
1706
+ };
1707
+ _setBlock = (r, bs, be) => {
1708
+ const c = /** @type {Container} */ (r);
1709
+ c.blockStart = bs;
1710
+ c.blockEnd = be;
1711
+ };
1712
+ _setImportant = (r) => {
1713
+ /** @type {Container} */ (r).important = true;
1714
+ };
1715
+ _setToken = (r, ch) => {
1716
+ /** @type {Container} */ (r).token = ch;
1717
+ };
1718
+ _setValue = (r, list) => {
1719
+ /** @type {Container} */ (r).value = /** @type {ComponentValue[]} */ (list);
1720
+ };
1721
+ _setPrelude = (r, list) => {
1722
+ /** @type {Container} */ (r).prelude = /** @type {ComponentValue[]} */ (
1723
+ list
1724
+ );
1725
+ };
1726
+ _setBody = (r, decls, childRules) => {
1727
+ const c = /** @type {Container} */ (r);
1728
+ c.declarations = /** @type {Declaration[]} */ (decls);
1729
+ c.childRules = /** @type {Rule[]} */ (childRules);
1730
+ };
1731
+ _setRules = (r, list) => {
1732
+ /** @type {Stylesheet} */ (r).rules = /** @type {Rule[]} */ (list);
1733
+ };
1734
+ _nType = (r) => r.type;
1735
+ _nStart = (r) => r.start;
1736
+ _nValue = (r) => /** @type {Token} */ (r).value;
1737
+ _nToken = (r) =>
1738
+ /** @type {SimpleBlockToken} */ (/** @type {Container} */ (r).token);
1739
+ };
1740
+
1741
+ // -- struct-of-arrays backend: writes nodes into reused typed arrays --
1742
+ // A node ref is its integer id; fields live in parallel arrays indexed by id.
1743
+ // Three reused int slots (`_soaA0/1/2`) plus a flags byte carry the per-type
1744
+ // extras; child lists hang off three object arrays. Slot meaning by type:
1745
+ // url: a0 contentStart, a1 contentEnd
1746
+ // function: a0 nameEnd
1747
+ // declaration: a0 nameEnd, flags bit0 important
1748
+ // at-rule: a0 nameEnd, a1 blockStart, a2 blockEnd
1749
+ // qualified: a1 blockStart, a2 blockEnd
1750
+ // `name` / `nameStart` / a simple block's `token` are derived from the source
1751
+ // on read (see the SoA accessors), so they need no slot. Lists: l0 =
1752
+ // value | prelude | stylesheet rules, l1 = declarations, l2 = childRules.
1753
+ // `grammar` resets `_soaN` to 0 after each top-level rule's walk, so the
1754
+ // buffers are reused across rules and the parse allocates almost nothing.
1755
+ let _soaCap = 0;
1756
+ let _soaN = 0;
1757
+ let _soaTy = new Uint8Array(0);
1758
+ let _soaSt = new Int32Array(0);
1759
+ let _soaEn = new Int32Array(0);
1760
+ let _soaA0 = new Int32Array(0);
1761
+ let _soaA1 = new Int32Array(0);
1762
+ let _soaA2 = new Int32Array(0);
1763
+ let _soaFl = new Uint8Array(0);
1764
+ /** @type {(Node[] | null)[]} */
1765
+ const _soaL0 = [];
1766
+ /** @type {(Node[] | null)[]} */
1767
+ const _soaL1 = [];
1768
+ /** @type {(Node[] | null)[]} */
1769
+ const _soaL2 = [];
1770
+ let _soaInput = "";
1771
+ let _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1772
+
1773
+ // Node refs are integers here but typed `Node` across the parser; these are
1774
+ // identity casts that just satisfy the type system at the boundary.
1775
+ /** @type {(n: Node) => number} */
1776
+ const _ix = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1777
+ /** @type {(i: number) => Node} */
1778
+ const _ref = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1779
+
1780
+ /** @param {number} need minimum capacity */
1781
+ const _soaGrow = (need) => {
1782
+ let cap = _soaCap || 4096;
1783
+ while (cap < need) cap *= 2;
1784
+ const ty = new Uint8Array(cap);
1785
+ ty.set(_soaTy);
1786
+ _soaTy = ty;
1787
+ const st = new Int32Array(cap);
1788
+ st.set(_soaSt);
1789
+ _soaSt = st;
1790
+ const en = new Int32Array(cap);
1791
+ en.set(_soaEn);
1792
+ _soaEn = en;
1793
+ const a0 = new Int32Array(cap);
1794
+ a0.set(_soaA0);
1795
+ _soaA0 = a0;
1796
+ const a1 = new Int32Array(cap);
1797
+ a1.set(_soaA1);
1798
+ _soaA1 = a1;
1799
+ const a2 = new Int32Array(cap);
1800
+ a2.set(_soaA2);
1801
+ _soaA2 = a2;
1802
+ const fl = new Uint8Array(cap);
1803
+ fl.set(_soaFl);
1804
+ _soaFl = fl;
1805
+ _soaCap = cap;
1806
+ };
1807
+ /** @type {(type: number, start: number, end: number) => Node} */
1808
+ const _soaAlloc = (type, start, end) => {
1809
+ // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1810
+ // so 0 must stay reserved for "no node".
1811
+ // Leaves never read the flag / list slots — `_soaAllocContainer` clears
1812
+ // them instead, keeping the dominant leaf allocation at three writes.
1813
+ const i = _soaN + 1;
1814
+ if (i >= _soaCap) _soaGrow(i + 1);
1815
+ _soaTy[i] = type;
1816
+ _soaSt[i] = start;
1817
+ _soaEn[i] = end;
1818
+ _soaN = i;
1819
+ return _ref(i);
1820
+ };
1821
+ /** @type {(type: number, start: number, end: number) => Node} */
1822
+ const _soaAllocContainer = (type, start, end) => {
1823
+ const r = _soaAlloc(type, start, end);
1824
+ const i = _ix(r);
1825
+ _soaFl[i] = 0;
1826
+ // Clear list slots so a reused id never exposes a previous node's children.
1827
+ _soaL0[i] = null;
1828
+ _soaL1[i] = null;
1829
+ _soaL2[i] = null;
1830
+ return r;
1831
+ };
1832
+ // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
1833
+ // one-char prefix, url uses its content range. Shared by the parser's
1834
+ // mid-parse reads and the SoA accessor.
1835
+ /**
1836
+ * @param {number} i node id
1837
+ * @returns {string} raw token value
1838
+ */
1839
+ const _soaValueOf = (i) => {
1840
+ const ty = _soaTy[i];
1841
+ if (ty === T_HASH || ty === T_AT_KEYWORD) {
1842
+ return _soaInput.slice(_soaSt[i] + 1, _soaEn[i]);
1843
+ }
1844
+ if (ty === T_URL) return _soaInput.slice(_soaA0[i], _soaA1[i]);
1845
+ return _soaInput.slice(_soaSt[i], _soaEn[i]);
1846
+ };
1847
+ /**
1848
+ * @param {string} input source
1849
+ * @param {LocConverter} lc loc converter
1850
+ */
1851
+ const useSoaBackend = (input, lc) => {
1852
+ _soaInput = input;
1853
+ _soaLC = lc;
1854
+ _soaN = 0;
1855
+ _mkLeaf = (type, start, end) => _soaAlloc(type, start, end);
1856
+ _mkUrl = (start, end, cs, ce) => {
1857
+ const r = _soaAlloc(T_URL, start, end);
1858
+ _soaA0[_ix(r)] = cs;
1859
+ _soaA1[_ix(r)] = ce;
1860
+ return r;
1861
+ };
1862
+ _mkContainer = (type, start, end) => _soaAllocContainer(type, start, end);
1863
+ _mkStylesheet = (start) => _soaAllocContainer(T_STYLESHEET, start, start);
1864
+ // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
1865
+ _setName = (r, v, ns, ne) => {
1866
+ _soaA0[_ix(r)] = ne;
1867
+ };
1868
+ _setEnd = (r, v) => {
1869
+ _soaEn[_ix(r)] = v;
1870
+ };
1871
+ _setBlock = (r, bs, be) => {
1872
+ const i = _ix(r);
1873
+ _soaA1[i] = bs;
1874
+ _soaA2[i] = be;
1875
+ };
1876
+ _setImportant = (r) => {
1877
+ _soaFl[_ix(r)] |= 1;
1878
+ };
1879
+ // A simple block's token is derived from its opening char on read.
1880
+ _setToken = (r, ch) => {};
1881
+ _setValue = (r, list) => {
1882
+ _soaL0[_ix(r)] = list;
1883
+ };
1884
+ _setPrelude = (r, list) => {
1885
+ _soaL0[_ix(r)] = list;
1886
+ };
1887
+ _setBody = (r, decls, childRules) => {
1888
+ const i = _ix(r);
1889
+ _soaL1[i] = decls;
1890
+ _soaL2[i] = childRules;
1891
+ };
1892
+ _setRules = (r, list) => {
1893
+ _soaL0[_ix(r)] = list;
1894
+ };
1895
+ _nType = (r) => _soaTy[_ix(r)];
1896
+ _nStart = (r) => _soaSt[_ix(r)];
1897
+ _nValue = (r) => _soaValueOf(_ix(r));
1898
+ _nToken = (r) => /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(r)]]);
1899
+ };
1900
+
1433
1901
  /**
1434
1902
  * Materialize a single non-block, non-function lexer token as its leaf AST node — the spec's "consume a token" result (§5.4.8 "anything else"), preserving stray closers / CDO / CDC.
1435
1903
  * @param {MutableToken} t token from the lexer
1436
- * @param {string} input source
1437
- * @param {LocConverter} locConverter shared loc converter
1438
- * @returns {Token} the leaf token node
1439
- */
1440
- const tokenToNode = (t, input, locConverter) => {
1441
- switch (t.type) {
1442
- case TT_WHITESPACE:
1443
- return new Token(T_WHITESPACE, t.start, t.end, locConverter);
1444
- case TT_IDENTIFIER:
1445
- return new Token(T_IDENT, t.start, t.end, locConverter);
1446
- case TT_STRING:
1447
- return new Token(T_STRING, t.start, t.end, locConverter);
1448
- case TT_DELIM:
1449
- return new Token(T_DELIM, t.start, t.end, locConverter);
1450
- // Numeric tokens: construct only — `numericValue` / `typeFlag` / `sign` /
1451
- // `unit` are lazy `Token` getters, so the `Number()` parse + slices +
1452
- // `toLowerCase` are skipped for the (common) tokens never read numerically.
1453
- case TT_NUMBER:
1454
- return new Token(T_NUMBER, t.start, t.end, locConverter);
1455
- case TT_PERCENTAGE:
1456
- return new Token(T_PERCENTAGE, t.start, t.end, locConverter);
1457
- case TT_DIMENSION:
1458
- return new Token(T_DIMENSION, t.start, t.end, locConverter);
1459
- case TT_HASH: {
1460
- const hash = new Token(T_HASH, t.start, t.end, locConverter);
1461
- hash._value = input.slice(t.start + 1, t.end);
1462
- // Store the raw id-ness; the `typeFlag` getter maps it to "id" /
1463
- // "unrestricted". Kept off `Token`'s declared fields so non-hash
1464
- // tokens don't carry the slot.
1465
- /** @type {Token & { _isId: boolean }} */ (hash)._isId = t.isId;
1466
- return hash;
1467
- }
1468
- case TT_AT_KEYWORD: {
1469
- const at = new Token(T_AT_KEYWORD, t.start, t.end, locConverter);
1470
- at._value = input.slice(t.start + 1, t.end);
1471
- return at;
1472
- }
1473
- case TT_URL: {
1474
- const ut = /** @type {CssUrlToken} */ (t);
1475
- const url = /** @type {UrlToken} */ (
1476
- new Token(T_URL, t.start, t.end, locConverter)
1477
- );
1478
- url._value = input.slice(ut.contentStart, ut.contentEnd);
1479
- url.contentStart = ut.contentStart;
1480
- url.contentEnd = ut.contentEnd;
1481
- return url;
1482
- }
1483
- case TT_BAD_STRING_TOKEN:
1484
- return new Token(T_BAD_STRING, t.start, t.end, locConverter);
1485
- case TT_BAD_URL_TOKEN:
1486
- return new Token(T_BAD_URL, t.start, t.end, locConverter);
1487
- case TT_COLON:
1488
- return new Token(T_COLON, t.start, t.end, locConverter);
1489
- case TT_COMMA:
1490
- return new Token(T_COMMA, t.start, t.end, locConverter);
1491
- case TT_SEMICOLON:
1492
- return new Token(T_SEMICOLON, t.start, t.end, locConverter);
1493
- // Stray closers / CDO / CDC reach here only on malformed input; the spec
1494
- // preserves them as component values ("consume a token and return it").
1495
- case TT_RIGHT_PARENTHESIS:
1496
- return new Token(T_RIGHT_PARENTHESIS, t.start, t.end, locConverter);
1497
- case TT_RIGHT_SQUARE_BRACKET:
1498
- return new Token(T_RIGHT_SQUARE_BRACKET, t.start, t.end, locConverter);
1499
- case TT_RIGHT_CURLY_BRACKET:
1500
- return new Token(T_RIGHT_CURLY_BRACKET, t.start, t.end, locConverter);
1501
- case TT_CDO:
1502
- return new Token(T_CDO, t.start, t.end, locConverter);
1503
- case TT_CDC:
1504
- return new Token(T_CDC, t.start, t.end, locConverter);
1505
- /* istanbul ignore next -- @preserve: unreachable; blocks/functions are routed earlier, comments filtered, EOF guarded by callers */
1506
- default:
1507
- throw new Error(`Unexpected token type "${t.type}"`);
1904
+ * @returns {Node} the leaf token node
1905
+ */
1906
+ const tokenToNode = (t) => {
1907
+ const tt = t.type;
1908
+ // URL is the only leaf with own state (its content range); all others are a
1909
+ // plain leaf whose node type comes from the map.
1910
+ if (tt === TT_URL) {
1911
+ const ut = /** @type {CssUrlToken} */ (t);
1912
+ return _mkUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
1508
1913
  }
1914
+ return _mkLeaf(_ttToNodeType[tt], t.start, t.end);
1509
1915
  };
1510
1916
 
1511
1917
  /**
@@ -1555,12 +1961,12 @@ class TokenStream {
1555
1961
  /** @type {number} */
1556
1962
  this._commentHigh = pos;
1557
1963
  // Single reused token the lexer writes into on the `next` path — see
1558
- // `MutableToken`. `_next` points at it when a token is cached, else
1559
- // `undefined` (re-tokenize on next read).
1964
+ // `MutableToken`. `_hasNext` marks it cached a boolean instead of an
1965
+ // object slot, so caching a token never pays a GC write barrier.
1560
1966
  /** @type {MutableToken} */
1561
1967
  this._tok = createToken();
1562
- /** @type {MutableToken | undefined} the next token, lazily tokenized */
1563
- this._next = undefined;
1968
+ /** @type {boolean} whether `_tok` holds the (lazily tokenized) next token */
1969
+ this._hasNext = false;
1564
1970
  /** @type {number[]} byte offsets to rewind to */
1565
1971
  this._marks = [];
1566
1972
  }
@@ -1574,14 +1980,14 @@ class TokenStream {
1574
1980
  * @returns {MutableToken} the next token
1575
1981
  */
1576
1982
  next() {
1577
- if (this._next === undefined) {
1983
+ if (!this._hasNext) {
1578
1984
  const input = this.input;
1579
1985
  const tok = this._tok;
1580
1986
  let pos = this._pos;
1581
1987
  for (;;) {
1582
1988
  const t = readToken(input, pos, tok);
1583
1989
  if (t === undefined) {
1584
- this._next = fill(tok, TT_EOF, input.length, input.length);
1990
+ fill(tok, TT_EOF, input.length, input.length);
1585
1991
  break;
1586
1992
  }
1587
1993
  if (t.type === TT_COMMENT) {
@@ -1592,11 +1998,11 @@ class TokenStream {
1592
1998
  pos = t.end;
1593
1999
  continue;
1594
2000
  }
1595
- this._next = t;
1596
2001
  break;
1597
2002
  }
2003
+ this._hasNext = true;
1598
2004
  }
1599
- return /** @type {MutableToken} */ (this._next);
2005
+ return this._tok;
1600
2006
  }
1601
2007
 
1602
2008
  /**
@@ -1609,7 +2015,7 @@ class TokenStream {
1609
2015
  const t = this.next();
1610
2016
  if (t.type !== TT_EOF) {
1611
2017
  this._pos = t.end;
1612
- this._next = undefined;
2018
+ this._hasNext = false;
1613
2019
  }
1614
2020
  return t;
1615
2021
  }
@@ -1623,7 +2029,7 @@ class TokenStream {
1623
2029
  const t = this.next();
1624
2030
  if (t.type !== TT_EOF) {
1625
2031
  this._pos = t.end;
1626
- this._next = undefined;
2032
+ this._hasNext = false;
1627
2033
  }
1628
2034
  }
1629
2035
 
@@ -1643,7 +2049,7 @@ class TokenStream {
1643
2049
  */
1644
2050
  restoreMark() {
1645
2051
  this._pos = /** @type {number} */ (this._marks.pop());
1646
- this._next = undefined;
2052
+ this._hasNext = false;
1647
2053
  }
1648
2054
 
1649
2055
  /**
@@ -1697,15 +2103,13 @@ const parseAStylesheet = (input, comment) => {
1697
2103
  // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
1698
2104
  // 2. Normalize input, and set input to the result.
1699
2105
  const ts = normalizeIntoTokenStream(input, 0, comment);
2106
+ useObjectBackend(ts.locConverter);
1700
2107
  // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
1701
2108
  const start = ts.next().start;
1702
- const stylesheet = /** @type {Stylesheet} */ (
1703
- new Node(T_STYLESHEET, start, start, ts.locConverter)
1704
- );
1705
- stylesheet.rules = /** @type {Rule[]} */ ([]);
2109
+ const stylesheet = /** @type {Stylesheet} */ (_mkStylesheet(start));
1706
2110
  // 4. Consume a stylesheet's contents from input, and set the stylesheet's rules to the result.
1707
- stylesheet.rules = consumeAStylesheetsContents(ts);
1708
- stylesheet.end = ts.next().start;
2111
+ _setRules(stylesheet, consumeAStylesheetsContents(ts));
2112
+ _setEnd(stylesheet, ts.next().start);
1709
2113
  // 5. Return the stylesheet.
1710
2114
  return stylesheet;
1711
2115
  };
@@ -1723,6 +2127,7 @@ const parseAStylesheet = (input, comment) => {
1723
2127
  const parseAStylesheetsContents = (input, comment) => {
1724
2128
  // 1. Normalize input, and set input to the result.
1725
2129
  const ts = normalizeIntoTokenStream(input, 0, comment);
2130
+ useObjectBackend(ts.locConverter);
1726
2131
  // 2. Consume a stylesheet’s contents from input, and return the result.
1727
2132
  return consumeAStylesheetsContents(ts);
1728
2133
  };
@@ -1738,6 +2143,7 @@ const parseAStylesheetsContents = (input, comment) => {
1738
2143
  const parseABlocksContents = (input, pos, comment) => {
1739
2144
  // 1. Normalize input, and set input to the result.
1740
2145
  const ts = normalizeIntoTokenStream(input, pos, comment);
2146
+ useObjectBackend(ts.locConverter);
1741
2147
  // 2. Consume a block’s contents from input, and return the result.
1742
2148
  return consumeABlocksContents(ts);
1743
2149
  };
@@ -1755,6 +2161,7 @@ const parseABlocksContents = (input, pos, comment) => {
1755
2161
  const parseARule = (input, pos, comment) => {
1756
2162
  // 1. Normalize input, and set input to the result.
1757
2163
  const ts = normalizeIntoTokenStream(input, pos, comment);
2164
+ useObjectBackend(ts.locConverter);
1758
2165
  // 2. Discard whitespace from input.
1759
2166
  while (ts.next().type === TT_WHITESPACE) ts.discard();
1760
2167
  // 3. If the next token from input is an <EOF-token>, return a syntax error.
@@ -1785,6 +2192,7 @@ const parseARule = (input, pos, comment) => {
1785
2192
  const parseADeclaration = (input, pos, comment) => {
1786
2193
  // 1. Normalize input, and set input to the result.
1787
2194
  const ts = normalizeIntoTokenStream(input, pos, comment);
2195
+ useObjectBackend(ts.locConverter);
1788
2196
  // 2. Discard whitespace from input.
1789
2197
  while (ts.next().type === TT_WHITESPACE) ts.discard();
1790
2198
  // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
@@ -1801,6 +2209,7 @@ const parseADeclaration = (input, pos, comment) => {
1801
2209
  const parseAComponentValue = (input, pos, options = {}) => {
1802
2210
  // 1. Normalize input, and set input to the result.
1803
2211
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2212
+ useObjectBackend(ts.locConverter);
1804
2213
  // 2. Discard whitespace from input.
1805
2214
  while (ts.next().type === TT_WHITESPACE) ts.discard();
1806
2215
  // 3. If input is empty, return a syntax error.
@@ -1825,6 +2234,7 @@ const parseAComponentValue = (input, pos, options = {}) => {
1825
2234
  const parseAListOfComponentValues = (input, pos, options = {}) => {
1826
2235
  // 1. Normalize input, and set input to the result.
1827
2236
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2237
+ useObjectBackend(ts.locConverter);
1828
2238
  // 2. Consume a list of component values from input, and return the result.
1829
2239
  return consumeAListOfComponentValues(ts);
1830
2240
  };
@@ -1843,6 +2253,7 @@ const parseACommaSeparatedListOfComponentValues = (
1843
2253
  ) => {
1844
2254
  // 1. Normalize input, and set input to the result.
1845
2255
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2256
+ useObjectBackend(ts.locConverter);
1846
2257
  // 2. Let groups be an empty list.
1847
2258
  /** @type {ComponentValue[][]} */
1848
2259
  const groups = [];
@@ -1924,17 +2335,23 @@ const consumeAnAtRule = (ts, nested = false) => {
1924
2335
  // 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.
1925
2336
  const head = ts.consume();
1926
2337
  const rule = /** @type {AtRule} */ (
1927
- new Node(T_AT_RULE, head.start, head.end, ts.locConverter)
2338
+ _mkContainer(T_AT_RULE, head.start, head.end)
1928
2339
  );
1929
- rule.name = ts.input.slice(head.start + 1, head.end);
1930
- rule.nameStart = head.start;
1931
- rule.nameEnd = head.end;
1932
- rule.prelude = /** @type {ComponentValue[]} */ ([]);
1933
- rule.declarations = null;
1934
- rule.childRules = null;
1935
- // -1 = no block (the `;` / EOF / nested-`}` at-rule forms).
1936
- rule.blockStart = -1;
1937
- rule.blockEnd = -1;
2340
+ _setName(
2341
+ rule,
2342
+ ts.input.slice(head.start + 1, head.end),
2343
+ head.start,
2344
+ head.end
2345
+ );
2346
+ const prelude = _list();
2347
+ _setPrelude(rule, prelude);
2348
+ // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2349
+ // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
2350
+
2351
+ // Like `consumeAQualifiedRule`: skip mode scans the prelude without
2352
+ // materializing it (url tokens / functions kept so `@import url(…)` still
2353
+ // resolves); the block boundary is found by scanning, not the prelude nodes.
2354
+ const skip = _skipAtRulePrelude;
1938
2355
 
1939
2356
  // Process input
1940
2357
  for (;;) {
@@ -1945,7 +2362,7 @@ const consumeAnAtRule = (ts, nested = false) => {
1945
2362
  // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
1946
2363
  if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
1947
2364
  ts.discard();
1948
- rule.end = t.start;
2365
+ _setEnd(rule, t.start);
1949
2366
  return rule;
1950
2367
  }
1951
2368
  // <}-token>
@@ -1953,27 +2370,31 @@ const consumeAnAtRule = (ts, nested = false) => {
1953
2370
  // Otherwise, consume a token and append the result to rule’s prelude.
1954
2371
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
1955
2372
  if (nested) {
1956
- rule.end = t.start;
2373
+ _setEnd(rule, t.start);
1957
2374
  return rule;
1958
2375
  }
1959
- rule.prelude.push(consumeATokenAsNode(ts));
2376
+ const node = consumeATokenAsNode(ts);
2377
+ if (!skip) prelude.push(node);
1960
2378
  continue;
1961
2379
  }
1962
2380
  // <{-token>
1963
2381
  // Consume a block from input, and assign the result to rule's declarations and child rules.
1964
2382
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
1965
2383
  const block = consumeABlock(ts);
1966
- rule.declarations = block.decls;
1967
- rule.childRules = block.rules;
1968
- rule.blockStart = block.blockStart;
1969
- rule.blockEnd = block.blockEnd;
1970
- rule.end = block.blockEnd;
2384
+ _setBody(rule, block.decls, block.rules);
2385
+ _setBlock(rule, block.blockStart, block.blockEnd);
2386
+ _setEnd(rule, block.blockEnd);
1971
2387
  return rule;
1972
2388
  }
1973
2389
 
1974
2390
  // anything else
1975
2391
  // Consume a component value from input and append the returned value to rule’s prelude.
1976
- rule.prelude.push(consumeAComponentValue(ts));
2392
+ const node = consumeAComponentValue(ts, t);
2393
+ if (!skip) {
2394
+ prelude.push(node);
2395
+ } else if (_nType(node) === T_FUNCTION || _nType(node) === T_URL) {
2396
+ prelude.push(node);
2397
+ }
1977
2398
  }
1978
2399
  };
1979
2400
 
@@ -1988,7 +2409,7 @@ const consumeAnAtRule = (ts, nested = false) => {
1988
2409
  */
1989
2410
  const consumeATokenAsNode = (ts) => {
1990
2411
  const t = ts.consume();
1991
- return tokenToNode(t, ts.input, ts.locConverter);
2412
+ return /** @type {Token} */ (tokenToNode(t));
1992
2413
  };
1993
2414
 
1994
2415
  /**
@@ -2002,14 +2423,19 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2002
2423
  const start = ts.next().start;
2003
2424
  // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
2004
2425
  const rule = /** @type {QualifiedRule} */ (
2005
- new Node(T_QUALIFIED_RULE, start, start, ts.locConverter)
2426
+ _mkContainer(T_QUALIFIED_RULE, start, start)
2006
2427
  );
2007
- rule.prelude = /** @type {ComponentValue[]} */ ([]);
2008
- rule.declarations = null;
2009
- rule.childRules = null;
2010
- // -1 = no block (EOF reached before `{`).
2011
- rule.blockStart = -1;
2012
- rule.blockEnd = -1;
2428
+ const prelude = _list();
2429
+ _setPrelude(rule, prelude);
2430
+ // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2431
+ // defaults (no block until a `{` is reached).
2432
+
2433
+ // Skip mode leaves `prelude` empty (selector text is recovered from the
2434
+ // rule's byte range, not its nodes); `first`/`second` still track the first
2435
+ // two non-whitespace tokens the `--foo: {` disambiguation below needs.
2436
+ const skip = _skipSelectorPrelude;
2437
+ let first = /** @type {Node} */ (/** @type {unknown} */ (0));
2438
+ let second = /** @type {Node} */ (/** @type {unknown} */ (0));
2013
2439
 
2014
2440
  // Process input
2015
2441
  for (;;) {
@@ -2024,7 +2450,13 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2024
2450
  // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
2025
2451
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2026
2452
  if (nested) return undefined;
2027
- rule.prelude.push(consumeATokenAsNode(ts));
2453
+ const node = consumeATokenAsNode(ts);
2454
+ if (skip) {
2455
+ if (!first) first = node;
2456
+ else if (!second) second = node;
2457
+ } else {
2458
+ prelude.push(node);
2459
+ }
2028
2460
  continue;
2029
2461
  }
2030
2462
  // <{-token>
@@ -2034,31 +2466,33 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2034
2466
  // (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
2035
2467
  // Otherwise, consume a block from input, and let child rules be the result.
2036
2468
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2037
- let firstIdx = 0;
2038
- /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2039
- while (
2040
- firstIdx < rule.prelude.length &&
2041
- rule.prelude[firstIdx].type === T_WHITESPACE
2042
- ) {
2043
- firstIdx++;
2044
- }
2045
- let secondIdx = firstIdx + 1;
2046
- while (
2047
- secondIdx < rule.prelude.length &&
2048
- rule.prelude[secondIdx].type === T_WHITESPACE
2049
- ) {
2050
- secondIdx++;
2469
+ if (!skip) {
2470
+ let firstIdx = 0;
2471
+ /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2472
+ while (
2473
+ firstIdx < prelude.length &&
2474
+ _nType(prelude[firstIdx]) === T_WHITESPACE
2475
+ ) {
2476
+ firstIdx++;
2477
+ }
2478
+ let secondIdx = firstIdx + 1;
2479
+ while (
2480
+ secondIdx < prelude.length &&
2481
+ _nType(prelude[secondIdx]) === T_WHITESPACE
2482
+ ) {
2483
+ secondIdx++;
2484
+ }
2485
+ first = prelude[firstIdx];
2486
+ second = prelude[secondIdx];
2051
2487
  }
2052
- const first = rule.prelude[firstIdx];
2053
- const second = rule.prelude[secondIdx];
2054
2488
  if (
2055
2489
  first &&
2056
- first.type === T_IDENT &&
2490
+ _nType(first) === T_IDENT &&
2057
2491
  // Test the source bytes directly — avoids forcing the lazy `value`
2058
2492
  // slice just to check the `--` custom-property prefix.
2059
- ts.input.startsWith("--", first.start) &&
2493
+ ts.input.startsWith("--", _nStart(first)) &&
2060
2494
  second &&
2061
- second.type === T_COLON
2495
+ _nType(second) === T_COLON
2062
2496
  ) {
2063
2497
  /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
2064
2498
  if (nested) {
@@ -2069,17 +2503,29 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2069
2503
  return undefined;
2070
2504
  }
2071
2505
  const block = consumeABlock(ts);
2072
- rule.declarations = block.decls;
2073
- rule.childRules = block.rules;
2074
- rule.blockStart = block.blockStart;
2075
- rule.blockEnd = block.blockEnd;
2076
- rule.end = block.blockEnd;
2506
+ _setBody(rule, block.decls, block.rules);
2507
+ _setBlock(rule, block.blockStart, block.blockEnd);
2508
+ _setEnd(rule, block.blockEnd);
2077
2509
  return rule;
2078
2510
  }
2079
2511
 
2080
2512
  // anything else
2081
2513
  // Consume a component value from input and append the result to rule’s prelude.
2082
- rule.prelude.push(consumeAComponentValue(ts));
2514
+ const node = consumeAComponentValue(ts, t);
2515
+ if (skip) {
2516
+ // Keep only url-bearing nodes (url tokens, or functions that may hold a
2517
+ // url like `:unknown(url(x))`) so the url visitor still rewrites them;
2518
+ // other selector tokens have no non-modules consumer, so drop them.
2519
+ const ty = _nType(node);
2520
+ if (ty === T_FUNCTION || ty === T_URL) prelude.push(node);
2521
+ // Track the first two non-whitespace tokens for the disambiguation above.
2522
+ if (t.type !== TT_WHITESPACE) {
2523
+ if (!first) first = node;
2524
+ else if (!second) second = node;
2525
+ }
2526
+ } else {
2527
+ prelude.push(node);
2528
+ }
2083
2529
  }
2084
2530
  };
2085
2531
 
@@ -2103,7 +2549,8 @@ const consumeABlock = (ts) => {
2103
2549
  };
2104
2550
 
2105
2551
  /**
2106
- * 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).
2552
+ * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>`?
2553
+ * Peeks raw code points without advancing; comments still fire `onComment` later.
2107
2554
  * @param {TokenStream} ts token stream
2108
2555
  * @returns {boolean} true if consume-a-declaration's step 1 + step 3 would both succeed on the current input
2109
2556
  */
@@ -2245,22 +2692,17 @@ const consumeADeclaration = (ts, nested = false) => {
2245
2692
  const { input } = ts;
2246
2693
  // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
2247
2694
  const start = ts.next().start;
2695
+ // name "" / nameStart / nameEnd (= start) / important (false) keep their
2696
+ // `Container` defaults; `value` is set unconditionally at step 5 below.
2248
2697
  const decl = /** @type {Declaration} */ (
2249
- new Node(T_DECLARATION, start, start, ts.locConverter)
2698
+ _mkContainer(T_DECLARATION, start, start)
2250
2699
  );
2251
- decl.name = "";
2252
- decl.nameStart = start;
2253
- decl.nameEnd = start;
2254
- decl.value = /** @type {ComponentValue[]} */ ([]);
2255
- decl.important = false;
2256
2700
 
2257
2701
  // 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.
2258
2702
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2259
2703
  if (ts.next().type === TT_IDENTIFIER) {
2260
2704
  const head = ts.consume();
2261
- decl.nameStart = head.start;
2262
- decl.nameEnd = head.end;
2263
- decl.name = input.slice(head.start, head.end);
2705
+ _setName(decl, input.slice(head.start, head.end), head.start, head.end);
2264
2706
  } else {
2265
2707
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2266
2708
  return undefined;
@@ -2282,51 +2724,42 @@ const consumeADeclaration = (ts, nested = false) => {
2282
2724
  while (ts.next().type === TT_WHITESPACE) ts.discard();
2283
2725
 
2284
2726
  // 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.
2285
- decl.value = consumeAListOfComponentValues(ts, TT_SEMICOLON, nested);
2286
- decl.end = ts.next().start;
2727
+ const value = consumeAListOfComponentValues(ts, TT_SEMICOLON, nested);
2728
+ _setValue(decl, value);
2729
+ _setEnd(decl, ts.next().start);
2287
2730
 
2288
2731
  // 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.
2289
2732
  {
2290
- let last = decl.value.length - 1;
2291
- while (last >= 0 && decl.value[last].type === T_WHITESPACE) last--;
2733
+ let last = value.length - 1;
2734
+ while (last >= 0 && _nType(value[last]) === T_WHITESPACE) last--;
2292
2735
  let prev = last - 1;
2293
- while (prev >= 0 && decl.value[prev].type === T_WHITESPACE) prev--;
2736
+ while (prev >= 0 && _nType(value[prev]) === T_WHITESPACE) prev--;
2294
2737
  if (
2295
2738
  prev >= 0 &&
2296
- decl.value[last].type === T_IDENT &&
2297
- equalsLowerCase(
2298
- /** @type {Token} */ (decl.value[last]).value,
2299
- "important"
2300
- ) &&
2301
- decl.value[prev].type === T_DELIM &&
2302
- input.charCodeAt(decl.value[prev].start) === CC_EXCLAMATION
2739
+ _nType(value[last]) === T_IDENT &&
2740
+ equalsLowerCase(_nValue(value[last]), "important") &&
2741
+ _nType(value[prev]) === T_DELIM &&
2742
+ input.charCodeAt(_nStart(value[prev])) === CC_EXCLAMATION
2303
2743
  ) {
2304
- decl.important = true;
2305
- decl.value.length = prev;
2744
+ _setImportant(decl);
2745
+ value.length = prev;
2306
2746
  }
2307
2747
  }
2308
2748
 
2309
2749
  // 7. While the last item in decl's value is a <whitespace-token>, remove that token.
2310
- while (
2311
- decl.value.length > 0 &&
2312
- decl.value[decl.value.length - 1].type === T_WHITESPACE
2313
- ) {
2314
- decl.value.pop();
2750
+ while (value.length > 0 && _nType(value[value.length - 1]) === T_WHITESPACE) {
2751
+ value.pop();
2315
2752
  }
2316
2753
 
2317
2754
  // 8. If decl's name starts with "--" (a custom property), it can contain any value (including a top-level `{}` block) — accept it.
2318
2755
  // Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, return nothing.
2319
2756
  // (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.)
2320
2757
  // 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.)
2321
- const isCustomProperty = input.startsWith("--", decl.nameStart);
2758
+ const isCustomProperty = input.startsWith("--", start);
2322
2759
  if (!isCustomProperty) {
2323
- const value = decl.value;
2324
2760
  for (let i = 0; i < value.length; i++) {
2325
2761
  const v = value[i];
2326
- if (
2327
- v.type === T_SIMPLE_BLOCK &&
2328
- /** @type {SimpleBlock} */ (v).token === "{"
2329
- ) {
2762
+ if (_nType(v) === T_SIMPLE_BLOCK && _nToken(v) === "{") {
2330
2763
  return undefined;
2331
2764
  }
2332
2765
  }
@@ -2361,22 +2794,40 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2361
2794
  // Otherwise, this is a parse error. Consume a token from input and append the result to values.
2362
2795
  if (t.type === TT_RIGHT_CURLY_BRACKET) {
2363
2796
  if (nested) return values;
2364
- values.push(consumeATokenAsNode(ts));
2797
+ const closer = consumeATokenAsNode(ts);
2798
+ // Keep unless the type is explicitly marked skip (1); an out-of-range
2799
+ // lookup on a short `skip.types` yields `undefined`, which must not drop.
2800
+ if (!_skipActive || _skipTypes[_nType(closer)] !== 1) values.push(closer);
2365
2801
  continue;
2366
2802
  }
2367
2803
  // anything else
2368
2804
  // Consume a component value from input, and append the result to values.
2369
- values.push(consumeAComponentValue(ts));
2805
+ // Skipped leaf types short-circuit before materializing: no SoA slot is
2806
+ // written and no node is built (blocks / functions never skip here).
2807
+ const tt = t.type;
2808
+ if (
2809
+ _skipActive &&
2810
+ tt !== TT_FUNCTION &&
2811
+ !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
2812
+ _skipTypes[_ttToNodeType[tt]] === 1
2813
+ ) {
2814
+ ts.consume();
2815
+ continue;
2816
+ }
2817
+ const node = consumeAComponentValue(ts, t);
2818
+ if (!_skipActive || _skipTypes[_nType(node)] !== 1) values.push(node);
2370
2819
  }
2371
2820
  };
2372
2821
 
2373
2822
  /**
2374
2823
  * Consume a component value, CSS Syntax Level 3 [§5.4.8](https://drafts.csswg.org/css-syntax/#consume-component-value) — consumes the next value (simple block, function, or single token); callers guard against EOF before calling.
2375
2824
  * @param {TokenStream} ts token stream
2825
+ * @param {MutableToken=} t the next token, if the caller already peeked it (defaults to `ts.next()`)
2376
2826
  * @returns {SimpleBlock | FunctionNode | ComponentValue} the consumed component value
2377
2827
  */
2378
- const consumeAComponentValue = (ts) => {
2379
- const t = ts.next();
2828
+ const consumeAComponentValue = (ts, t = ts.next()) => {
2829
+ // `t` is the next token; hot callers already peeked it and pass it in to
2830
+ // skip a redundant `ts.next()` per component value.
2380
2831
  // <{-token> / <[-token> / <(-token> (the three contiguous opening brackets)
2381
2832
  // Consume a simple block from input and return the result.
2382
2833
  if (t.type >= TT_LEFT_PARENTHESIS && t.type <= TT_LEFT_CURLY_BRACKET) {
@@ -2389,7 +2840,11 @@ const consumeAComponentValue = (ts) => {
2389
2840
  }
2390
2841
  // anything else
2391
2842
  // Consume a token from input and return the result. (Asserted: not EOF.)
2392
- return consumeATokenAsNode(ts);
2843
+ // Inlined `consumeATokenAsNode`: `t` is already the peeked next token, so
2844
+ // advance past it and materialize it directly — one fewer call per leaf
2845
+ // component value (the bulk of the nodes on a large stylesheet).
2846
+ ts.consume();
2847
+ return /** @type {ComponentValue} */ (tokenToNode(t));
2393
2848
  };
2394
2849
 
2395
2850
  /**
@@ -2406,10 +2861,11 @@ const consumeASimpleBlock = (ts) => {
2406
2861
 
2407
2862
  // 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.
2408
2863
  const block = /** @type {SimpleBlock} */ (
2409
- new Node(T_SIMPLE_BLOCK, open.start, open.end, ts.locConverter)
2864
+ _mkContainer(T_SIMPLE_BLOCK, open.start, open.end)
2410
2865
  );
2411
- block.token = token;
2412
- block.value = /** @type {ComponentValue[]} */ ([]);
2866
+ _setToken(block, token);
2867
+ const val = _list();
2868
+ _setValue(block, val);
2413
2869
 
2414
2870
  // Discard a token from input.
2415
2871
  ts.discard();
@@ -2423,13 +2879,13 @@ const consumeASimpleBlock = (ts) => {
2423
2879
  // Discard a token from input. Return block.
2424
2880
  if (t.type === TT_EOF || t.type === ending) {
2425
2881
  ts.discard();
2426
- block.end = t.end;
2882
+ _setEnd(block, t.end);
2427
2883
  return block;
2428
2884
  }
2429
2885
 
2430
2886
  // anything else
2431
2887
  // Consume a component value from input and append the result to block’s value.
2432
- block.value.push(consumeAComponentValue(ts));
2888
+ val.push(consumeAComponentValue(ts, t));
2433
2889
  }
2434
2890
  };
2435
2891
 
@@ -2439,17 +2895,16 @@ const consumeASimpleBlock = (ts) => {
2439
2895
  * @returns {FunctionNode | undefined} the consumed function node
2440
2896
  */
2441
2897
  const consumeAFunction = (ts) => {
2442
- const { input, locConverter } = ts;
2898
+ const { input } = ts;
2443
2899
  // Assert (spec): the next token is a <function-token>.
2444
2900
  // 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.
2445
2901
  const tFn = ts.consume();
2446
2902
  const fn = /** @type {FunctionNode} */ (
2447
- new Node(T_FUNCTION, tFn.start, tFn.end, locConverter)
2903
+ _mkContainer(T_FUNCTION, tFn.start, tFn.end)
2448
2904
  );
2449
- fn.name = input.slice(tFn.start, tFn.end - 1);
2450
- fn.nameStart = tFn.start;
2451
- fn.nameEnd = tFn.end - 1;
2452
- fn.value = /** @type {ComponentValue[]} */ ([]);
2905
+ _setName(fn, input.slice(tFn.start, tFn.end - 1), tFn.start, tFn.end - 1);
2906
+ const val = _list();
2907
+ _setValue(fn, val);
2453
2908
 
2454
2909
  // Process input
2455
2910
  for (;;) {
@@ -2460,13 +2915,25 @@ const consumeAFunction = (ts) => {
2460
2915
  // <)-token>
2461
2916
  // Discard a token from input. Return function.
2462
2917
  ts.discard();
2463
- fn.end = t.end;
2918
+ _setEnd(fn, t.end);
2464
2919
  return fn;
2465
2920
  }
2466
2921
 
2467
2922
  // anything else
2468
2923
  // Consume a component value from input and append the result to function’s value.
2469
- fn.value.push(consumeAComponentValue(ts));
2924
+ // Same pre-materialization skip as `consumeAListOfComponentValues`.
2925
+ const tt = t.type;
2926
+ if (
2927
+ _skipActive &&
2928
+ tt !== TT_FUNCTION &&
2929
+ !(tt >= TT_LEFT_PARENTHESIS && tt <= TT_LEFT_CURLY_BRACKET) &&
2930
+ _skipTypes[_ttToNodeType[tt]] === 1
2931
+ ) {
2932
+ ts.consume();
2933
+ continue;
2934
+ }
2935
+ const node = consumeAComponentValue(ts, t);
2936
+ if (!_skipActive || _skipTypes[_nType(node)] !== 1) val.push(node);
2470
2937
  }
2471
2938
  };
2472
2939
 
@@ -2644,11 +3111,10 @@ const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
2644
3111
  // CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
2645
3112
  // re-exported so consumers keep importing them from this module.
2646
3113
  /**
2647
- * @typedef {import("../util/SourceProcessor").VisitorContext} VisitorContext
2648
- * @typedef {import("../util/SourceProcessor").VisitorFn<Node>} VisitorFn
2649
- * @typedef {import("../util/SourceProcessor").VisitorBucket<Node>} VisitorBucket
2650
- * @typedef {import("../util/SourceProcessor").VisitorMap<Node>} VisitorMap
2651
- * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<Node>} CompiledVisitorMap
3114
+ * @typedef {import("../util/SourceProcessor").VisitorFn<CssPath>} VisitorFn
3115
+ * @typedef {import("../util/SourceProcessor").VisitorBucket<CssPath>} VisitorBucket
3116
+ * @typedef {import("../util/SourceProcessor").VisitorMap<CssPath>} VisitorMap
3117
+ * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<CssPath>} CompiledVisitorMap
2652
3118
  */
2653
3119
 
2654
3120
  /**
@@ -2672,9 +3138,17 @@ const TOP_LEVEL_CONSUMERS = {
2672
3138
  /**
2673
3139
  * @typedef {object} CssProcessOptions
2674
3140
  * @property {LocConverter=} locConverter shared loc converter (default a fresh one over the input)
2675
- * @property {((input: string, start: number, end: number) => number)=} comment comment-token callback
2676
3141
  * @property {boolean=} recurseBlocks walk into block bodies' nested rules (default true)
2677
3142
  * @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)
3143
+ * @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
3144
+ */
3145
+
3146
+ /**
3147
+ * `CssProcessOptions.skip`: two independent axes, so each reads unambiguously.
3148
+ * @typedef {object} SkipOptions
3149
+ * @property {Uint8Array=} types component-value node types to drop from declaration value / function-arg lists (indexed by `NodeType`, 1 = skip; build with `buildSkipSet`)
3150
+ * @property {boolean=} selectorPrelude drop qualified-rule (selector) preludes — the rule and its block are still produced (default false)
3151
+ * @property {boolean=} atRulePrelude drop at-rule preludes — the at-rule and its block are still produced (default false)
2678
3152
  */
2679
3153
 
2680
3154
  /**
@@ -2688,17 +3162,36 @@ const TOP_LEVEL_CONSUMERS = {
2688
3162
  * @param {CssProcessOptions} options process options
2689
3163
  */
2690
3164
  const grammar = (input, visitors, options) => {
2691
- const { comment } = options;
2692
3165
  const locConverter = options.locConverter || new LocConverter(input);
3166
+ useSoaBackend(input, locConverter);
3167
+ const skip = options.skip;
3168
+ _skipTypes = (skip && skip.types) || _NO_SKIP_TYPES;
3169
+ _skipActive = _skipTypes !== _NO_SKIP_TYPES;
3170
+ _skipSelectorPrelude = skip !== undefined && skip.selectorPrelude === true;
3171
+ _skipAtRulePrelude = skip !== undefined && skip.atRulePrelude === true;
2693
3172
  const recurseBlocks = options.recurseBlocks !== false;
2694
3173
 
2695
- // Babel's `path.skip()`, children-only. Reset per `enter` dispatch.
2696
- let skipFlag = false;
2697
- const visitorCtx = {
2698
- skipChildren() {
2699
- skipFlag = true;
2700
- }
2701
- };
3174
+ // Comments reach the visitor map through `NodeType.Comment` instead of a
3175
+ // side callback. They fire during tokenization — in source order among
3176
+ // comments, not interleaved with the node walk — on a transient SoA node so
3177
+ // `A.start`/`end`/`loc`/`source` work. No comment visitor → no callback →
3178
+ // the tokenizer skips comments with zero overhead.
3179
+ const commentBucket = visitors[T_COMMENT];
3180
+ const onComment =
3181
+ commentBucket === undefined
3182
+ ? undefined
3183
+ : /** @type {(input: string, start: number, end: number) => number} */ (
3184
+ (_input, start, end) => {
3185
+ const node = _soaAlloc(T_COMMENT, start, end);
3186
+ _curNode = node;
3187
+ _curParent = null;
3188
+ const e = commentBucket.enter;
3189
+ for (let i = 0; i < e.length; i++) e[i](A);
3190
+ const x = commentBucket.exit;
3191
+ for (let i = 0; i < x.length; i++) x[i](A);
3192
+ return end;
3193
+ }
3194
+ );
2702
3195
 
2703
3196
  /**
2704
3197
  * Walk a component-value subtree; children are already materialized. Fetches
@@ -2708,29 +3201,28 @@ const grammar = (input, visitors, options) => {
2708
3201
  * @param {Node | null} parent enclosing node
2709
3202
  */
2710
3203
  const walkValue = (node, parent) => {
2711
- const b = visitors[node.type];
3204
+ const ty = _soaTy[_ix(node)];
3205
+ const b = visitors[ty];
2712
3206
  let skip = false;
2713
3207
  if (b !== undefined && b.enter.length !== 0) {
2714
- skipFlag = false;
3208
+ _walkSkip = false;
3209
+ _curNode = node;
3210
+ _curParent = parent;
2715
3211
  const e = b.enter;
2716
- for (let i = 0; i < e.length; i++) e[i](node, parent, visitorCtx);
2717
- skip = skipFlag;
2718
- skipFlag = false;
3212
+ for (let i = 0; i < e.length; i++) e[i](A);
3213
+ skip = _walkSkip;
3214
+ _walkSkip = false;
2719
3215
  }
2720
- if (!skip) {
2721
- switch (node.type) {
2722
- case T_FUNCTION:
2723
- case T_SIMPLE_BLOCK: {
2724
- const v = /** @type {FunctionNode | SimpleBlock} */ (node).value;
2725
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
2726
- break;
2727
- }
2728
- // All other types are leaf tokens.
2729
- }
3216
+ if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3217
+ const v = /** @type {Node[]} */ (_soaL0[_ix(node)]);
3218
+ for (let i = 0; i < v.length; i++) walkValue(v[i], node);
2730
3219
  }
2731
3220
  if (b !== undefined) {
3221
+ // Rebind: descending into children moved the path.
3222
+ _curNode = node;
3223
+ _curParent = parent;
2732
3224
  const x = b.exit;
2733
- for (let i = 0; i < x.length; i++) x[i](node, parent, visitorCtx);
3225
+ for (let i = 0; i < x.length; i++) x[i](A);
2734
3226
  }
2735
3227
  };
2736
3228
 
@@ -2741,54 +3233,67 @@ const grammar = (input, visitors, options) => {
2741
3233
  * @param {Node | null} parent enclosing node
2742
3234
  */
2743
3235
  const walkRule = (node, parent) => {
2744
- const b = visitors[node.type];
3236
+ const i0 = _ix(node);
3237
+ const ty = _soaTy[i0];
3238
+ const b = visitors[ty];
2745
3239
  let skip = false;
2746
3240
  if (b !== undefined && b.enter.length !== 0) {
2747
- skipFlag = false;
3241
+ _walkSkip = false;
3242
+ _curNode = node;
3243
+ _curParent = parent;
2748
3244
  const e = b.enter;
2749
- for (let i = 0; i < e.length; i++) e[i](node, parent, visitorCtx);
2750
- skip = skipFlag;
2751
- skipFlag = false;
3245
+ for (let i = 0; i < e.length; i++) e[i](A);
3246
+ skip = _walkSkip;
3247
+ _walkSkip = false;
2752
3248
  }
2753
3249
  if (!skip) {
2754
- switch (node.type) {
2755
- case T_AT_RULE:
2756
- case T_QUALIFIED_RULE: {
2757
- const r = /** @type {AtRule | QualifiedRule} */ (node);
2758
- const p = r.prelude;
2759
- for (let i = 0; i < p.length; i++) walkValue(p[i], node);
2760
- if (recurseBlocks) {
2761
- // Declarations then child rules downstream consumers don't need them strictly interleaved in source order.
2762
- const decls = r.declarations;
2763
- if (decls) {
2764
- for (let i = 0; i < decls.length; i++) walkRule(decls[i], node);
2765
- }
2766
- const ch = r.childRules;
2767
- if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
3250
+ if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
3251
+ const p = /** @type {Node[]} */ (_soaL0[i0]);
3252
+ for (let i = 0; i < p.length; i++) walkValue(p[i], node);
3253
+ if (recurseBlocks) {
3254
+ // Declarations then child rules — downstream consumers don't need them strictly interleaved in source order.
3255
+ const decls = _soaL1[i0];
3256
+ if (decls) {
3257
+ for (let i = 0; i < decls.length; i++) walkRule(decls[i], node);
2768
3258
  }
2769
- break;
2770
- }
2771
- case T_DECLARATION: {
2772
- const v = /** @type {Declaration} */ (node).value;
2773
- for (let i = 0; i < v.length; i++) walkValue(v[i], node);
2774
- break;
3259
+ const ch = _soaL2[i0];
3260
+ if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
2775
3261
  }
3262
+ } else if (ty === T_DECLARATION) {
3263
+ const v = /** @type {Node[]} */ (_soaL0[i0]);
3264
+ for (let i = 0; i < v.length; i++) walkValue(v[i], node);
2776
3265
  }
2777
3266
  }
2778
3267
  if (b !== undefined) {
3268
+ // Rebind: descending into children moved the path.
3269
+ _curNode = node;
3270
+ _curParent = parent;
2779
3271
  const x = b.exit;
2780
- for (let i = 0; i < x.length; i++) x[i](node, parent, visitorCtx);
3272
+ for (let i = 0; i < x.length; i++) x[i](A);
2781
3273
  }
2782
3274
  };
2783
3275
 
2784
3276
  // Stream each top-level node (selected by `as`) to the walker the moment it's
2785
3277
  // consumed, rather than collecting them first — so the whole AST is never
2786
3278
  // held at once; peak heap is ~one top-level node's subtree.
2787
- const ts = new TokenStream(input, 0, locConverter, comment);
3279
+ const ts = new TokenStream(input, 0, locConverter, onComment);
2788
3280
  const consume =
2789
3281
  TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
2790
3282
  consumeAStylesheetsContents;
2791
- consume(ts, (node) => walkRule(node, null));
3283
+ try {
3284
+ consume(ts, (node) => {
3285
+ walkRule(node, null);
3286
+ _soaN = 0;
3287
+ });
3288
+ } finally {
3289
+ // Drop the module-level SoA references so the last parsed source (and
3290
+ // its LocConverter / child lists) don't stay alive between parses.
3291
+ _soaInput = "";
3292
+ _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
3293
+ _soaL0.length = 0;
3294
+ _soaL1.length = 0;
3295
+ _soaL2.length = 0;
3296
+ }
2792
3297
  };
2793
3298
 
2794
3299
  /**
@@ -2796,23 +3301,303 @@ const grammar = (input, visitors, options) => {
2796
3301
  * `grammar`. Babel-style usage:
2797
3302
  *
2798
3303
  * ```
2799
- * processor.use({ [NodeType.AtRule]: (at) => {}, [NodeType.Declaration]: { enter, exit } });
2800
- * processor.process(source);
3304
+ * new SourceProcessor({ skip }).use({ [NodeType.AtRule]: (path) => {} }).process(source);
2801
3305
  * ```
2802
- * @extends {GenericSourceProcessor<Node, CssProcessOptions>}
3306
+ * @extends {GenericSourceProcessor<CssPath, CssProcessOptions>}
2803
3307
  */
2804
3308
  class SourceProcessor extends GenericSourceProcessor {
2805
- constructor() {
2806
- super(grammar);
3309
+ /**
3310
+ * @param {CssProcessOptions=} options default process options (`skip`, `as`, …) for every `process` call
3311
+ */
3312
+ constructor(options) {
3313
+ super(grammar, options);
2807
3314
  }
2808
3315
  }
2809
3316
 
3317
+ /**
3318
+ * Build a `SkipOptions.types` set (drop these component-value node types from
3319
+ * value / function-arg lists) from a list of `NodeType`s. Preludes are separate
3320
+ * (`SkipOptions.selectorPrelude` / `atRulePrelude`). The caller owns the safety
3321
+ * contract: only pass types nothing reads in the intended parse. Two
3322
+ * grammar-internal caveats beyond consumer needs: dropping both `Delim` and
3323
+ * `Ident` loses `!important` detection, and dropping `SimpleBlock` loses the
3324
+ * custom-property `{}`-value check (and its subtree). Precompute once per
3325
+ * configuration and reuse across parses.
3326
+ * @param {number[]} nodeTypes component-value node types to drop
3327
+ * @returns {Uint8Array} skip-types set indexed by `NodeType`
3328
+ */
3329
+ const buildSkipSet = (nodeTypes) => {
3330
+ const set = new Uint8Array(32);
3331
+ for (let i = 0; i < nodeTypes.length; i++) set[nodeTypes[i]] = 1;
3332
+ return set;
3333
+ };
3334
+
3335
+ /* eslint-disable jsdoc/require-template -- `A` below is the accessor const, not a type parameter */
3336
+ /**
3337
+ * The CSS path (Babel's `path` shape): the AST accessor with the walk's
3338
+ * current position on it — the single argument every visitor receives.
3339
+ * @typedef {typeof A} CssPath
3340
+ */
3341
+ /* eslint-enable jsdoc/require-template */
3342
+
3343
+ // Babel's `path.skip()`, children-only: set by `A.skipChildren()` during an
3344
+ // `enter` dispatch, consumed by the walk.
3345
+ let _walkSkip = false;
3346
+ // The walk's current position (`A.node` / `A.parent` read these; module-level
3347
+ // so the accessor methods' defaults avoid self-referential `this` typing).
3348
+ /** @type {Node} */
3349
+ let _curNode = /** @type {Node} */ (/** @type {unknown} */ (0));
3350
+ /** @type {Node | null} */
3351
+ let _curParent = null;
3352
+
3353
+ // AST field-access seam. Every AST-node field read by `CssParser` goes through
3354
+ // one of these accessors so the node representation can change underneath the
3355
+ // consumer without touching it. Today they are backed by the `Node` / `Token` /
3356
+ // `Container` objects (`n` is a node); the Struct-of-Arrays migration rewrites
3357
+ // the bodies to index typed arrays (`n` becomes an integer node id) without any
3358
+ // consumer edit. `value` is the leaf-token string; container child lists are
3359
+ // `children` / `prelude` / `declarations` / `childRules`.
3360
+ const A = {
3361
+ // === path position (rebound by the walk before every visitor call) ===
3362
+ /**
3363
+ * @returns {Node} current node — only valid during a visitor callback
3364
+ */
3365
+ get node() {
3366
+ return _curNode;
3367
+ },
3368
+ /**
3369
+ * @returns {Node | null} enclosing node (null = a top-level node)
3370
+ */
3371
+ get parent() {
3372
+ return _curParent;
3373
+ },
3374
+ /** Stop the walk descending into the current node (enter only). */
3375
+ skipChildren() {
3376
+ _walkSkip = true;
3377
+ },
3378
+ // === field reads — `n` defaults to the current node ===
3379
+ /**
3380
+ * @param {Node=} n node
3381
+ * @returns {number} node type
3382
+ */
3383
+ type(n = _curNode) {
3384
+ return _soaTy[_ix(n)];
3385
+ },
3386
+ /**
3387
+ * @param {Node=} n node
3388
+ * @returns {number} start offset
3389
+ */
3390
+ start(n = _curNode) {
3391
+ return _soaSt[_ix(n)];
3392
+ },
3393
+ /**
3394
+ * @param {Node=} n node
3395
+ * @returns {number} end offset
3396
+ */
3397
+ end(n = _curNode) {
3398
+ return _soaEn[_ix(n)];
3399
+ },
3400
+ /**
3401
+ * @param {Node=} n node
3402
+ * @returns {[number, number]} start / end offsets
3403
+ */
3404
+ range(n = _curNode) {
3405
+ const i = _ix(n);
3406
+ return [_soaSt[i], _soaEn[i]];
3407
+ },
3408
+ /**
3409
+ * @param {Node=} n node
3410
+ * @returns {{ start: { line: number, column: number }, end: { line: number, column: number } }} source location
3411
+ */
3412
+ loc(n = _curNode) {
3413
+ const i = _ix(n);
3414
+ const lc = _soaLC;
3415
+ const s = lc.get(_soaSt[i]);
3416
+ const sl = s.line;
3417
+ const sc = s.column;
3418
+ const e = lc.get(_soaEn[i]);
3419
+ return {
3420
+ start: { line: sl, column: sc },
3421
+ end: { line: e.line, column: e.column }
3422
+ };
3423
+ },
3424
+ /**
3425
+ * @param {Node=} n node
3426
+ * @returns {string} raw source slice
3427
+ */
3428
+ source(n = _curNode) {
3429
+ const i = _ix(n);
3430
+ return _soaInput.slice(_soaSt[i], _soaEn[i]);
3431
+ },
3432
+ /**
3433
+ * @param {Node=} n node
3434
+ * @returns {string} raw token value
3435
+ */
3436
+ value(n = _curNode) {
3437
+ return _soaValueOf(_ix(n));
3438
+ },
3439
+ /**
3440
+ * @param {Node=} n node
3441
+ * @returns {string} unescaped token value
3442
+ */
3443
+ unescaped(n = _curNode) {
3444
+ const i = _ix(n);
3445
+ const v = _soaValueOf(i);
3446
+ return _soaTy[i] === T_STRING
3447
+ ? unescapeIdentifier(v.slice(1, -1))
3448
+ : unescapeIdentifier(v);
3449
+ },
3450
+ /**
3451
+ * @param {Node=} n node
3452
+ * @returns {string} hash / numeric type flag
3453
+ */
3454
+ typeFlag(n = _curNode) {
3455
+ const i = _ix(n);
3456
+ if (_soaTy[i] === T_HASH) {
3457
+ const input = _soaInput;
3458
+ const p = _soaSt[i] + 1;
3459
+ return _ifThreeCodePointsWouldStartAnIdentSequence(
3460
+ input,
3461
+ p,
3462
+ input.charCodeAt(p),
3463
+ input.charCodeAt(p + 1),
3464
+ input.charCodeAt(p + 2)
3465
+ )
3466
+ ? "id"
3467
+ : "unrestricted";
3468
+ }
3469
+ const v = _soaValueOf(i);
3470
+ return _typeFlagOf(
3471
+ _soaTy[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3472
+ );
3473
+ },
3474
+ /**
3475
+ * @param {Node=} n node
3476
+ * @returns {number} url content start offset
3477
+ */
3478
+ contentStart(n = _curNode) {
3479
+ return _soaA0[_ix(n)];
3480
+ },
3481
+ /**
3482
+ * @param {Node=} n node
3483
+ * @returns {number} url content end offset
3484
+ */
3485
+ contentEnd(n = _curNode) {
3486
+ return _soaA1[_ix(n)];
3487
+ },
3488
+ /**
3489
+ * @param {Node=} n node
3490
+ * @returns {string} rule / declaration / function name
3491
+ */
3492
+ name(n = _curNode) {
3493
+ const i = _ix(n);
3494
+ return _soaTy[i] === T_AT_RULE
3495
+ ? _soaInput.slice(_soaSt[i] + 1, _soaA0[i])
3496
+ : _soaInput.slice(_soaSt[i], _soaA0[i]);
3497
+ },
3498
+ /**
3499
+ * @param {Node=} n node
3500
+ * @returns {number} name start offset
3501
+ */
3502
+ nameStart(n = _curNode) {
3503
+ return _soaSt[_ix(n)];
3504
+ },
3505
+ /**
3506
+ * @param {Node=} n node
3507
+ * @returns {number} name end offset
3508
+ */
3509
+ nameEnd(n = _curNode) {
3510
+ return _soaA0[_ix(n)];
3511
+ },
3512
+ /**
3513
+ * @param {Node=} n node
3514
+ * @returns {string} unescaped name
3515
+ */
3516
+ unescapedName(n = _curNode) {
3517
+ return unescapeIdentifier(A.name(n));
3518
+ },
3519
+ /**
3520
+ * @param {Node=} n node
3521
+ * @returns {ComponentValue[]} function / block children
3522
+ */
3523
+ children(n = _curNode) {
3524
+ return /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]);
3525
+ },
3526
+ /**
3527
+ * @param {Node=} n node
3528
+ * @returns {ComponentValue[]} rule prelude
3529
+ */
3530
+ prelude(n = _curNode) {
3531
+ return /** @type {ComponentValue[]} */ (_soaL0[_ix(n)]);
3532
+ },
3533
+ /**
3534
+ * @param {Node=} n node
3535
+ * @returns {Declaration[] | null} block declarations
3536
+ */
3537
+ declarations(n = _curNode) {
3538
+ return /** @type {Declaration[] | null} */ (_soaL1[_ix(n)]);
3539
+ },
3540
+ /**
3541
+ * @param {Node=} n node
3542
+ * @returns {Rule[] | null} block child rules
3543
+ */
3544
+ childRules(n = _curNode) {
3545
+ return /** @type {Rule[] | null} */ (_soaL2[_ix(n)]);
3546
+ },
3547
+ /**
3548
+ * @param {Node=} n node
3549
+ * @returns {number} block start offset
3550
+ */
3551
+ blockStart(n = _curNode) {
3552
+ return _soaA1[_ix(n)];
3553
+ },
3554
+ /**
3555
+ * @param {Node=} n node
3556
+ * @returns {number} block end offset
3557
+ */
3558
+ blockEnd(n = _curNode) {
3559
+ return _soaA2[_ix(n)];
3560
+ },
3561
+ /**
3562
+ * @param {Node=} n node
3563
+ * @returns {boolean} `!important` flag
3564
+ */
3565
+ important(n = _curNode) {
3566
+ return (_soaFl[_ix(n)] & 1) !== 0;
3567
+ },
3568
+ /**
3569
+ * @param {Node=} n node
3570
+ * @returns {SimpleBlockToken} block opening token
3571
+ */
3572
+ blockToken(n = _curNode) {
3573
+ return /** @type {SimpleBlockToken} */ (_soaInput[_soaSt[_ix(n)]]);
3574
+ },
3575
+ // Writers — `CssParser` rewrites a rule's end / block-end when it folds an
3576
+ // inline ICSS `:import` / `:export` body into a single dependency. The
3577
+ // node stays explicit here: writes should never be implicit on position.
3578
+ /**
3579
+ * @param {Node} n node
3580
+ * @param {number} v new end offset
3581
+ */
3582
+ setEnd(n, v) {
3583
+ _soaEn[_ix(n)] = v;
3584
+ },
3585
+ /**
3586
+ * @param {Node} n node
3587
+ * @param {number} v new block end offset
3588
+ */
3589
+ setBlockEnd(n, v) {
3590
+ _soaA2[_ix(n)] = v;
3591
+ }
3592
+ };
3593
+
2810
3594
  // The two AST runtime classes — `Node` and its sole subclass `Token` (the
2811
3595
  // other node shapes are `@typedef`s over `Node`, exported as types only). Plus
2812
3596
  // the full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, `consumeASimpleBlock`
2813
3597
  // (the one §5.4 algorithm exposed as a byte entry point for `CssParser`), the
2814
3598
  // `TokenStream` (so callers can pass a pre-built stream to any `parseA*`), and
2815
3599
  // the `escape` / `unescapeIdentifier` string utils.
3600
+ module.exports.A = A;
2816
3601
  module.exports.Node = Node;
2817
3602
  module.exports.NodeType = NodeType;
2818
3603
  module.exports.SourceProcessor = SourceProcessor;
@@ -2844,6 +3629,7 @@ module.exports.TT_URL = TT_URL;
2844
3629
  module.exports.TT_WHITESPACE = TT_WHITESPACE;
2845
3630
  module.exports.Token = Token;
2846
3631
  module.exports.TokenStream = TokenStream;
3632
+ module.exports.buildSkipSet = buildSkipSet;
2847
3633
  module.exports.equalsLowerCase = equalsLowerCase;
2848
3634
  module.exports.escapeIdentifier = escapeIdentifier;
2849
3635
  module.exports.parseABlocksContents = parseABlocksContents;