webpack 5.108.1 → 5.108.3

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,18 +297,6 @@ 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;
312
-
313
300
  /**
314
301
  * @param {number} cc char code
315
302
  * @returns {boolean} true, if cc is a digit
@@ -482,11 +469,17 @@ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
482
469
  * @returns {number} position just past the last ident-sequence code point
483
470
  */
484
471
  const _consumeAnIdentSequence = (input, pos) => {
472
+ // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks
473
+ // 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.
485
476
  for (;;) {
486
477
  const cc = input.charCodeAt(pos);
487
478
  pos++;
488
- if (_isIdentCodePoint(cc)) continue;
489
- if (_ifTwoCodePointsAreValidEscape(input, pos, cc)) {
479
+ if (cc < 128 ? _identCharTable[cc] === 1 : _isIdentStartCodePointCC(cc)) {
480
+ continue;
481
+ }
482
+ if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) {
490
483
  pos = _consumeAnEscapedCodePoint(input, pos);
491
484
  continue;
492
485
  }
@@ -950,6 +943,96 @@ function consumeReverseSolidus(input, pos, out) {
950
943
  return fill(out, TT_DELIM, pos - 1, pos);
951
944
  }
952
945
 
946
+ // `consumeAToken` dispatch: the §4 token rules keyed by the lead code point are
947
+ // === Tokenizer lead-character dispatch (CSS Syntax Level 3 §4 "consume a token") ===
948
+ //
949
+ // `consumeAToken` selects a sub-routine from the first ("lead") code point of each
950
+ // token. The §4 rules are keyed on specific code points (`"` `#` `(` digit
951
+ // ident-start …) that sit SPARSELY across the ASCII range, so a plain `switch (cc)`
952
+ // compiles to a jump table spanning U+0009..U+007D in which the most common lead —
953
+ // an ident-start letter — is not a case and reaches its handler only after the
954
+ // digit/whitespace tests miss. `_charClass` precomputes, for every ASCII code
955
+ // point, a dense handler id (`HC_*`, 0..12) so `consumeAToken` is one array load +
956
+ // a compact 13-entry jump table and idents dispatch directly. Non-ASCII
957
+ // (cc >= 128) is always ident-start per §4, so it skips the table.
958
+ //
959
+ // Extending for a spec change: repoint the code point in the build loop below; if
960
+ // it needs a new sub-routine, add an `HC_*` id, a `case` in `consumeAToken`, and a
961
+ // row here. This list is the authoritative "which lead code point dispatches
962
+ // where" map (§4 "consume a token", step by lead code point):
963
+ //
964
+ // HC_WHITESPACE whitespace U+0009 TAB U+000A LF U+000C FF U+000D CR U+0020 SPACE
965
+ // HC_STRING string start U+0022 " U+0027 '
966
+ // HC_SINGLE one-char token ( ) , : ; [ ] { } (its token type comes from `_singleTT`)
967
+ // HC_NUMBER_SIGN hash / delim U+0023 #
968
+ // HC_PLUS_SIGN number / delim U+002B +
969
+ // HC_HYPHEN_MINUS number / CDC / ident / delim U+002D -
970
+ // HC_FULL_STOP number / delim U+002E .
971
+ // HC_LESS_THAN CDO / delim U+003C <
972
+ // HC_AT_SIGN at-keyword / delim U+0040 @
973
+ // HC_REVERSE_SOLIDUS escape / delim U+005C \
974
+ // HC_DIGIT number U+0030..U+0039 0-9
975
+ // HC_IDENT ident-like U+0041..U+005A A-Z U+0061..U+007A a-z U+005F _ (plus cc >= 128)
976
+ // HC_DELIM anything else -> a single <delim-token>
977
+ //
978
+ // `_singleTT[cc]` is the token type for the HC_SINGLE code points (a second table
979
+ // so they share one handler instead of one `case` each). The default class 0 is
980
+ // the delim handler (anything not matched below), so it needs no named constant.
981
+ const HC_WHITESPACE = 1;
982
+ const HC_STRING = 2;
983
+ const HC_SINGLE = 3;
984
+ const HC_NUMBER_SIGN = 4;
985
+ const HC_PLUS_SIGN = 5;
986
+ const HC_HYPHEN_MINUS = 6;
987
+ const HC_FULL_STOP = 7;
988
+ const HC_LESS_THAN = 8;
989
+ const HC_AT_SIGN = 9;
990
+ const HC_REVERSE_SOLIDUS = 10;
991
+ const HC_DIGIT = 11;
992
+ const HC_IDENT = 12;
993
+ const _charClass = new Uint8Array(128);
994
+ const _singleTT = new Uint8Array(128);
995
+ _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS;
996
+ _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS;
997
+ _singleTT[CC_COMMA] = TT_COMMA;
998
+ _singleTT[CC_COLON] = TT_COLON;
999
+ _singleTT[CC_SEMICOLON] = TT_SEMICOLON;
1000
+ _singleTT[CC_LEFT_SQUARE] = TT_LEFT_SQUARE_BRACKET;
1001
+ _singleTT[CC_RIGHT_SQUARE] = TT_RIGHT_SQUARE_BRACKET;
1002
+ _singleTT[CC_LEFT_CURLY] = TT_LEFT_CURLY_BRACKET;
1003
+ _singleTT[CC_RIGHT_CURLY] = TT_RIGHT_CURLY_BRACKET;
1004
+ // Each ASCII code point belongs to exactly one class; HC_SINGLE is seeded from
1005
+ // `_singleTT` above, the rest follow §4's lead-code-point rules, and everything
1006
+ // unmatched stays the delim class (0). Keep this in sync with the table above.
1007
+ for (let i = 0; i < 128; i++) {
1008
+ if (_singleTT[i] !== 0) {
1009
+ _charClass[i] = HC_SINGLE;
1010
+ } else if (_isWhiteSpace(i)) {
1011
+ _charClass[i] = HC_WHITESPACE;
1012
+ } else if (i === CC_QUOTATION_MARK || i === CC_APOSTROPHE) {
1013
+ _charClass[i] = HC_STRING;
1014
+ } else if (i === CC_NUMBER_SIGN) {
1015
+ _charClass[i] = HC_NUMBER_SIGN;
1016
+ } else if (i === CC_PLUS_SIGN) {
1017
+ _charClass[i] = HC_PLUS_SIGN;
1018
+ } else if (i === CC_HYPHEN_MINUS) {
1019
+ _charClass[i] = HC_HYPHEN_MINUS;
1020
+ } else if (i === CC_FULL_STOP) {
1021
+ _charClass[i] = HC_FULL_STOP;
1022
+ } else if (i === CC_LESS_THAN_SIGN) {
1023
+ _charClass[i] = HC_LESS_THAN;
1024
+ } else if (i === CC_AT_SIGN) {
1025
+ _charClass[i] = HC_AT_SIGN;
1026
+ } else if (i === CC_REVERSE_SOLIDUS) {
1027
+ _charClass[i] = HC_REVERSE_SOLIDUS;
1028
+ } else if (_isDigit(i)) {
1029
+ _charClass[i] = HC_DIGIT;
1030
+ } else if (_isIdentStartCodePointCC(i)) {
1031
+ _charClass[i] = HC_IDENT;
1032
+ }
1033
+ // else stays the delim class (0)
1034
+ }
1035
+
953
1036
  /**
954
1037
  * Per-character dispatcher. The outer loop has already advanced past
955
1038
  * the lead code point (`pos - 1` is the lead).
@@ -960,65 +1043,50 @@ function consumeReverseSolidus(input, pos, out) {
960
1043
  * @returns {MutableToken | undefined} the resulting token, or undefined at EOF
961
1044
  */
962
1045
  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:
1046
+ // `u` / `U` would start a unicode-range token in the spec; those are not
1047
+ // produced, so they map to HC_IDENT and fall through to ident-like.
1048
+ switch (cc < 128 ? _charClass[cc] : HC_IDENT) {
1049
+ // Run of whitespace → one <whitespace-token>.
1050
+ case HC_WHITESPACE:
969
1051
  return consumeSpace(input, pos, out);
970
- case CC_QUOTATION_MARK:
971
- case CC_APOSTROPHE:
1052
+ // `"` / `'` → <string-token> (or <bad-string-token> on a raw newline).
1053
+ case HC_STRING:
972
1054
  return consumeAStringToken(input, pos, out);
973
- case CC_NUMBER_SIGN:
1055
+ // One-code-point token: its type is looked up in `_singleTT` (the `(` `)`
1056
+ // `,` `:` `;` `[` `]` `{` `}` set), so all of them share this arm.
1057
+ case HC_SINGLE:
1058
+ return fill(out, _singleTT[cc], pos - 1, pos);
1059
+ // `#` → <hash-token> if an ident/escape follows, else a <delim-token>.
1060
+ case HC_NUMBER_SIGN:
974
1061
  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:
1062
+ // `+` → <number-token> if it starts a number, else a <delim-token>.
1063
+ case HC_PLUS_SIGN:
980
1064
  return consumePlusSign(input, pos, out);
981
- case CC_COMMA:
982
- return fill(out, TT_COMMA, pos - 1, pos);
983
- case CC_HYPHEN_MINUS:
1065
+ // `-` → number / <CDC-token> (`-->`) / ident / <delim-token>.
1066
+ case HC_HYPHEN_MINUS:
984
1067
  return consumeHyphenMinus(input, pos, out);
985
- case CC_FULL_STOP:
1068
+ // `.` → <number-token> if a digit follows, else a <delim-token>.
1069
+ case HC_FULL_STOP:
986
1070
  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:
1071
+ // `<` → <CDO-token> (`<!--`), else a <delim-token>.
1072
+ case HC_LESS_THAN:
992
1073
  return consumeLessThan(input, pos, out);
993
- case CC_AT_SIGN:
1074
+ // `@` → <at-keyword-token> if an ident follows, else a <delim-token>.
1075
+ case HC_AT_SIGN:
994
1076
  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:
1077
+ // `\` → ident-like token if it's a valid escape, else a <delim-token>.
1078
+ case HC_REVERSE_SOLIDUS:
998
1079
  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);
1080
+ // Digit → numeric token; `pos - 1` re-includes the digit the caller passed.
1081
+ case HC_DIGIT:
1082
+ return consumeANumericToken(input, pos - 1, out);
1083
+ // Ident-start (letter / `_` / non-ASCII, incl. `u`/`U`) → ident / function /
1084
+ // url token; `pos - 1` re-includes the lead code point.
1085
+ case HC_IDENT:
1086
+ return consumeAnIdentLikeToken(input, pos - 1, out);
1005
1087
  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.
1088
+ // HC_DELIM. EOF is impossible here (caller guarded with the outer
1089
+ // loop's `pos < input.length` check). Anything else: a <delim-token>.
1022
1090
  return fill(out, TT_DELIM, pos - 1, pos);
1023
1091
  }
1024
1092
  }
@@ -1043,18 +1111,16 @@ function readToken(input, pos, out) {
1043
1111
  // Comment: `/*…*/` is yielded as a token (filtered by `next`).
1044
1112
  if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) {
1045
1113
  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
- }
1114
+ // Jump to the closing `*/` in one native scan instead of a per-character
1115
+ // loop — comment bodies (license banners, source comments) can be long.
1116
+ // No close: unterminated comment runs to EOF so ranges cover all input.
1117
+ const close = input.indexOf("*/", pos + 2);
1118
+ return fill(
1119
+ out,
1120
+ TT_COMMENT,
1121
+ start,
1122
+ close === -1 ? input.length : close + 2
1123
+ );
1058
1124
  }
1059
1125
  // `consumeAToken` dispatches on the lead code point at `pos` (it expects the
1060
1126
  // position just past the lead and the already-read lead code point).
@@ -1240,26 +1306,28 @@ class Token extends Node {
1240
1306
  * @param {number} end byte offset just past the token's last code point
1241
1307
  * @param {LocConverter} locConverter shared loc converter
1242
1308
  */
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
- }
1309
+ // No own fields: a leaf token is exactly a `Node` plus the value getters
1310
+ // below. `value` is derived from the byte range on read instead of cached in
1311
+ // a `_value` slot most tokens (whitespace, punctuation) never read it, and
1312
+ // dropping the slot is ~8 bytes saved on every token (the bulk of all nodes).
1313
+ // hash / at-keyword strip their `#` / `@` prefix; url uses its content range
1314
+ // (`contentStart` / `contentEnd`, the token's only own fields).
1252
1315
 
1253
1316
  /**
1254
1317
  * @returns {string} the token's value (raw source slice unless overridden)
1255
1318
  */
1256
1319
  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
- ));
1320
+ const input = this._locConverter._input;
1321
+ const type = this.type;
1322
+ // hash (`#name` `name`) and at-keyword (`@name` → `name`) drop one char.
1323
+ if (type === T_HASH || type === T_AT_KEYWORD) {
1324
+ return input.slice(this.start + 1, this.end);
1325
+ }
1326
+ if (type === T_URL) {
1327
+ const u = /** @type {UrlToken} */ (/** @type {unknown} */ (this));
1328
+ return input.slice(u.contentStart, u.contentEnd);
1329
+ }
1330
+ return input.slice(this.start, this.end);
1263
1331
  }
1264
1332
 
1265
1333
  /**
@@ -1297,19 +1365,28 @@ class Token extends Node {
1297
1365
 
1298
1366
  /**
1299
1367
  * 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`).
1368
+ * (derived from `value`); for hash tokens it's "id" / "unrestricted" (re-derived
1369
+ * from the source). The two senses share the name in the spec; both are computed
1370
+ * on read so every leaf token keeps the same object shape (no own `typeFlag`).
1303
1371
  * @returns {"integer" | "number" | "id" | "unrestricted"} the spec type flag
1304
1372
  */
1305
1373
  get typeFlag() {
1306
1374
  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";
1375
+ // Re-derive id-ness from the source (whether the name after `#` starts an
1376
+ // ident sequence) rather than storing an `_isId` slot keeps hash tokens
1377
+ // the same shape as every other leaf token. Matches the lexer's
1378
+ // `consumeNumberSign`, which sets `isId` from the same check.
1379
+ const input = this._locConverter._input;
1380
+ const p = this.start + 1;
1381
+ return _ifThreeCodePointsWouldStartAnIdentSequence(
1382
+ input,
1383
+ p,
1384
+ input.charCodeAt(p),
1385
+ input.charCodeAt(p + 1),
1386
+ input.charCodeAt(p + 2)
1387
+ )
1388
+ ? "id"
1389
+ : "unrestricted";
1313
1390
  }
1314
1391
  const v = this.value;
1315
1392
  return _typeFlagOf(
@@ -1333,6 +1410,54 @@ class Token extends Node {
1333
1410
  }
1334
1411
  }
1335
1412
 
1413
+ /**
1414
+ * The non-leaf `Node` subclass: functions, simple blocks, declarations, at-rules
1415
+ * and qualified rules. It declares the union of every container field up front so
1416
+ * all five share **one** hidden class — the consume algorithms only overwrite the
1417
+ * slots relevant to their type, never adding a property, so the shape never
1418
+ * transitions. This caps the shape count: the walker's hot `.type` / `.value` /
1419
+ * `.prelude` loads otherwise see eight distinct node maps (five container types
1420
+ * plus the token / hash / url token maps), tipping V8's inline cache into the
1421
+ * slow megamorphic path; folding the five containers into one leaves four maps
1422
+ * (token, hash, url, container) — within the polymorphic limit, so those loads
1423
+ * stay inline-cached. Unused-for-the-type slots keep their defaults (the field
1424
+ * typedefs below document which fields each type uses). The stylesheet node is
1425
+ * rare (one per parse) and stays a bare `Node`.
1426
+ */
1427
+ class Container extends Node {
1428
+ /**
1429
+ * @param {number} type node type
1430
+ * @param {number} start byte offset of the node's first code point
1431
+ * @param {number} end byte offset just past the node's last code point
1432
+ * @param {LocConverter} locConverter shared loc converter
1433
+ */
1434
+ constructor(type, start, end, locConverter) {
1435
+ super(type, start, end, locConverter);
1436
+ /** @type {string} name (function / at-rule / declaration) */
1437
+ this.name = "";
1438
+ /** @type {number} */
1439
+ this.nameStart = start;
1440
+ /** @type {number} */
1441
+ this.nameEnd = start;
1442
+ /** @type {ComponentValue[] | null} component values (function / block / declaration) */
1443
+ this.value = null;
1444
+ /** @type {ComponentValue[] | null} prelude (at-rule / qualified rule) */
1445
+ this.prelude = null;
1446
+ /** @type {Declaration[] | null} */
1447
+ this.declarations = null;
1448
+ /** @type {Rule[] | null} */
1449
+ this.childRules = null;
1450
+ /** @type {number} `{` start offset, or -1 (at-rule / qualified rule) */
1451
+ this.blockStart = -1;
1452
+ /** @type {number} `}` end offset, or -1 (at-rule / qualified rule) */
1453
+ this.blockEnd = -1;
1454
+ /** @type {boolean} stripped `!important` (declaration) */
1455
+ this.important = false;
1456
+ /** @type {SimpleBlockToken | undefined} opening char (simple block) */
1457
+ this.token = undefined;
1458
+ }
1459
+ }
1460
+
1336
1461
  /**
1337
1462
  * 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
1463
  * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "" }} NumberToken
@@ -1430,82 +1555,320 @@ class Token extends Node {
1430
1555
  * @typedef {Node & { rules: Rule[] }} Stylesheet
1431
1556
  */
1432
1557
 
1558
+ // Lexer-token-type → AST-node-type map. A single \`new Token\` construct site
1559
+ // (vs a ~20-case switch with a \`new Token\` in each arm) keeps V8 on the fast
1560
+ // monomorphic allocation path — the switch form showed up as generic construct
1561
+ // stubs in profiles. URL is the one type with extra own state, handled first.
1562
+ const _ttToNodeType = new Uint8Array(27);
1563
+ _ttToNodeType[TT_WHITESPACE] = T_WHITESPACE;
1564
+ _ttToNodeType[TT_IDENTIFIER] = T_IDENT;
1565
+ _ttToNodeType[TT_STRING] = T_STRING;
1566
+ _ttToNodeType[TT_DELIM] = T_DELIM;
1567
+ _ttToNodeType[TT_NUMBER] = T_NUMBER;
1568
+ _ttToNodeType[TT_PERCENTAGE] = T_PERCENTAGE;
1569
+ _ttToNodeType[TT_DIMENSION] = T_DIMENSION;
1570
+ _ttToNodeType[TT_HASH] = T_HASH;
1571
+ _ttToNodeType[TT_AT_KEYWORD] = T_AT_KEYWORD;
1572
+ _ttToNodeType[TT_BAD_STRING_TOKEN] = T_BAD_STRING;
1573
+ _ttToNodeType[TT_BAD_URL_TOKEN] = T_BAD_URL;
1574
+ _ttToNodeType[TT_COLON] = T_COLON;
1575
+ _ttToNodeType[TT_COMMA] = T_COMMA;
1576
+ _ttToNodeType[TT_SEMICOLON] = T_SEMICOLON;
1577
+ _ttToNodeType[TT_RIGHT_PARENTHESIS] = T_RIGHT_PARENTHESIS;
1578
+ _ttToNodeType[TT_RIGHT_SQUARE_BRACKET] = T_RIGHT_SQUARE_BRACKET;
1579
+ _ttToNodeType[TT_RIGHT_CURLY_BRACKET] = T_RIGHT_CURLY_BRACKET;
1580
+ _ttToNodeType[TT_CDO] = T_CDO;
1581
+ _ttToNodeType[TT_CDC] = T_CDC;
1582
+
1583
+ // === AST construction backend ===
1584
+ // The consume algorithms build nodes through these module-level primitives
1585
+ // rather than `new Token` / `new Container` directly, so the node
1586
+ // representation can be swapped under the parser. The object backend below
1587
+ // builds the retainable `Node` / `Token` / `Container` tree the `parseA*`
1588
+ // entry points return; the Struct-of-Arrays backend (added separately) writes
1589
+ // the same nodes into reused typed arrays for the streaming `grammar`, where
1590
+ // per-node allocation dominates cost. Child lists are plain arrays in both
1591
+ // backends (a node ref is an object or an integer index); only node creation /
1592
+ // field access differs, so only those ops are swapped.
1593
+
1594
+ // Current loc converter for the active parse (object backend reads it).
1595
+ let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1596
+
1597
+ /** @type {(type: number, start: number, end: number) => Node} */
1598
+ let _mkLeaf;
1599
+ /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
1600
+ let _mkUrl;
1601
+ /** @type {(type: number, start: number, end: number) => Node} */
1602
+ let _mkContainer;
1603
+ /** @type {(start: number) => Node} */
1604
+ let _mkStylesheet;
1605
+ /** @type {(r: Node, v: string, nameStart: number, nameEnd: number) => void} */
1606
+ let _setName;
1607
+ /** @type {(r: Node, v: number) => void} */
1608
+ let _setEnd;
1609
+ /** @type {(r: Node, blockStart: number, blockEnd: number) => void} */
1610
+ let _setBlock;
1611
+ /** @type {(r: Node) => void} */
1612
+ let _setImportant;
1613
+ /** @type {(r: Node, ch: SimpleBlockToken) => void} */
1614
+ let _setToken;
1615
+ /** @type {(r: Node, list: Node[]) => void} */
1616
+ let _setValue;
1617
+ /** @type {(r: Node, list: Node[]) => void} */
1618
+ let _setPrelude;
1619
+ /** @type {(r: Node, decls: Node[], childRules: Node[]) => void} */
1620
+ let _setBody;
1621
+ /** @type {(r: Node, list: Node[]) => void} */
1622
+ let _setRules;
1623
+ /** @type {(r: Node) => number} */
1624
+ let _nType;
1625
+ /** @type {(r: Node) => number} */
1626
+ let _nStart;
1627
+ /** @type {(r: Node) => string} */
1628
+ let _nValue;
1629
+ /** @type {(r: Node) => SimpleBlockToken} */
1630
+ let _nToken;
1631
+
1632
+ // Child lists are plain arrays in every backend; refs are pushed by value.
1633
+ const _list = () => /** @type {Node[]} */ ([]);
1634
+
1635
+ // -- 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} */
1639
+ const _objUrl = (start, end, cs, ce) => {
1640
+ const u = /** @type {UrlToken} */ (new Token(T_URL, start, end, _lc));
1641
+ u.contentStart = cs;
1642
+ u.contentEnd = ce;
1643
+ return u;
1644
+ };
1645
+ /** @type {typeof _mkContainer} */
1646
+ const _objContainer = (type, start, end) =>
1647
+ new Container(type, start, end, _lc);
1648
+ /** @type {typeof _mkStylesheet} */
1649
+ const _objStylesheet = (start) => {
1650
+ const s = /** @type {Stylesheet} */ (
1651
+ new Node(T_STYLESHEET, start, start, _lc)
1652
+ );
1653
+ s.rules = [];
1654
+ return s;
1655
+ };
1656
+ /** @param {LocConverter} lc loc converter for this parse */
1657
+ 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);
1704
+ };
1705
+
1706
+ // -- struct-of-arrays backend: writes nodes into reused typed arrays --
1707
+ // 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
1715
+ // `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
1719
+ // 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 = [];
1731
+ /** @type {(Node[] | null)[]} */
1732
+ const _soaL1 = [];
1733
+ /** @type {(Node[] | null)[]} */
1734
+ const _soaL2 = [];
1735
+ let _soaInput = "";
1736
+ let _soaLC = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1737
+
1738
+ // Node refs are integers here but typed `Node` across the parser; these are
1739
+ // identity casts that just satisfy the type system at the boundary.
1740
+ /** @type {(n: Node) => number} */
1741
+ const _ix = (n) => /** @type {number} */ (/** @type {unknown} */ (n));
1742
+ /** @type {(i: number) => Node} */
1743
+ const _ref = (i) => /** @type {Node} */ (/** @type {unknown} */ (i));
1744
+
1745
+ /** @param {number} need minimum capacity */
1746
+ const _soaGrow = (need) => {
1747
+ let cap = _soaCap || 4096;
1748
+ while (cap < need) cap *= 2;
1749
+ const ty = new Uint8Array(cap);
1750
+ ty.set(_soaTy);
1751
+ _soaTy = ty;
1752
+ const st = new Int32Array(cap);
1753
+ st.set(_soaSt);
1754
+ _soaSt = st;
1755
+ const en = new Int32Array(cap);
1756
+ en.set(_soaEn);
1757
+ _soaEn = en;
1758
+ const a0 = new Int32Array(cap);
1759
+ a0.set(_soaA0);
1760
+ _soaA0 = a0;
1761
+ const a1 = new Int32Array(cap);
1762
+ a1.set(_soaA1);
1763
+ _soaA1 = a1;
1764
+ const a2 = new Int32Array(cap);
1765
+ a2.set(_soaA2);
1766
+ _soaA2 = a2;
1767
+ const fl = new Uint8Array(cap);
1768
+ fl.set(_soaFl);
1769
+ _soaFl = fl;
1770
+ _soaCap = cap;
1771
+ };
1772
+ /** @type {(type: number, start: number, end: number) => Node} */
1773
+ const _soaAlloc = (type, start, end) => {
1774
+ // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1775
+ // 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);
1788
+ };
1789
+ // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
1790
+ // one-char prefix, url uses its content range. Shared by the parser's
1791
+ // mid-parse reads and the SoA accessor.
1792
+ /**
1793
+ * @param {number} i node id
1794
+ * @returns {string} raw token value
1795
+ */
1796
+ const _soaValueOf = (i) => {
1797
+ const ty = _soaTy[i];
1798
+ if (ty === T_HASH || ty === T_AT_KEYWORD) {
1799
+ return _soaInput.slice(_soaSt[i] + 1, _soaEn[i]);
1800
+ }
1801
+ if (ty === T_URL) return _soaInput.slice(_soaA0[i], _soaA1[i]);
1802
+ return _soaInput.slice(_soaSt[i], _soaEn[i]);
1803
+ };
1804
+ /**
1805
+ * @param {string} input source
1806
+ * @param {LocConverter} lc loc converter
1807
+ */
1808
+ const useSoaBackend = (input, lc) => {
1809
+ _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)]]);
1856
+ };
1857
+
1433
1858
  /**
1434
1859
  * 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
1860
  * @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}"`);
1861
+ * @returns {Node} the leaf token node
1862
+ */
1863
+ const tokenToNode = (t) => {
1864
+ const tt = t.type;
1865
+ // URL is the only leaf with own state (its content range); all others are a
1866
+ // plain leaf whose node type comes from the map.
1867
+ if (tt === TT_URL) {
1868
+ const ut = /** @type {CssUrlToken} */ (t);
1869
+ return _mkUrl(t.start, t.end, ut.contentStart, ut.contentEnd);
1508
1870
  }
1871
+ return _mkLeaf(_ttToNodeType[tt], t.start, t.end);
1509
1872
  };
1510
1873
 
1511
1874
  /**
@@ -1697,15 +2060,13 @@ const parseAStylesheet = (input, comment) => {
1697
2060
  // 1. If input is a byte stream for a stylesheet, decode bytes from input, and set input to the result.
1698
2061
  // 2. Normalize input, and set input to the result.
1699
2062
  const ts = normalizeIntoTokenStream(input, 0, comment);
2063
+ useObjectBackend(ts.locConverter);
1700
2064
  // 3. Create a new stylesheet, with its location set to location (or null, if location was not passed).
1701
2065
  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[]} */ ([]);
2066
+ const stylesheet = /** @type {Stylesheet} */ (_mkStylesheet(start));
1706
2067
  // 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;
2068
+ _setRules(stylesheet, consumeAStylesheetsContents(ts));
2069
+ _setEnd(stylesheet, ts.next().start);
1709
2070
  // 5. Return the stylesheet.
1710
2071
  return stylesheet;
1711
2072
  };
@@ -1723,6 +2084,7 @@ const parseAStylesheet = (input, comment) => {
1723
2084
  const parseAStylesheetsContents = (input, comment) => {
1724
2085
  // 1. Normalize input, and set input to the result.
1725
2086
  const ts = normalizeIntoTokenStream(input, 0, comment);
2087
+ useObjectBackend(ts.locConverter);
1726
2088
  // 2. Consume a stylesheet’s contents from input, and return the result.
1727
2089
  return consumeAStylesheetsContents(ts);
1728
2090
  };
@@ -1738,6 +2100,7 @@ const parseAStylesheetsContents = (input, comment) => {
1738
2100
  const parseABlocksContents = (input, pos, comment) => {
1739
2101
  // 1. Normalize input, and set input to the result.
1740
2102
  const ts = normalizeIntoTokenStream(input, pos, comment);
2103
+ useObjectBackend(ts.locConverter);
1741
2104
  // 2. Consume a block’s contents from input, and return the result.
1742
2105
  return consumeABlocksContents(ts);
1743
2106
  };
@@ -1755,6 +2118,7 @@ const parseABlocksContents = (input, pos, comment) => {
1755
2118
  const parseARule = (input, pos, comment) => {
1756
2119
  // 1. Normalize input, and set input to the result.
1757
2120
  const ts = normalizeIntoTokenStream(input, pos, comment);
2121
+ useObjectBackend(ts.locConverter);
1758
2122
  // 2. Discard whitespace from input.
1759
2123
  while (ts.next().type === TT_WHITESPACE) ts.discard();
1760
2124
  // 3. If the next token from input is an <EOF-token>, return a syntax error.
@@ -1785,6 +2149,7 @@ const parseARule = (input, pos, comment) => {
1785
2149
  const parseADeclaration = (input, pos, comment) => {
1786
2150
  // 1. Normalize input, and set input to the result.
1787
2151
  const ts = normalizeIntoTokenStream(input, pos, comment);
2152
+ useObjectBackend(ts.locConverter);
1788
2153
  // 2. Discard whitespace from input.
1789
2154
  while (ts.next().type === TT_WHITESPACE) ts.discard();
1790
2155
  // 3. Consume a declaration from input. If anything was returned, return it. Otherwise, return a syntax error.
@@ -1801,6 +2166,7 @@ const parseADeclaration = (input, pos, comment) => {
1801
2166
  const parseAComponentValue = (input, pos, options = {}) => {
1802
2167
  // 1. Normalize input, and set input to the result.
1803
2168
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2169
+ useObjectBackend(ts.locConverter);
1804
2170
  // 2. Discard whitespace from input.
1805
2171
  while (ts.next().type === TT_WHITESPACE) ts.discard();
1806
2172
  // 3. If input is empty, return a syntax error.
@@ -1825,6 +2191,7 @@ const parseAComponentValue = (input, pos, options = {}) => {
1825
2191
  const parseAListOfComponentValues = (input, pos, options = {}) => {
1826
2192
  // 1. Normalize input, and set input to the result.
1827
2193
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2194
+ useObjectBackend(ts.locConverter);
1828
2195
  // 2. Consume a list of component values from input, and return the result.
1829
2196
  return consumeAListOfComponentValues(ts);
1830
2197
  };
@@ -1843,6 +2210,7 @@ const parseACommaSeparatedListOfComponentValues = (
1843
2210
  ) => {
1844
2211
  // 1. Normalize input, and set input to the result.
1845
2212
  const ts = normalizeIntoTokenStream(input, pos, options.comment);
2213
+ useObjectBackend(ts.locConverter);
1846
2214
  // 2. Let groups be an empty list.
1847
2215
  /** @type {ComponentValue[][]} */
1848
2216
  const groups = [];
@@ -1924,17 +2292,18 @@ const consumeAnAtRule = (ts, nested = false) => {
1924
2292
  // 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
2293
  const head = ts.consume();
1926
2294
  const rule = /** @type {AtRule} */ (
1927
- new Node(T_AT_RULE, head.start, head.end, ts.locConverter)
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
1928
2302
  );
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;
2303
+ const prelude = _list();
2304
+ _setPrelude(rule, prelude);
2305
+ // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2306
+ // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
1938
2307
 
1939
2308
  // Process input
1940
2309
  for (;;) {
@@ -1945,7 +2314,7 @@ const consumeAnAtRule = (ts, nested = false) => {
1945
2314
  // Discard a token from input. If rule is valid in the current context, return it; otherwise return nothing.
1946
2315
  if (t.type === TT_SEMICOLON || t.type === TT_EOF) {
1947
2316
  ts.discard();
1948
- rule.end = t.start;
2317
+ _setEnd(rule, t.start);
1949
2318
  return rule;
1950
2319
  }
1951
2320
  // <}-token>
@@ -1953,27 +2322,25 @@ const consumeAnAtRule = (ts, nested = false) => {
1953
2322
  // Otherwise, consume a token and append the result to rule’s prelude.
1954
2323
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
1955
2324
  if (nested) {
1956
- rule.end = t.start;
2325
+ _setEnd(rule, t.start);
1957
2326
  return rule;
1958
2327
  }
1959
- rule.prelude.push(consumeATokenAsNode(ts));
2328
+ prelude.push(consumeATokenAsNode(ts));
1960
2329
  continue;
1961
2330
  }
1962
2331
  // <{-token>
1963
2332
  // Consume a block from input, and assign the result to rule's declarations and child rules.
1964
2333
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
1965
2334
  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;
2335
+ _setBody(rule, block.decls, block.rules);
2336
+ _setBlock(rule, block.blockStart, block.blockEnd);
2337
+ _setEnd(rule, block.blockEnd);
1971
2338
  return rule;
1972
2339
  }
1973
2340
 
1974
2341
  // anything else
1975
2342
  // Consume a component value from input and append the returned value to rule’s prelude.
1976
- rule.prelude.push(consumeAComponentValue(ts));
2343
+ prelude.push(consumeAComponentValue(ts, t));
1977
2344
  }
1978
2345
  };
1979
2346
 
@@ -1988,7 +2355,7 @@ const consumeAnAtRule = (ts, nested = false) => {
1988
2355
  */
1989
2356
  const consumeATokenAsNode = (ts) => {
1990
2357
  const t = ts.consume();
1991
- return tokenToNode(t, ts.input, ts.locConverter);
2358
+ return /** @type {Token} */ (tokenToNode(t));
1992
2359
  };
1993
2360
 
1994
2361
  /**
@@ -2002,14 +2369,12 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2002
2369
  const start = ts.next().start;
2003
2370
  // Let rule be a new qualified rule with its prelude, declarations, and child rules all initially set to empty lists.
2004
2371
  const rule = /** @type {QualifiedRule} */ (
2005
- new Node(T_QUALIFIED_RULE, start, start, ts.locConverter)
2372
+ _mkContainer(T_QUALIFIED_RULE, start, start)
2006
2373
  );
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;
2374
+ const prelude = _list();
2375
+ _setPrelude(rule, prelude);
2376
+ // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2377
+ // defaults (no block until a `{` is reached).
2013
2378
 
2014
2379
  // Process input
2015
2380
  for (;;) {
@@ -2024,7 +2389,7 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2024
2389
  // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
2025
2390
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2026
2391
  if (nested) return undefined;
2027
- rule.prelude.push(consumeATokenAsNode(ts));
2392
+ prelude.push(consumeATokenAsNode(ts));
2028
2393
  continue;
2029
2394
  }
2030
2395
  // <{-token>
@@ -2037,28 +2402,28 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2037
2402
  let firstIdx = 0;
2038
2403
  /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2039
2404
  while (
2040
- firstIdx < rule.prelude.length &&
2041
- rule.prelude[firstIdx].type === T_WHITESPACE
2405
+ firstIdx < prelude.length &&
2406
+ _nType(prelude[firstIdx]) === T_WHITESPACE
2042
2407
  ) {
2043
2408
  firstIdx++;
2044
2409
  }
2045
2410
  let secondIdx = firstIdx + 1;
2046
2411
  while (
2047
- secondIdx < rule.prelude.length &&
2048
- rule.prelude[secondIdx].type === T_WHITESPACE
2412
+ secondIdx < prelude.length &&
2413
+ _nType(prelude[secondIdx]) === T_WHITESPACE
2049
2414
  ) {
2050
2415
  secondIdx++;
2051
2416
  }
2052
- const first = rule.prelude[firstIdx];
2053
- const second = rule.prelude[secondIdx];
2417
+ const first = prelude[firstIdx];
2418
+ const second = prelude[secondIdx];
2054
2419
  if (
2055
2420
  first &&
2056
- first.type === T_IDENT &&
2421
+ _nType(first) === T_IDENT &&
2057
2422
  // Test the source bytes directly — avoids forcing the lazy `value`
2058
2423
  // slice just to check the `--` custom-property prefix.
2059
- ts.input.startsWith("--", first.start) &&
2424
+ ts.input.startsWith("--", _nStart(first)) &&
2060
2425
  second &&
2061
- second.type === T_COLON
2426
+ _nType(second) === T_COLON
2062
2427
  ) {
2063
2428
  /* istanbul ignore if -- @preserve: when nested, `declarationStartLikely` routes every `--x:` to consumeADeclaration (which accepts custom properties), so this fallthrough is unreachable */
2064
2429
  if (nested) {
@@ -2069,17 +2434,15 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2069
2434
  return undefined;
2070
2435
  }
2071
2436
  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;
2437
+ _setBody(rule, block.decls, block.rules);
2438
+ _setBlock(rule, block.blockStart, block.blockEnd);
2439
+ _setEnd(rule, block.blockEnd);
2077
2440
  return rule;
2078
2441
  }
2079
2442
 
2080
2443
  // anything else
2081
2444
  // Consume a component value from input and append the result to rule’s prelude.
2082
- rule.prelude.push(consumeAComponentValue(ts));
2445
+ prelude.push(consumeAComponentValue(ts, t));
2083
2446
  }
2084
2447
  };
2085
2448
 
@@ -2245,22 +2608,17 @@ const consumeADeclaration = (ts, nested = false) => {
2245
2608
  const { input } = ts;
2246
2609
  // Let decl be a new declaration, with an initially empty name and a value set to an empty list.
2247
2610
  const start = ts.next().start;
2611
+ // name "" / nameStart / nameEnd (= start) / important (false) keep their
2612
+ // `Container` defaults; `value` is set unconditionally at step 5 below.
2248
2613
  const decl = /** @type {Declaration} */ (
2249
- new Node(T_DECLARATION, start, start, ts.locConverter)
2614
+ _mkContainer(T_DECLARATION, start, start)
2250
2615
  );
2251
- decl.name = "";
2252
- decl.nameStart = start;
2253
- decl.nameEnd = start;
2254
- decl.value = /** @type {ComponentValue[]} */ ([]);
2255
- decl.important = false;
2256
2616
 
2257
2617
  // 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
2618
  // Otherwise, consume the remnants of a bad declaration from input, with nested, and return nothing.
2259
2619
  if (ts.next().type === TT_IDENTIFIER) {
2260
2620
  const head = ts.consume();
2261
- decl.nameStart = head.start;
2262
- decl.nameEnd = head.end;
2263
- decl.name = input.slice(head.start, head.end);
2621
+ _setName(decl, input.slice(head.start, head.end), head.start, head.end);
2264
2622
  } else {
2265
2623
  consumeTheRemnantsOfABadDeclaration(ts, nested);
2266
2624
  return undefined;
@@ -2282,51 +2640,42 @@ const consumeADeclaration = (ts, nested = false) => {
2282
2640
  while (ts.next().type === TT_WHITESPACE) ts.discard();
2283
2641
 
2284
2642
  // 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;
2643
+ const value = consumeAListOfComponentValues(ts, TT_SEMICOLON, nested);
2644
+ _setValue(decl, value);
2645
+ _setEnd(decl, ts.next().start);
2287
2646
 
2288
2647
  // 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
2648
  {
2290
- let last = decl.value.length - 1;
2291
- while (last >= 0 && decl.value[last].type === T_WHITESPACE) last--;
2649
+ let last = value.length - 1;
2650
+ while (last >= 0 && _nType(value[last]) === T_WHITESPACE) last--;
2292
2651
  let prev = last - 1;
2293
- while (prev >= 0 && decl.value[prev].type === T_WHITESPACE) prev--;
2652
+ while (prev >= 0 && _nType(value[prev]) === T_WHITESPACE) prev--;
2294
2653
  if (
2295
2654
  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
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
2303
2659
  ) {
2304
- decl.important = true;
2305
- decl.value.length = prev;
2660
+ _setImportant(decl);
2661
+ value.length = prev;
2306
2662
  }
2307
2663
  }
2308
2664
 
2309
2665
  // 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();
2666
+ while (value.length > 0 && _nType(value[value.length - 1]) === T_WHITESPACE) {
2667
+ value.pop();
2315
2668
  }
2316
2669
 
2317
2670
  // 8. If decl's name starts with "--" (a custom property), it can contain any value (including a top-level `{}` block) — accept it.
2318
2671
  // Otherwise, if decl's value contains a top-level simple block with an associated token of <{-token>, return nothing.
2319
2672
  // (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
2673
  // 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);
2674
+ const isCustomProperty = input.startsWith("--", start);
2322
2675
  if (!isCustomProperty) {
2323
- const value = decl.value;
2324
2676
  for (let i = 0; i < value.length; i++) {
2325
2677
  const v = value[i];
2326
- if (
2327
- v.type === T_SIMPLE_BLOCK &&
2328
- /** @type {SimpleBlock} */ (v).token === "{"
2329
- ) {
2678
+ if (_nType(v) === T_SIMPLE_BLOCK && _nToken(v) === "{") {
2330
2679
  return undefined;
2331
2680
  }
2332
2681
  }
@@ -2366,17 +2715,19 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2366
2715
  }
2367
2716
  // anything else
2368
2717
  // Consume a component value from input, and append the result to values.
2369
- values.push(consumeAComponentValue(ts));
2718
+ values.push(consumeAComponentValue(ts, t));
2370
2719
  }
2371
2720
  };
2372
2721
 
2373
2722
  /**
2374
2723
  * 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
2724
  * @param {TokenStream} ts token stream
2725
+ * @param {MutableToken=} t the next token, if the caller already peeked it (defaults to `ts.next()`)
2376
2726
  * @returns {SimpleBlock | FunctionNode | ComponentValue} the consumed component value
2377
2727
  */
2378
- const consumeAComponentValue = (ts) => {
2379
- const t = ts.next();
2728
+ const consumeAComponentValue = (ts, t = ts.next()) => {
2729
+ // `t` is the next token; hot callers already peeked it and pass it in to
2730
+ // skip a redundant `ts.next()` per component value.
2380
2731
  // <{-token> / <[-token> / <(-token> (the three contiguous opening brackets)
2381
2732
  // Consume a simple block from input and return the result.
2382
2733
  if (t.type >= TT_LEFT_PARENTHESIS && t.type <= TT_LEFT_CURLY_BRACKET) {
@@ -2389,7 +2740,11 @@ const consumeAComponentValue = (ts) => {
2389
2740
  }
2390
2741
  // anything else
2391
2742
  // Consume a token from input and return the result. (Asserted: not EOF.)
2392
- return consumeATokenAsNode(ts);
2743
+ // Inlined `consumeATokenAsNode`: `t` is already the peeked next token, so
2744
+ // advance past it and materialize it directly — one fewer call per leaf
2745
+ // component value (the bulk of the nodes on a large stylesheet).
2746
+ ts.consume();
2747
+ return /** @type {ComponentValue} */ (tokenToNode(t));
2393
2748
  };
2394
2749
 
2395
2750
  /**
@@ -2406,10 +2761,11 @@ const consumeASimpleBlock = (ts) => {
2406
2761
 
2407
2762
  // 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
2763
  const block = /** @type {SimpleBlock} */ (
2409
- new Node(T_SIMPLE_BLOCK, open.start, open.end, ts.locConverter)
2764
+ _mkContainer(T_SIMPLE_BLOCK, open.start, open.end)
2410
2765
  );
2411
- block.token = token;
2412
- block.value = /** @type {ComponentValue[]} */ ([]);
2766
+ _setToken(block, token);
2767
+ const val = _list();
2768
+ _setValue(block, val);
2413
2769
 
2414
2770
  // Discard a token from input.
2415
2771
  ts.discard();
@@ -2423,13 +2779,13 @@ const consumeASimpleBlock = (ts) => {
2423
2779
  // Discard a token from input. Return block.
2424
2780
  if (t.type === TT_EOF || t.type === ending) {
2425
2781
  ts.discard();
2426
- block.end = t.end;
2782
+ _setEnd(block, t.end);
2427
2783
  return block;
2428
2784
  }
2429
2785
 
2430
2786
  // anything else
2431
2787
  // Consume a component value from input and append the result to block’s value.
2432
- block.value.push(consumeAComponentValue(ts));
2788
+ val.push(consumeAComponentValue(ts, t));
2433
2789
  }
2434
2790
  };
2435
2791
 
@@ -2439,17 +2795,16 @@ const consumeASimpleBlock = (ts) => {
2439
2795
  * @returns {FunctionNode | undefined} the consumed function node
2440
2796
  */
2441
2797
  const consumeAFunction = (ts) => {
2442
- const { input, locConverter } = ts;
2798
+ const { input } = ts;
2443
2799
  // Assert (spec): the next token is a <function-token>.
2444
2800
  // 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
2801
  const tFn = ts.consume();
2446
2802
  const fn = /** @type {FunctionNode} */ (
2447
- new Node(T_FUNCTION, tFn.start, tFn.end, locConverter)
2803
+ _mkContainer(T_FUNCTION, tFn.start, tFn.end)
2448
2804
  );
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[]} */ ([]);
2805
+ _setName(fn, input.slice(tFn.start, tFn.end - 1), tFn.start, tFn.end - 1);
2806
+ const val = _list();
2807
+ _setValue(fn, val);
2453
2808
 
2454
2809
  // Process input
2455
2810
  for (;;) {
@@ -2460,13 +2815,13 @@ const consumeAFunction = (ts) => {
2460
2815
  // <)-token>
2461
2816
  // Discard a token from input. Return function.
2462
2817
  ts.discard();
2463
- fn.end = t.end;
2818
+ _setEnd(fn, t.end);
2464
2819
  return fn;
2465
2820
  }
2466
2821
 
2467
2822
  // anything else
2468
2823
  // Consume a component value from input and append the result to function’s value.
2469
- fn.value.push(consumeAComponentValue(ts));
2824
+ val.push(consumeAComponentValue(ts, t));
2470
2825
  }
2471
2826
  };
2472
2827
 
@@ -2690,6 +3045,7 @@ const TOP_LEVEL_CONSUMERS = {
2690
3045
  const grammar = (input, visitors, options) => {
2691
3046
  const { comment } = options;
2692
3047
  const locConverter = options.locConverter || new LocConverter(input);
3048
+ useSoaBackend(input, locConverter);
2693
3049
  const recurseBlocks = options.recurseBlocks !== false;
2694
3050
 
2695
3051
  // Babel's `path.skip()`, children-only. Reset per `enter` dispatch.
@@ -2708,7 +3064,8 @@ const grammar = (input, visitors, options) => {
2708
3064
  * @param {Node | null} parent enclosing node
2709
3065
  */
2710
3066
  const walkValue = (node, parent) => {
2711
- const b = visitors[node.type];
3067
+ const ty = _soaTy[_ix(node)];
3068
+ const b = visitors[ty];
2712
3069
  let skip = false;
2713
3070
  if (b !== undefined && b.enter.length !== 0) {
2714
3071
  skipFlag = false;
@@ -2717,16 +3074,9 @@ const grammar = (input, visitors, options) => {
2717
3074
  skip = skipFlag;
2718
3075
  skipFlag = false;
2719
3076
  }
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
- }
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);
2730
3080
  }
2731
3081
  if (b !== undefined) {
2732
3082
  const x = b.exit;
@@ -2741,7 +3091,9 @@ const grammar = (input, visitors, options) => {
2741
3091
  * @param {Node | null} parent enclosing node
2742
3092
  */
2743
3093
  const walkRule = (node, parent) => {
2744
- const b = visitors[node.type];
3094
+ const i0 = _ix(node);
3095
+ const ty = _soaTy[i0];
3096
+ const b = visitors[ty];
2745
3097
  let skip = false;
2746
3098
  if (b !== undefined && b.enter.length !== 0) {
2747
3099
  skipFlag = false;
@@ -2751,28 +3103,21 @@ const grammar = (input, visitors, options) => {
2751
3103
  skipFlag = false;
2752
3104
  }
2753
3105
  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);
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);
2768
3114
  }
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;
3115
+ const ch = _soaL2[i0];
3116
+ if (ch) for (let i = 0; i < ch.length; i++) walkRule(ch[i], node);
2775
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);
2776
3121
  }
2777
3122
  }
2778
3123
  if (b !== undefined) {
@@ -2788,7 +3133,10 @@ const grammar = (input, visitors, options) => {
2788
3133
  const consume =
2789
3134
  TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
2790
3135
  consumeAStylesheetsContents;
2791
- consume(ts, (node) => walkRule(node, null));
3136
+ consume(ts, (node) => {
3137
+ walkRule(node, null);
3138
+ _soaN = 0;
3139
+ });
2792
3140
  };
2793
3141
 
2794
3142
  /**
@@ -2807,12 +3155,127 @@ class SourceProcessor extends GenericSourceProcessor {
2807
3155
  }
2808
3156
  }
2809
3157
 
3158
+ // AST field-access seam. Every AST-node field read by `CssParser` goes through
3159
+ // one of these accessors so the node representation can change underneath the
3160
+ // consumer without touching it. Today they are backed by the `Node` / `Token` /
3161
+ // `Container` objects (`n` is a node); the Struct-of-Arrays migration rewrites
3162
+ // the bodies to index typed arrays (`n` becomes an integer node id) without any
3163
+ // consumer edit. `value` is the leaf-token string; container child lists are
3164
+ // `children` / `prelude` / `declarations` / `childRules`.
3165
+ 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]];
3176
+ },
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]);
3182
+ const sl = s.line;
3183
+ const sc = s.column;
3184
+ const e = lc.get(_soaEn[i]);
3185
+ return {
3186
+ start: { line: sl, column: sc },
3187
+ end: { line: e.line, column: e.column }
3188
+ };
3189
+ },
3190
+ /** @type {(n: Node) => string} */
3191
+ source: (n) => {
3192
+ const i = _ix(n);
3193
+ return _soaInput.slice(_soaSt[i], _soaEn[i]);
3194
+ },
3195
+ /** @type {(n: Node) => string} */
3196
+ value: (n) => _soaValueOf(_ix(n)),
3197
+ /** @type {(n: Node) => string} */
3198
+ unescaped: (n) => {
3199
+ const i = _ix(n);
3200
+ const v = _soaValueOf(i);
3201
+ return _soaTy[i] === T_STRING
3202
+ ? unescapeIdentifier(v.slice(1, -1))
3203
+ : unescapeIdentifier(v);
3204
+ },
3205
+ /** @type {(n: Node) => string} */
3206
+ typeFlag: (n) => {
3207
+ const i = _ix(n);
3208
+ if (_soaTy[i] === T_HASH) {
3209
+ const input = _soaInput;
3210
+ const p = _soaSt[i] + 1;
3211
+ return _ifThreeCodePointsWouldStartAnIdentSequence(
3212
+ input,
3213
+ p,
3214
+ input.charCodeAt(p),
3215
+ input.charCodeAt(p + 1),
3216
+ input.charCodeAt(p + 2)
3217
+ )
3218
+ ? "id"
3219
+ : "unrestricted";
3220
+ }
3221
+ const v = _soaValueOf(i);
3222
+ return _typeFlagOf(
3223
+ _soaTy[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3224
+ );
3225
+ },
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]);
3236
+ },
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
+ // 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;
3265
+ },
3266
+ /** @type {(n: Node, v: number) => void} */
3267
+ setBlockEnd: (n, v) => {
3268
+ _soaA2[_ix(n)] = v;
3269
+ }
3270
+ };
3271
+
2810
3272
  // The two AST runtime classes — `Node` and its sole subclass `Token` (the
2811
3273
  // other node shapes are `@typedef`s over `Node`, exported as types only). Plus
2812
3274
  // the full CSS-Syntax-3 §5.3 `parseA*` entry-point surface, `consumeASimpleBlock`
2813
3275
  // (the one §5.4 algorithm exposed as a byte entry point for `CssParser`), the
2814
3276
  // `TokenStream` (so callers can pass a pre-built stream to any `parseA*`), and
2815
3277
  // the `escape` / `unescapeIdentifier` string utils.
3278
+ module.exports.A = A;
2816
3279
  module.exports.Node = Node;
2817
3280
  module.exports.NodeType = NodeType;
2818
3281
  module.exports.SourceProcessor = SourceProcessor;