webpack 5.108.3 → 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
@@ -297,6 +297,15 @@ const _isSpace = (cc) => cc === CC_SPACE || cc === CC_TAB;
297
297
  // rarer tab / newline tests.
298
298
  const _isWhiteSpace = (cc) => _isSpace(cc) || _isNewline(cc);
299
299
 
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;
308
+
300
309
  /**
301
310
  * @param {number} cc char code
302
311
  * @returns {boolean} true, if cc is a digit
@@ -612,7 +621,7 @@ const fill = (out, type, start, end) => {
612
621
  */
613
622
  function consumeSpace(input, pos, out) {
614
623
  const start = pos - 1;
615
- while (_isWhiteSpace(input.charCodeAt(pos))) pos++;
624
+ while (_wsTable[input.charCodeAt(pos)] === 1) pos++;
616
625
  return fill(out, TT_WHITESPACE, start, pos);
617
626
  }
618
627
 
@@ -1166,7 +1175,10 @@ const NodeType = {
1166
1175
  Declaration: 23,
1167
1176
  AtRule: 24,
1168
1177
  QualifiedRule: 25,
1169
- 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
1170
1182
  };
1171
1183
  const {
1172
1184
  Ident: T_IDENT,
@@ -1194,7 +1206,8 @@ const {
1194
1206
  Declaration: T_DECLARATION,
1195
1207
  AtRule: T_AT_RULE,
1196
1208
  QualifiedRule: T_QUALIFIED_RULE,
1197
- Stylesheet: T_STYLESHEET
1209
+ Stylesheet: T_STYLESHEET,
1210
+ Comment: T_COMMENT
1198
1211
  } = NodeType;
1199
1212
 
1200
1213
  /**
@@ -1594,6 +1607,23 @@ _ttToNodeType[TT_CDC] = T_CDC;
1594
1607
  // Current loc converter for the active parse (object backend reads it).
1595
1608
  let _lc = /** @type {LocConverter} */ (/** @type {unknown} */ (null));
1596
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
+
1597
1627
  /** @type {(type: number, start: number, end: number) => Node} */
1598
1628
  let _mkLeaf;
1599
1629
  /** @type {(start: number, end: number, contentStart: number, contentEnd: number) => Node} */
@@ -1656,6 +1686,11 @@ const _objStylesheet = (start) => {
1656
1686
  /** @param {LocConverter} lc loc converter for this parse */
1657
1687
  const useObjectBackend = (lc) => {
1658
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;
1659
1694
  _mkLeaf = _objLeaf;
1660
1695
  _mkUrl = _objUrl;
1661
1696
  _mkContainer = _objContainer;
@@ -1773,18 +1808,26 @@ const _soaGrow = (need) => {
1773
1808
  const _soaAlloc = (type, start, end) => {
1774
1809
  // Ids are 1-based: a node ref is used in truthiness checks (`if (!parent)`),
1775
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.
1776
1813
  const i = _soaN + 1;
1777
1814
  if (i >= _soaCap) _soaGrow(i + 1);
1778
1815
  _soaTy[i] = type;
1779
1816
  _soaSt[i] = start;
1780
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);
1781
1825
  _soaFl[i] = 0;
1782
1826
  // Clear list slots so a reused id never exposes a previous node's children.
1783
1827
  _soaL0[i] = null;
1784
1828
  _soaL1[i] = null;
1785
1829
  _soaL2[i] = null;
1786
- _soaN = i;
1787
- return _ref(i);
1830
+ return r;
1788
1831
  };
1789
1832
  // Raw token value (the lazy `Token.value` form): hash / at-keyword drop their
1790
1833
  // one-char prefix, url uses its content range. Shared by the parser's
@@ -1816,8 +1859,8 @@ const useSoaBackend = (input, lc) => {
1816
1859
  _soaA1[_ix(r)] = ce;
1817
1860
  return r;
1818
1861
  };
1819
- _mkContainer = (type, start, end) => _soaAlloc(type, start, end);
1820
- _mkStylesheet = (start) => _soaAlloc(T_STYLESHEET, start, start);
1862
+ _mkContainer = (type, start, end) => _soaAllocContainer(type, start, end);
1863
+ _mkStylesheet = (start) => _soaAllocContainer(T_STYLESHEET, start, start);
1821
1864
  // name / nameStart are derived from start + nameEnd; only nameEnd is stored.
1822
1865
  _setName = (r, v, ns, ne) => {
1823
1866
  _soaA0[_ix(r)] = ne;
@@ -1918,12 +1961,12 @@ class TokenStream {
1918
1961
  /** @type {number} */
1919
1962
  this._commentHigh = pos;
1920
1963
  // Single reused token the lexer writes into on the `next` path — see
1921
- // `MutableToken`. `_next` points at it when a token is cached, else
1922
- // `undefined` (re-tokenize on next read).
1964
+ // `MutableToken`. `_hasNext` marks it cached a boolean instead of an
1965
+ // object slot, so caching a token never pays a GC write barrier.
1923
1966
  /** @type {MutableToken} */
1924
1967
  this._tok = createToken();
1925
- /** @type {MutableToken | undefined} the next token, lazily tokenized */
1926
- this._next = undefined;
1968
+ /** @type {boolean} whether `_tok` holds the (lazily tokenized) next token */
1969
+ this._hasNext = false;
1927
1970
  /** @type {number[]} byte offsets to rewind to */
1928
1971
  this._marks = [];
1929
1972
  }
@@ -1937,14 +1980,14 @@ class TokenStream {
1937
1980
  * @returns {MutableToken} the next token
1938
1981
  */
1939
1982
  next() {
1940
- if (this._next === undefined) {
1983
+ if (!this._hasNext) {
1941
1984
  const input = this.input;
1942
1985
  const tok = this._tok;
1943
1986
  let pos = this._pos;
1944
1987
  for (;;) {
1945
1988
  const t = readToken(input, pos, tok);
1946
1989
  if (t === undefined) {
1947
- this._next = fill(tok, TT_EOF, input.length, input.length);
1990
+ fill(tok, TT_EOF, input.length, input.length);
1948
1991
  break;
1949
1992
  }
1950
1993
  if (t.type === TT_COMMENT) {
@@ -1955,11 +1998,11 @@ class TokenStream {
1955
1998
  pos = t.end;
1956
1999
  continue;
1957
2000
  }
1958
- this._next = t;
1959
2001
  break;
1960
2002
  }
2003
+ this._hasNext = true;
1961
2004
  }
1962
- return /** @type {MutableToken} */ (this._next);
2005
+ return this._tok;
1963
2006
  }
1964
2007
 
1965
2008
  /**
@@ -1972,7 +2015,7 @@ class TokenStream {
1972
2015
  const t = this.next();
1973
2016
  if (t.type !== TT_EOF) {
1974
2017
  this._pos = t.end;
1975
- this._next = undefined;
2018
+ this._hasNext = false;
1976
2019
  }
1977
2020
  return t;
1978
2021
  }
@@ -1986,7 +2029,7 @@ class TokenStream {
1986
2029
  const t = this.next();
1987
2030
  if (t.type !== TT_EOF) {
1988
2031
  this._pos = t.end;
1989
- this._next = undefined;
2032
+ this._hasNext = false;
1990
2033
  }
1991
2034
  }
1992
2035
 
@@ -2006,7 +2049,7 @@ class TokenStream {
2006
2049
  */
2007
2050
  restoreMark() {
2008
2051
  this._pos = /** @type {number} */ (this._marks.pop());
2009
- this._next = undefined;
2052
+ this._hasNext = false;
2010
2053
  }
2011
2054
 
2012
2055
  /**
@@ -2305,6 +2348,11 @@ const consumeAnAtRule = (ts, nested = false) => {
2305
2348
  // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2306
2349
  // defaults (no block: the `;` / EOF / nested-`}` at-rule forms).
2307
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;
2355
+
2308
2356
  // Process input
2309
2357
  for (;;) {
2310
2358
  const t = ts.next();
@@ -2325,7 +2373,8 @@ const consumeAnAtRule = (ts, nested = false) => {
2325
2373
  _setEnd(rule, t.start);
2326
2374
  return rule;
2327
2375
  }
2328
- prelude.push(consumeATokenAsNode(ts));
2376
+ const node = consumeATokenAsNode(ts);
2377
+ if (!skip) prelude.push(node);
2329
2378
  continue;
2330
2379
  }
2331
2380
  // <{-token>
@@ -2340,7 +2389,12 @@ const consumeAnAtRule = (ts, nested = false) => {
2340
2389
 
2341
2390
  // anything else
2342
2391
  // Consume a component value from input and append the returned value to rule’s prelude.
2343
- prelude.push(consumeAComponentValue(ts, t));
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
+ }
2344
2398
  }
2345
2399
  };
2346
2400
 
@@ -2376,6 +2430,13 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2376
2430
  // declarations / childRules / blockStart (-1) / blockEnd (-1) keep their
2377
2431
  // defaults (no block until a `{` is reached).
2378
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));
2439
+
2379
2440
  // Process input
2380
2441
  for (;;) {
2381
2442
  const t = ts.next();
@@ -2389,7 +2450,13 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2389
2450
  // This is a parse error. If nested is true, return nothing. Otherwise, consume a token and append the result to rule’s prelude.
2390
2451
  else if (t.type === TT_RIGHT_CURLY_BRACKET) {
2391
2452
  if (nested) return undefined;
2392
- 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
+ }
2393
2460
  continue;
2394
2461
  }
2395
2462
  // <{-token>
@@ -2399,23 +2466,25 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2399
2466
  // (This disambiguates custom-property declarations from nested qualified rules — `--foo: { … }` at top level of a block is a declaration, not a rule.)
2400
2467
  // Otherwise, consume a block from input, and let child rules be the result.
2401
2468
  else if (t.type === TT_LEFT_CURLY_BRACKET) {
2402
- let firstIdx = 0;
2403
- /* istanbul ignore next -- @preserve: leading whitespace is discarded before the rule, so the prelude never starts with it */
2404
- while (
2405
- firstIdx < prelude.length &&
2406
- _nType(prelude[firstIdx]) === T_WHITESPACE
2407
- ) {
2408
- firstIdx++;
2409
- }
2410
- let secondIdx = firstIdx + 1;
2411
- while (
2412
- secondIdx < prelude.length &&
2413
- _nType(prelude[secondIdx]) === T_WHITESPACE
2414
- ) {
2415
- secondIdx++;
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];
2416
2487
  }
2417
- const first = prelude[firstIdx];
2418
- const second = prelude[secondIdx];
2419
2488
  if (
2420
2489
  first &&
2421
2490
  _nType(first) === T_IDENT &&
@@ -2442,7 +2511,21 @@ const consumeAQualifiedRule = (ts, stopToken, nested = false) => {
2442
2511
 
2443
2512
  // anything else
2444
2513
  // Consume a component value from input and append the result to rule’s prelude.
2445
- prelude.push(consumeAComponentValue(ts, t));
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
+ }
2446
2529
  }
2447
2530
  };
2448
2531
 
@@ -2466,7 +2549,8 @@ const consumeABlock = (ts) => {
2466
2549
  };
2467
2550
 
2468
2551
  /**
2469
- * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>` (the prerequisite for consume-a-declaration steps 1 + 3)? Used by `consumeABlocksContents` to skip a declaration attempt that would otherwise call consume-the-remnants-of-a-bad-declaration and be undone by `restoreMark`. A webpack fast-path, not a spec algorithm — so the implementation is free to peek cheaply. It scans raw code points after the cached ident for the next significant one (skipping whitespace + comments) rather than tokenizing them, and never advances the stream: the ident stays cached in `_next` for `consumeADeclaration` to reuse instead of re-tokenizing the property name. Comments skipped here fire `onComment` later, when the chosen consume algorithm tokenizes past them (once, in source order, as before).
2552
+ * 2-token lookahead: is the next non-whitespace pair `<ident> <colon>`?
2553
+ * Peeks raw code points without advancing; comments still fire `onComment` later.
2470
2554
  * @param {TokenStream} ts token stream
2471
2555
  * @returns {boolean} true if consume-a-declaration's step 1 + step 3 would both succeed on the current input
2472
2556
  */
@@ -2710,12 +2794,28 @@ const consumeAListOfComponentValues = (ts, stopToken, nested = false) => {
2710
2794
  // Otherwise, this is a parse error. Consume a token from input and append the result to values.
2711
2795
  if (t.type === TT_RIGHT_CURLY_BRACKET) {
2712
2796
  if (nested) return values;
2713
- 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);
2714
2801
  continue;
2715
2802
  }
2716
2803
  // anything else
2717
2804
  // Consume a component value from input, and append the result to values.
2718
- values.push(consumeAComponentValue(ts, t));
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);
2719
2819
  }
2720
2820
  };
2721
2821
 
@@ -2821,7 +2921,19 @@ const consumeAFunction = (ts) => {
2821
2921
 
2822
2922
  // anything else
2823
2923
  // Consume a component value from input and append the result to function’s value.
2824
- val.push(consumeAComponentValue(ts, t));
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);
2825
2937
  }
2826
2938
  };
2827
2939
 
@@ -2999,11 +3111,10 @@ const unescapeIdentifier = makeCacheable(_unescapeIdentifier);
2999
3111
  // CSS-typed views over the generic visitor machinery (`util/SourceProcessor`),
3000
3112
  // re-exported so consumers keep importing them from this module.
3001
3113
  /**
3002
- * @typedef {import("../util/SourceProcessor").VisitorContext} VisitorContext
3003
- * @typedef {import("../util/SourceProcessor").VisitorFn<Node>} VisitorFn
3004
- * @typedef {import("../util/SourceProcessor").VisitorBucket<Node>} VisitorBucket
3005
- * @typedef {import("../util/SourceProcessor").VisitorMap<Node>} VisitorMap
3006
- * @typedef {import("../util/SourceProcessor").CompiledVisitorMap<Node>} CompiledVisitorMap
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
3007
3118
  */
3008
3119
 
3009
3120
  /**
@@ -3027,9 +3138,17 @@ const TOP_LEVEL_CONSUMERS = {
3027
3138
  /**
3028
3139
  * @typedef {object} CssProcessOptions
3029
3140
  * @property {LocConverter=} locConverter shared loc converter (default a fresh one over the input)
3030
- * @property {((input: string, start: number, end: number) => number)=} comment comment-token callback
3031
3141
  * @property {boolean=} recurseBlocks walk into block bodies' nested rules (default true)
3032
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)
3033
3152
  */
3034
3153
 
3035
3154
  /**
@@ -3043,18 +3162,36 @@ const TOP_LEVEL_CONSUMERS = {
3043
3162
  * @param {CssProcessOptions} options process options
3044
3163
  */
3045
3164
  const grammar = (input, visitors, options) => {
3046
- const { comment } = options;
3047
3165
  const locConverter = options.locConverter || new LocConverter(input);
3048
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;
3049
3172
  const recurseBlocks = options.recurseBlocks !== false;
3050
3173
 
3051
- // Babel's `path.skip()`, children-only. Reset per `enter` dispatch.
3052
- let skipFlag = false;
3053
- const visitorCtx = {
3054
- skipChildren() {
3055
- skipFlag = true;
3056
- }
3057
- };
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
+ );
3058
3195
 
3059
3196
  /**
3060
3197
  * Walk a component-value subtree; children are already materialized. Fetches
@@ -3068,19 +3205,24 @@ const grammar = (input, visitors, options) => {
3068
3205
  const b = visitors[ty];
3069
3206
  let skip = false;
3070
3207
  if (b !== undefined && b.enter.length !== 0) {
3071
- skipFlag = false;
3208
+ _walkSkip = false;
3209
+ _curNode = node;
3210
+ _curParent = parent;
3072
3211
  const e = b.enter;
3073
- for (let i = 0; i < e.length; i++) e[i](node, parent, visitorCtx);
3074
- skip = skipFlag;
3075
- skipFlag = false;
3212
+ for (let i = 0; i < e.length; i++) e[i](A);
3213
+ skip = _walkSkip;
3214
+ _walkSkip = false;
3076
3215
  }
3077
3216
  if (!skip && (ty === T_FUNCTION || ty === T_SIMPLE_BLOCK)) {
3078
3217
  const v = /** @type {Node[]} */ (_soaL0[_ix(node)]);
3079
3218
  for (let i = 0; i < v.length; i++) walkValue(v[i], node);
3080
3219
  }
3081
3220
  if (b !== undefined) {
3221
+ // Rebind: descending into children moved the path.
3222
+ _curNode = node;
3223
+ _curParent = parent;
3082
3224
  const x = b.exit;
3083
- 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);
3084
3226
  }
3085
3227
  };
3086
3228
 
@@ -3096,11 +3238,13 @@ const grammar = (input, visitors, options) => {
3096
3238
  const b = visitors[ty];
3097
3239
  let skip = false;
3098
3240
  if (b !== undefined && b.enter.length !== 0) {
3099
- skipFlag = false;
3241
+ _walkSkip = false;
3242
+ _curNode = node;
3243
+ _curParent = parent;
3100
3244
  const e = b.enter;
3101
- for (let i = 0; i < e.length; i++) e[i](node, parent, visitorCtx);
3102
- skip = skipFlag;
3103
- skipFlag = false;
3245
+ for (let i = 0; i < e.length; i++) e[i](A);
3246
+ skip = _walkSkip;
3247
+ _walkSkip = false;
3104
3248
  }
3105
3249
  if (!skip) {
3106
3250
  if (ty === T_AT_RULE || ty === T_QUALIFIED_RULE) {
@@ -3121,22 +3265,35 @@ const grammar = (input, visitors, options) => {
3121
3265
  }
3122
3266
  }
3123
3267
  if (b !== undefined) {
3268
+ // Rebind: descending into children moved the path.
3269
+ _curNode = node;
3270
+ _curParent = parent;
3124
3271
  const x = b.exit;
3125
- 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);
3126
3273
  }
3127
3274
  };
3128
3275
 
3129
3276
  // Stream each top-level node (selected by `as`) to the walker the moment it's
3130
3277
  // consumed, rather than collecting them first — so the whole AST is never
3131
3278
  // held at once; peak heap is ~one top-level node's subtree.
3132
- const ts = new TokenStream(input, 0, locConverter, comment);
3279
+ const ts = new TokenStream(input, 0, locConverter, onComment);
3133
3280
  const consume =
3134
3281
  TOP_LEVEL_CONSUMERS[options.as || "stylesheet"] ||
3135
3282
  consumeAStylesheetsContents;
3136
- consume(ts, (node) => {
3137
- walkRule(node, null);
3138
- _soaN = 0;
3139
- });
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
+ }
3140
3297
  };
3141
3298
 
3142
3299
  /**
@@ -3144,17 +3301,55 @@ const grammar = (input, visitors, options) => {
3144
3301
  * `grammar`. Babel-style usage:
3145
3302
  *
3146
3303
  * ```
3147
- * processor.use({ [NodeType.AtRule]: (at) => {}, [NodeType.Declaration]: { enter, exit } });
3148
- * processor.process(source);
3304
+ * new SourceProcessor({ skip }).use({ [NodeType.AtRule]: (path) => {} }).process(source);
3149
3305
  * ```
3150
- * @extends {GenericSourceProcessor<Node, CssProcessOptions>}
3306
+ * @extends {GenericSourceProcessor<CssPath, CssProcessOptions>}
3151
3307
  */
3152
3308
  class SourceProcessor extends GenericSourceProcessor {
3153
- constructor() {
3154
- super(grammar);
3309
+ /**
3310
+ * @param {CssProcessOptions=} options default process options (`skip`, `as`, …) for every `process` call
3311
+ */
3312
+ constructor(options) {
3313
+ super(grammar, options);
3155
3314
  }
3156
3315
  }
3157
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
+
3158
3353
  // AST field-access seam. Every AST-node field read by `CssParser` goes through
3159
3354
  // one of these accessors so the node representation can change underneath the
3160
3355
  // consumer without touching it. Today they are backed by the `Node` / `Token` /
@@ -3163,19 +3358,58 @@ class SourceProcessor extends GenericSourceProcessor {
3163
3358
  // consumer edit. `value` is the leaf-token string; container child lists are
3164
3359
  // `children` / `prelude` / `declarations` / `childRules`.
3165
3360
  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) => {
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) {
3174
3405
  const i = _ix(n);
3175
3406
  return [_soaSt[i], _soaEn[i]];
3176
3407
  },
3177
- /** @type {(n: Node) => { start: { line: number, column: number }, end: { line: number, column: number } }} */
3178
- loc: (n) => {
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) {
3179
3413
  const i = _ix(n);
3180
3414
  const lc = _soaLC;
3181
3415
  const s = lc.get(_soaSt[i]);
@@ -3187,23 +3421,37 @@ const A = {
3187
3421
  end: { line: e.line, column: e.column }
3188
3422
  };
3189
3423
  },
3190
- /** @type {(n: Node) => string} */
3191
- source: (n) => {
3424
+ /**
3425
+ * @param {Node=} n node
3426
+ * @returns {string} raw source slice
3427
+ */
3428
+ source(n = _curNode) {
3192
3429
  const i = _ix(n);
3193
3430
  return _soaInput.slice(_soaSt[i], _soaEn[i]);
3194
3431
  },
3195
- /** @type {(n: Node) => string} */
3196
- value: (n) => _soaValueOf(_ix(n)),
3197
- /** @type {(n: Node) => string} */
3198
- unescaped: (n) => {
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) {
3199
3444
  const i = _ix(n);
3200
3445
  const v = _soaValueOf(i);
3201
3446
  return _soaTy[i] === T_STRING
3202
3447
  ? unescapeIdentifier(v.slice(1, -1))
3203
3448
  : unescapeIdentifier(v);
3204
3449
  },
3205
- /** @type {(n: Node) => string} */
3206
- typeFlag: (n) => {
3450
+ /**
3451
+ * @param {Node=} n node
3452
+ * @returns {string} hash / numeric type flag
3453
+ */
3454
+ typeFlag(n = _curNode) {
3207
3455
  const i = _ix(n);
3208
3456
  if (_soaTy[i] === T_HASH) {
3209
3457
  const input = _soaInput;
@@ -3223,48 +3471,122 @@ const A = {
3223
3471
  _soaTy[i] === T_DIMENSION ? v.slice(0, _consumeANumber(v, 0)) : v
3224
3472
  );
3225
3473
  },
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) => {
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) {
3232
3493
  const i = _ix(n);
3233
3494
  return _soaTy[i] === T_AT_RULE
3234
3495
  ? _soaInput.slice(_soaSt[i] + 1, _soaA0[i])
3235
3496
  : _soaInput.slice(_soaSt[i], _soaA0[i]);
3236
3497
  },
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)]]),
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
+ },
3260
3575
  // 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) => {
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) {
3264
3583
  _soaEn[_ix(n)] = v;
3265
3584
  },
3266
- /** @type {(n: Node, v: number) => void} */
3267
- setBlockEnd: (n, v) => {
3585
+ /**
3586
+ * @param {Node} n node
3587
+ * @param {number} v new block end offset
3588
+ */
3589
+ setBlockEnd(n, v) {
3268
3590
  _soaA2[_ix(n)] = v;
3269
3591
  }
3270
3592
  };
@@ -3307,6 +3629,7 @@ module.exports.TT_URL = TT_URL;
3307
3629
  module.exports.TT_WHITESPACE = TT_WHITESPACE;
3308
3630
  module.exports.Token = Token;
3309
3631
  module.exports.TokenStream = TokenStream;
3632
+ module.exports.buildSkipSet = buildSkipSet;
3310
3633
  module.exports.equalsLowerCase = equalsLowerCase;
3311
3634
  module.exports.escapeIdentifier = escapeIdentifier;
3312
3635
  module.exports.parseABlocksContents = parseABlocksContents;