webpack 5.108.2 → 5.108.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -29,8 +29,10 @@ const {
29
29
  } = require("../util/magicComment");
30
30
  const topologicalSort = require("../util/topologicalSort");
31
31
  const {
32
+ A,
32
33
  NodeType,
33
34
  SourceProcessor,
35
+ buildSkipSet,
34
36
  equalsLowerCase,
35
37
  unescapeIdentifier
36
38
  } = require("./syntax");
@@ -65,6 +67,7 @@ const {
65
67
  /** @typedef {{ localName: string, value: string }} ValueAtRuleValue */
66
68
 
67
69
  const CC_COLON = ":".charCodeAt(0);
70
+ const CC_HYPHEN_MINUS = "-".charCodeAt(0);
68
71
  const CC_SEMICOLON = ";".charCodeAt(0);
69
72
  const CC_TAB = "\t".charCodeAt(0);
70
73
  const CC_SPACE = " ".charCodeAt(0);
@@ -91,8 +94,32 @@ const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
91
94
  const MAGIC_COMMENT_FAST_PATH =
92
95
  /^\s*(webpack[A-Z][A-Za-z]+)\s*:\s*(true|false|null|-?\d+(?:\.\d+)?)\s*$/;
93
96
  const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
97
+ const VENDOR_PREFIX = /^-\w+-/;
94
98
  const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
95
99
  const IS_MODULES = /\.modules?\.[^.]+$/i;
100
+
101
+ // Skip options for a non-CSS-Modules parse: drop the selector prelude (never
102
+ // walked without modules) plus value / function-arg leaves nothing reads (the
103
+ // `Ident` visitor no-ops, the `Declaration` visitor returns early, no ICSS).
104
+ // `url` / functions / strings / blocks / commas are kept — they carry url()
105
+ // rewrites and image-set fences. At-rule preludes are kept (`@media` / `@import`
106
+ // are read). CSS-Modules parses skip nothing: selectors are walked and ICSS
107
+ // `:export { k: v }` captures each value's byte range from its first / last node.
108
+ const SKIP_NON_MODULES = {
109
+ types: buildSkipSet([
110
+ NodeType.Number,
111
+ NodeType.Dimension,
112
+ NodeType.Percentage,
113
+ NodeType.Ident,
114
+ NodeType.Hash,
115
+ NodeType.Colon,
116
+ NodeType.Delim,
117
+ // Nothing reads value/arg whitespace either — consumers use
118
+ // `nextNonWhitespace` / type checks that tolerate its absence.
119
+ NodeType.Whitespace
120
+ ]),
121
+ selectorPrelude: true
122
+ };
96
123
  const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;
97
124
  // `@value` recognizers (postcss-modules-values shape): the import form `<names> from <source>`, and the `<importName> as <localName>` alias inside it.
98
125
  const VALUE_IMPORT_FORM = /from(\/\*|\s)(?:[\s\S]+)$/i;
@@ -182,6 +209,109 @@ const normalizeUrl = (str, isString) => {
182
209
  const isDashedIdentifier = (identifier) =>
183
210
  identifier.startsWith("--") && identifier.length >= 3;
184
211
 
212
+ /**
213
+ * `s.toLowerCase()` that returns `s` itself (no allocation) when it can't
214
+ * change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is
215
+ * left to the real `toLowerCase`).
216
+ * @param {string} s string
217
+ * @returns {string} lowercased string
218
+ */
219
+ const toLowerCaseIfNeeded = (s) => {
220
+ for (let i = 0; i < s.length; i++) {
221
+ const c = s.charCodeAt(i);
222
+ if ((c >= 65 && c <= 90) || c > 127) return s.toLowerCase();
223
+ }
224
+ return s;
225
+ };
226
+
227
+ /**
228
+ * Case-sensitive equality of a source range against a literal — no slice.
229
+ * @param {string} input source
230
+ * @param {number} start range start
231
+ * @param {number} end range end (exclusive)
232
+ * @param {string} lit literal to match
233
+ * @returns {boolean} true when the range equals `lit`
234
+ */
235
+ const rangeEquals = (input, start, end, lit) =>
236
+ end - start === lit.length && input.startsWith(lit, start);
237
+
238
+ /**
239
+ * ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice.
240
+ * @param {string} input source
241
+ * @param {number} start range start
242
+ * @param {number} end range end (exclusive)
243
+ * @param {string} lit lowercase ASCII literal to match
244
+ * @returns {boolean} true when the range equals `lit` ignoring ASCII case
245
+ */
246
+ const rangeEqualsLowerCase = (input, start, end, lit) => {
247
+ if (end - start !== lit.length) return false;
248
+ for (let i = 0; i < lit.length; i++) {
249
+ let c = input.charCodeAt(start + i);
250
+ if (c >= 65 && c <= 90) c |= 0x20;
251
+ if (c !== lit.charCodeAt(i)) return false;
252
+ }
253
+ return true;
254
+ };
255
+
256
+ /**
257
+ * Range-keyed index over a known-properties table: ASCII-case-folded 31-hash
258
+ * of the name bytes → canonical key(s). Lets the Declaration visitor answer
259
+ * "is this a known property" (and get the canonical lowercase name) without
260
+ * slicing the property name out of the source per declaration.
261
+ * @type {WeakMap<Map<string, Record<string, number>>, Map<number, string | string[]>>}
262
+ */
263
+ const KNOWN_PROPERTY_INDEX_CACHE = new WeakMap();
264
+
265
+ /**
266
+ * Gets (or builds) the hash index for a known-properties table.
267
+ * @param {Map<string, Record<string, number>>} knownProperties known properties table
268
+ * @returns {Map<number, string | string[]>} hash → canonical name(s)
269
+ */
270
+ const getKnownPropertyIndex = (knownProperties) => {
271
+ let index = KNOWN_PROPERTY_INDEX_CACHE.get(knownProperties);
272
+ if (index === undefined) {
273
+ index = new Map();
274
+ for (const name of knownProperties.keys()) {
275
+ let h = name.length;
276
+ for (let i = 0; i < name.length; i++) {
277
+ h = ((h << 5) - h + name.charCodeAt(i)) | 0;
278
+ }
279
+ const hit = index.get(h);
280
+ if (hit === undefined) index.set(h, name);
281
+ else if (typeof hit === "string") index.set(h, [hit, name]);
282
+ else hit.push(name);
283
+ }
284
+ KNOWN_PROPERTY_INDEX_CACHE.set(knownProperties, index);
285
+ }
286
+ return index;
287
+ };
288
+
289
+ /**
290
+ * Canonical known-property name for a source range (ASCII case-insensitive), without slicing.
291
+ * @param {Map<number, string | string[]>} index hash index from `getKnownPropertyIndex`
292
+ * @param {string} input source
293
+ * @param {number} start name start
294
+ * @param {number} end name end (exclusive)
295
+ * @returns {string | undefined} canonical lowercase name, or undefined when unknown
296
+ */
297
+ const knownPropertyForRange = (index, input, start, end) => {
298
+ let h = end - start;
299
+ for (let i = start; i < end; i++) {
300
+ let c = input.charCodeAt(i);
301
+ if (c >= 65 && c <= 90) c |= 0x20;
302
+ h = ((h << 5) - h + c) | 0;
303
+ }
304
+ const hit = index.get(h);
305
+ if (hit === undefined) return undefined;
306
+ if (typeof hit === "string") {
307
+ return rangeEqualsLowerCase(input, start, end, hit) ? hit : undefined;
308
+ }
309
+ for (let i = 0; i < hit.length; i++) {
310
+ if (rangeEqualsLowerCase(input, start, end, hit[i])) return hit[i];
311
+ }
312
+ return undefined;
313
+ };
314
+
185
315
  /**
186
316
  * `binarySearchBounds` comparator for `getComments` — hoisted so the lookup
187
317
  * doesn't allocate a fresh closure per call.
@@ -577,26 +707,27 @@ const isPureBodyAtRule = (name) =>
577
707
 
578
708
  /**
579
709
  * Scan a rule body once: does it hold a direct declaration counted against the enclosing rule (a declaration, or one in a transparent conditional-group at-rule like `@media`/`@supports`/…) and does it hold a nested block (qualified rule or any block-bearing at-rule)?
580
- * @param {{ declarations: Declaration[] | null, childRules: Rule[] | null }} body rule body
710
+ * @param {Declaration[] | null} declarations rule-body declarations
711
+ * @param {Rule[] | null} childRules rule-body child rules
581
712
  * @returns {{ hasDirectDecl: boolean, hasNestedBlock: boolean }} scan result
582
713
  */
583
- const scanRuleBody = (body) => {
584
- let hasDirectDecl = Boolean(
585
- body.declarations && body.declarations.length > 0
586
- );
714
+ const scanRuleBody = (declarations, childRules) => {
715
+ let hasDirectDecl = Boolean(declarations && declarations.length > 0);
587
716
  let hasNestedBlock = false;
588
- if (body.childRules) {
589
- for (const child of body.childRules) {
590
- if (child.type === NodeType.QualifiedRule) {
717
+ if (childRules) {
718
+ for (const child of childRules) {
719
+ const t = A.type(child);
720
+ if (t === NodeType.QualifiedRule) {
591
721
  hasNestedBlock = true;
592
- } else if (child.type === NodeType.AtRule) {
593
- const at = /** @type {AtRule} */ (child);
594
- if (!at.declarations && !at.childRules) continue;
722
+ } else if (t === NodeType.AtRule) {
723
+ const atDecls = A.declarations(child);
724
+ const atChildRules = A.childRules(child);
725
+ if (!atDecls && !atChildRules) continue;
595
726
  hasNestedBlock = true;
596
727
  if (
597
728
  !hasDirectDecl &&
598
- !isPureBodyAtRule(`@${at.name.toLowerCase()}`) &&
599
- scanRuleBody(at).hasDirectDecl
729
+ !isPureBodyAtRule(`@${toLowerCaseIfNeeded(A.name(child))}`) &&
730
+ scanRuleBody(atDecls, atChildRules).hasDirectDecl
600
731
  ) {
601
732
  hasDirectDecl = true;
602
733
  }
@@ -686,7 +817,7 @@ const parseValueAtRuleParams = (str) => {
686
817
  */
687
818
  const nextNonWhitespace = (nodes, from) => {
688
819
  let i = from;
689
- while (i < nodes.length && nodes[i].type === NodeType.Whitespace) i++;
820
+ while (i < nodes.length && A.type(nodes[i]) === NodeType.Whitespace) i++;
690
821
  return i;
691
822
  };
692
823
 
@@ -706,21 +837,22 @@ const parseImportPrelude = (prelude) => {
706
837
  let supportsNode;
707
838
 
708
839
  for (const cv of prelude) {
709
- if (cv.type === NodeType.Whitespace) continue;
840
+ const t = A.type(cv);
841
+ if (t === NodeType.Whitespace) continue;
710
842
 
711
843
  if (!urlNode) {
712
- if (cv.type === NodeType.Url || cv.type === NodeType.String) {
844
+ if (t === NodeType.Url || t === NodeType.String) {
713
845
  urlNode = cv;
714
846
  continue;
715
847
  }
716
848
  if (
717
- cv.type === NodeType.Function &&
718
- equalsLowerCase(/** @type {FunctionNode} */ (cv).unescapedName, "url")
849
+ t === NodeType.Function &&
850
+ equalsLowerCase(A.unescapedName(cv), "url")
719
851
  ) {
720
852
  urlNode = cv;
721
853
  continue;
722
854
  }
723
- if (cv.type === NodeType.Ident) {
855
+ if (t === NodeType.Ident) {
724
856
  // CSS Modules: bare ident is a `@value` reference.
725
857
  urlNode = cv;
726
858
  continue;
@@ -729,14 +861,14 @@ const parseImportPrelude = (prelude) => {
729
861
  }
730
862
 
731
863
  if (!layerNode && !supportsNode) {
732
- if (cv.type === NodeType.Ident) {
733
- if (equalsLowerCase(/** @type {Token} */ (cv).unescaped, "layer")) {
864
+ if (t === NodeType.Ident) {
865
+ if (equalsLowerCase(A.unescaped(cv), "layer")) {
734
866
  layerNode = cv;
735
867
  continue;
736
868
  }
737
869
  } else if (
738
- cv.type === NodeType.Function &&
739
- equalsLowerCase(/** @type {FunctionNode} */ (cv).unescapedName, "layer")
870
+ t === NodeType.Function &&
871
+ equalsLowerCase(A.unescapedName(cv), "layer")
740
872
  ) {
741
873
  layerNode = cv;
742
874
  continue;
@@ -745,11 +877,8 @@ const parseImportPrelude = (prelude) => {
745
877
 
746
878
  if (
747
879
  !supportsNode &&
748
- cv.type === NodeType.Function &&
749
- equalsLowerCase(
750
- /** @type {FunctionNode} */ (cv).unescapedName,
751
- "supports"
752
- )
880
+ t === NodeType.Function &&
881
+ equalsLowerCase(A.unescapedName(cv), "supports")
753
882
  ) {
754
883
  supportsNode = /** @type {FunctionNode} */ (cv);
755
884
  continue;
@@ -772,31 +901,28 @@ const parseImportPrelude = (prelude) => {
772
901
  const parseIcssImportRequest = (second, rule, source) => {
773
902
  /** @type {AstNode[] | undefined} */
774
903
  let args;
775
- if (second.type === NodeType.Function) {
776
- args = /** @type {FunctionNode} */ (second).value;
904
+ if (A.type(second) === NodeType.Function) {
905
+ args = A.children(second);
777
906
  } else {
778
- for (const p of rule.prelude) {
779
- if (
780
- p.type === NodeType.SimpleBlock &&
781
- /** @type {SimpleBlock} */ (p).token === "("
782
- ) {
783
- args = /** @type {SimpleBlock} */ (p).value;
907
+ for (const p of A.prelude(rule)) {
908
+ if (A.type(p) === NodeType.SimpleBlock && A.blockToken(p) === "(") {
909
+ args = A.children(p);
784
910
  break;
785
911
  }
786
912
  }
787
913
  }
788
914
  // The first non-whitespace value inside `(...)` must be a string.
789
915
  const innerStrToken =
790
- args && args.find((v) => v.type !== NodeType.Whitespace);
791
- if (!innerStrToken || innerStrToken.type !== NodeType.String) {
916
+ args && args.find((v) => A.type(v) !== NodeType.Whitespace);
917
+ if (!innerStrToken || A.type(innerStrToken) !== NodeType.String) {
792
918
  const errorPos =
793
- second.type === NodeType.Function
794
- ? /** @type {FunctionNode} */ (second).nameEnd + 1
795
- : second.end;
919
+ A.type(second) === NodeType.Function
920
+ ? A.nameEnd(second) + 1
921
+ : A.end(second);
796
922
  return { errorPos };
797
923
  }
798
924
  return {
799
- request: source.slice(innerStrToken.start + 1, innerStrToken.end - 1)
925
+ request: source.slice(A.start(innerStrToken) + 1, A.end(innerStrToken) - 1)
800
926
  };
801
927
  };
802
928
 
@@ -918,6 +1044,8 @@ class CssParser extends Parser {
918
1044
  exportMode = CssIcssExportDependency.EXPORT_MODE.REPLACE,
919
1045
  exportType = CssIcssExportDependency.EXPORT_TYPE.NORMAL
920
1046
  ) => {
1047
+ // Flat location numbers — the nested loc objects were the parser's
1048
+ // hottest allocation (3 objects per exported name).
921
1049
  cssExportEntries.push({
922
1050
  name,
923
1051
  value,
@@ -925,7 +1053,10 @@ class CssParser extends Parser {
925
1053
  interpolate,
926
1054
  exportMode,
927
1055
  exportType,
928
- loc: { start: { line: sl, column: sc }, end: { line: el, column: ec } }
1056
+ locStartLine: sl,
1057
+ locStartColumn: sc,
1058
+ locEndLine: el,
1059
+ locEndColumn: ec
929
1060
  });
930
1061
  };
931
1062
 
@@ -942,12 +1073,29 @@ class CssParser extends Parser {
942
1073
 
943
1074
  const isModules = mode === "global" || mode === "local";
944
1075
 
1076
+ /** @type {Map<string, boolean>} */
1077
+ const selfReferenceCache = new Map();
1078
+
945
1079
  /**
946
1080
  * Whether a relative `from "<request>"` resolves back to the current module (matching query/fragment too).
1081
+ * Memoized per parse — `composes … from "./x.css"` repeats the same request many times per file.
947
1082
  * @param {string} request request string from `from "<request>"`
948
1083
  * @returns {boolean} true if request resolves to the current module
949
1084
  */
950
1085
  const isSelfReferenceRequest = (request) => {
1086
+ const cached = selfReferenceCache.get(request);
1087
+ if (cached !== undefined) return cached;
1088
+ const result = isSelfReferenceRequestUncached(request);
1089
+ selfReferenceCache.set(request, result);
1090
+ return result;
1091
+ };
1092
+
1093
+ /**
1094
+ * Uncached `isSelfReferenceRequest`.
1095
+ * @param {string} request request string from `from "<request>"`
1096
+ * @returns {boolean} true if request resolves to the current module
1097
+ */
1098
+ const isSelfReferenceRequestUncached = (request) => {
951
1099
  if (!RELATIVE_REQUEST.test(request)) return false;
952
1100
  if (!module.context) return false;
953
1101
  const parsedRequest = parseResource(request);
@@ -971,6 +1119,7 @@ class CssParser extends Parser {
971
1119
  customIdents: this.options.customIdents,
972
1120
  grid: this.options.grid
973
1121
  });
1122
+ const knownPropertyIndex = getKnownPropertyIndex(knownProperties);
974
1123
 
975
1124
  /** @type {CssModuleBuildMeta} */
976
1125
  (module.buildMeta).isCssModule = isModules;
@@ -1054,7 +1203,7 @@ class CssParser extends Parser {
1054
1203
  /**
1055
1204
  * Unescape a CSS identifier from a source byte range — for value spans not
1056
1205
  * backed by a single token (string contents, `--` dashed-ident bodies,
1057
- * composed names). Token-backed names use `node.unescaped` instead.
1206
+ * composed names). Token-backed names use `A.unescaped(node)` instead.
1058
1207
  * @param {number} start start offset
1059
1208
  * @param {number} end end offset
1060
1209
  * @returns {string} the unescaped identifier
@@ -1170,10 +1319,10 @@ class CssParser extends Parser {
1170
1319
  const hasBody = Boolean(declarations || childRules);
1171
1320
  let leaf = treatAsLeaf || !hasBody;
1172
1321
  if (!leaf && hasBody) {
1173
- const { hasDirectDecl, hasNestedBlock } = scanRuleBody({
1322
+ const { hasDirectDecl, hasNestedBlock } = scanRuleBody(
1174
1323
  declarations,
1175
1324
  childRules
1176
- });
1325
+ );
1177
1326
  leaf = hasDirectDecl || !hasNestedBlock;
1178
1327
  }
1179
1328
  if (leaf) this.report(preludeStart, preludeEnd);
@@ -1244,18 +1393,17 @@ class CssParser extends Parser {
1244
1393
  modeData ? modeData === "local" : mode === "local";
1245
1394
 
1246
1395
  /**
1247
- * Comment callback: push every comment-token (in source order) onto the local `comments`, read back by `advanceCommentCursor` (pure-mode flags) and `parseCommentOptions` (magic comments).
1248
- * @param {string} input input
1249
- * @param {number} start start
1250
- * @param {number} end end
1251
- * @returns {number} end
1396
+ * Comment visitor (`NodeType.Comment`): push every comment (in source order) onto the local `comments`, read back by `advanceCommentCursor` (pure-mode flags) and `parseCommentOptions` (magic comments).
1397
+ * @param {import("./syntax").CssPath} path walk path at the comment node
1252
1398
  */
1253
- const comment = (input, start, end) => {
1399
+ const commentVisitor = (path) => {
1400
+ const node = path.node;
1401
+ const start = A.start(node);
1402
+ const end = A.end(node);
1254
1403
  comments.push({
1255
- value: input.slice(start + 2, end - 2),
1404
+ value: source.slice(start + 2, end - 2),
1256
1405
  range: [start, end]
1257
1406
  });
1258
- return end;
1259
1407
  };
1260
1408
 
1261
1409
  /**
@@ -1457,21 +1605,22 @@ class CssParser extends Parser {
1457
1605
  };
1458
1606
 
1459
1607
  // Body `{ name: value; … }` is parsed eagerly (§5.4.4) — emit a dep per declaration.
1460
- if (!rule.declarations || rule.blockStart === -1) return second.end;
1461
- for (const decl of rule.declarations) {
1462
- const vals = decl.value;
1608
+ const ruleDecls = A.declarations(rule);
1609
+ if (!ruleDecls || A.blockStart(rule) === -1) return A.end(second);
1610
+ for (const decl of ruleDecls) {
1611
+ const vals = A.children(decl);
1463
1612
  if (vals.length === 0) continue;
1464
- const rawStart = vals[0].start;
1465
- const rawEnd = vals[vals.length - 1].end;
1613
+ const rawStart = A.start(vals[0]);
1614
+ const rawEnd = A.end(vals[vals.length - 1]);
1466
1615
  createDep(
1467
- source.slice(decl.nameStart, decl.nameEnd),
1616
+ source.slice(A.nameStart(decl), A.nameEnd(decl)),
1468
1617
  source.slice(rawStart, rawEnd),
1469
- decl.nameEnd,
1618
+ A.nameEnd(decl),
1470
1619
  rawEnd
1471
1620
  );
1472
1621
  }
1473
1622
 
1474
- return skipWhiteLine(source, rule.blockEnd);
1623
+ return skipWhiteLine(source, A.blockEnd(rule));
1475
1624
  };
1476
1625
 
1477
1626
  /**
@@ -1506,20 +1655,20 @@ class CssParser extends Parser {
1506
1655
  */
1507
1656
  const processLocalOrGlobalFunction = (fn, type) => {
1508
1657
  // Replace `local(` / `global(` (and a leading `:` for the `:local(`/`:global(` selector form) with empty.
1509
- const isColon = input.charCodeAt(fn.start - 1) === CC_COLON;
1510
- const openEnd = fn.nameEnd + 1;
1658
+ const fnStart = A.start(fn);
1659
+ const isColon = input.charCodeAt(fnStart - 1) === CC_COLON;
1660
+ const openEnd = A.nameEnd(fn) + 1;
1511
1661
  module.addPresentationalDependency(
1512
- new ConstDependency("", [isColon ? fn.start - 1 : fn.start, openEnd])
1662
+ new ConstDependency("", [isColon ? fnStart - 1 : fnStart, openEnd])
1513
1663
  );
1514
1664
 
1515
1665
  if (type === 1) {
1516
- for (const cv of fn.value) {
1517
- if (cv.type !== NodeType.Ident) continue;
1518
- let identifier = /** @type {Token} */ (cv).unescaped;
1519
- const {
1520
- start: { line: sl, column: sc },
1521
- end: { line: el, column: ec }
1522
- } = cv.loc;
1666
+ for (const cv of A.children(fn)) {
1667
+ if (A.type(cv) !== NodeType.Ident) continue;
1668
+ let identifier = A.unescaped(cv);
1669
+ // Cursor reads instead of `A.loc` — no location objects allocated.
1670
+ const { line: sl, column: sc } = locConverter.get(A.start(cv));
1671
+ const { line: el, column: ec } = locConverter.get(A.end(cv));
1523
1672
  const isDashedIdent = isDashedIdentifier(identifier);
1524
1673
  if (isDashedIdent) identifier = identifier.slice(2);
1525
1674
  addCssExport(
@@ -1529,7 +1678,7 @@ class CssParser extends Parser {
1529
1678
  ec,
1530
1679
  identifier,
1531
1680
  getReexport(identifier),
1532
- [cv.start, cv.end],
1681
+ [A.start(cv), A.end(cv)],
1533
1682
  true,
1534
1683
  CssIcssExportDependency.EXPORT_MODE.ONCE,
1535
1684
  isDashedIdent
@@ -1541,7 +1690,7 @@ class CssParser extends Parser {
1541
1690
 
1542
1691
  // Replace the closing `)`.
1543
1692
  module.addPresentationalDependency(
1544
- new ConstDependency("", [fn.end - 1, fn.end])
1693
+ new ConstDependency("", [A.end(fn) - 1, A.end(fn)])
1545
1694
  );
1546
1695
  };
1547
1696
 
@@ -1553,16 +1702,15 @@ class CssParser extends Parser {
1553
1702
  */
1554
1703
  const processLocalAtRule = (atRule, options) => {
1555
1704
  let found = false;
1556
- for (const cv of atRule.prelude) {
1557
- if (cv.type === NodeType.Whitespace) continue;
1705
+ for (const cv of A.prelude(atRule)) {
1706
+ const cvType = A.type(cv);
1707
+ if (cvType === NodeType.Whitespace) continue;
1558
1708
 
1559
- if (cv.type === NodeType.String) {
1709
+ if (cvType === NodeType.String) {
1560
1710
  if (!found && options.string) {
1561
- const value = /** @type {Token} */ (cv).unescaped;
1562
- const {
1563
- start: { line: sl, column: sc },
1564
- end: { line: el, column: ec }
1565
- } = cv.loc;
1711
+ const value = A.unescaped(cv);
1712
+ const { line: sl, column: sc } = locConverter.get(A.start(cv));
1713
+ const { line: el, column: ec } = locConverter.get(A.end(cv));
1566
1714
  addCssExport(
1567
1715
  sl,
1568
1716
  sc,
@@ -1570,7 +1718,7 @@ class CssParser extends Parser {
1570
1718
  ec,
1571
1719
  value,
1572
1720
  value,
1573
- [cv.start, cv.end],
1721
+ [A.start(cv), A.end(cv)],
1574
1722
  true,
1575
1723
  CssIcssExportDependency.EXPORT_MODE.ONCE
1576
1724
  );
@@ -1580,19 +1728,17 @@ class CssParser extends Parser {
1580
1728
  continue;
1581
1729
  }
1582
1730
 
1583
- if (cv.type === NodeType.Ident) {
1731
+ if (cvType === NodeType.Ident) {
1584
1732
  if (!found && options.identifier) {
1585
- const identifier = /** @type {Token} */ (cv).unescaped;
1733
+ const identifier = A.unescaped(cv);
1586
1734
  if (
1587
1735
  options.identifier instanceof RegExp &&
1588
1736
  options.identifier.test(identifier)
1589
1737
  ) {
1590
1738
  continue;
1591
1739
  }
1592
- const {
1593
- start: { line: sl, column: sc },
1594
- end: { line: el, column: ec }
1595
- } = cv.loc;
1740
+ const { line: sl, column: sc } = locConverter.get(A.start(cv));
1741
+ const { line: el, column: ec } = locConverter.get(A.end(cv));
1596
1742
  addCssExport(
1597
1743
  sl,
1598
1744
  sc,
@@ -1600,7 +1746,7 @@ class CssParser extends Parser {
1600
1746
  ec,
1601
1747
  identifier,
1602
1748
  getReexport(identifier),
1603
- [cv.start, cv.end],
1749
+ [A.start(cv), A.end(cv)],
1604
1750
  true,
1605
1751
  CssIcssExportDependency.EXPORT_MODE.ONCE,
1606
1752
  CssIcssExportDependency.EXPORT_TYPE.NORMAL
@@ -1611,11 +1757,14 @@ class CssParser extends Parser {
1611
1757
  continue;
1612
1758
  }
1613
1759
 
1614
- if (cv.type === NodeType.Function) {
1760
+ if (cvType === NodeType.Function) {
1615
1761
  const fn = /** @type {FunctionNode} */ (cv);
1616
- const fname = fn.unescapedName.toLowerCase();
1617
- const type =
1618
- fname === "local" ? 1 : fname === "global" ? 2 : undefined;
1762
+ const fname = A.unescapedName(fn);
1763
+ const type = equalsLowerCase(fname, "local")
1764
+ ? 1
1765
+ : equalsLowerCase(fname, "global")
1766
+ ? 2
1767
+ : undefined;
1619
1768
  if (!found && type) {
1620
1769
  found = true;
1621
1770
  if (type === 1) pure.markLocal();
@@ -1625,13 +1774,13 @@ class CssParser extends Parser {
1625
1774
  if (
1626
1775
  this.options.dashedIdents &&
1627
1776
  isLocalMode() &&
1628
- (fname === "var" || fname === "style")
1777
+ (equalsLowerCase(fname, "var") || equalsLowerCase(fname, "style"))
1629
1778
  ) {
1630
1779
  processDashedIdentInVarFunction(fn);
1631
1780
  }
1632
1781
  }
1633
1782
  }
1634
- return atRule.end;
1783
+ return A.end(atRule);
1635
1784
  };
1636
1785
  /**
1637
1786
  * Emit the ICSS export declaring this module exports the given custom property.
@@ -1724,56 +1873,67 @@ class CssParser extends Parser {
1724
1873
  /** @type {Token | undefined} */
1725
1874
  let identNode;
1726
1875
  let identIdx = -1;
1727
- for (let i = 0; i < fn.value.length; i++) {
1728
- const cv = fn.value[i];
1729
- if (cv.type === NodeType.Whitespace) continue;
1730
- if (cv.type === NodeType.Ident) {
1876
+ const fv = A.children(fn);
1877
+ for (let i = 0; i < fv.length; i++) {
1878
+ const cv = fv[i];
1879
+ if (A.type(cv) === NodeType.Whitespace) continue;
1880
+ if (A.type(cv) === NodeType.Ident) {
1731
1881
  identNode = /** @type {Token} */ (cv);
1732
1882
  identIdx = i;
1733
1883
  }
1734
1884
  break;
1735
1885
  }
1736
- if (!identNode || !isDashedIdentifier(identNode.value)) return;
1886
+ if (!identNode) return;
1737
1887
 
1738
- const identStart = identNode.start;
1739
- const identEnd = identNode.end;
1888
+ const identStart = A.start(identNode);
1889
+ const identEnd = A.end(identNode);
1890
+ // `isDashedIdentifier` on the value without slicing it: a literal `--`
1891
+ // prefix of length >= 3 (escaped dashes don't match, same as the string
1892
+ // form, since the raw bytes aren't `--`).
1893
+ if (
1894
+ identEnd - identStart < 3 ||
1895
+ input.charCodeAt(identStart) !== CC_HYPHEN_MINUS ||
1896
+ input.charCodeAt(identStart + 1) !== CC_HYPHEN_MINUS
1897
+ ) {
1898
+ return;
1899
+ }
1740
1900
 
1741
1901
  let j = identIdx + 1;
1742
- while (j < fn.value.length && fn.value[j].type === NodeType.Whitespace) {
1902
+ while (j < fv.length && A.type(fv[j]) === NodeType.Whitespace) {
1743
1903
  j++;
1744
1904
  }
1745
1905
 
1746
1906
  if (
1747
- j >= fn.value.length ||
1748
- fn.value[j].type !== NodeType.Ident ||
1749
- !equalsLowerCase(/** @type {Token} */ (fn.value[j]).value, "from")
1907
+ j >= fv.length ||
1908
+ A.type(fv[j]) !== NodeType.Ident ||
1909
+ !rangeEqualsLowerCase(input, A.start(fv[j]), A.end(fv[j]), "from")
1750
1910
  ) {
1751
1911
  emitDashedIdentExport(identStart, identEnd);
1752
1912
  return;
1753
1913
  }
1754
1914
 
1755
- const fromIdent = fn.value[j];
1915
+ const fromIdent = fv[j];
1756
1916
  j++;
1757
- while (j < fn.value.length && fn.value[j].type === NodeType.Whitespace) {
1917
+ while (j < fv.length && A.type(fv[j]) === NodeType.Whitespace) {
1758
1918
  j++;
1759
1919
  }
1760
- if (j >= fn.value.length) return;
1920
+ if (j >= fv.length) return;
1761
1921
 
1762
- const source = fn.value[j];
1922
+ const src = fv[j];
1763
1923
  if (
1764
- source.type === NodeType.Ident &&
1765
- /** @type {Token} */ (source).value === "global"
1924
+ A.type(src) === NodeType.Ident &&
1925
+ rangeEquals(input, A.start(src), A.end(src), "global")
1766
1926
  ) {
1767
- emitDashedIdentFromGlobal(identEnd, source.end);
1927
+ emitDashedIdentFromGlobal(identEnd, A.end(src));
1768
1928
  return;
1769
1929
  }
1770
- if (source.type === NodeType.String) {
1930
+ if (A.type(src) === NodeType.String) {
1771
1931
  emitDashedIdentImport(
1772
1932
  identStart,
1773
1933
  identEnd,
1774
- fromIdent.start,
1775
- source.end,
1776
- input.slice(source.start + 1, source.end - 1)
1934
+ A.start(fromIdent),
1935
+ A.end(src),
1936
+ input.slice(A.start(src) + 1, A.end(src) - 1)
1777
1937
  );
1778
1938
  }
1779
1939
  };
@@ -1785,16 +1945,14 @@ class CssParser extends Parser {
1785
1945
  */
1786
1946
  const childrenOf = (parent) => {
1787
1947
  if (!parent) return undefined;
1788
- switch (parent.type) {
1948
+ switch (A.type(parent)) {
1789
1949
  case NodeType.Function:
1790
- return /** @type {FunctionNode} */ (parent).value;
1791
1950
  case NodeType.SimpleBlock:
1792
- return /** @type {SimpleBlock} */ (parent).value;
1793
1951
  case NodeType.Declaration:
1794
- return /** @type {Declaration} */ (parent).value;
1952
+ return A.children(parent);
1795
1953
  case NodeType.AtRule:
1796
1954
  case NodeType.QualifiedRule:
1797
- return /** @type {AtRule | QualifiedRule} */ (parent).prelude;
1955
+ return A.prelude(parent);
1798
1956
  default:
1799
1957
  return undefined;
1800
1958
  }
@@ -1833,6 +1991,8 @@ class CssParser extends Parser {
1833
1991
  let currentStructural;
1834
1992
  // Cached on each structural enter and read per value token, so the hot Function / Ident / Url visitors don't re-derive the property / at-rule name on every visit.
1835
1993
  let currentAtRuleName = "";
1994
+ // Set by `handleImportAtRule` for a malformed `@import` so its prelude still emits orphan url() deps; read by `urlActive`. Reset per at-rule enter (replaces an ad-hoc property on the node).
1995
+ let currentUrlRecovery = false;
1836
1996
  /** Whether the current Declaration's property is a localizable known property. */
1837
1997
  let currentDeclIsKnownProperty = false;
1838
1998
  /** Whether the current `composes:` declaration owns the whole value (suppresses value rewrites). */
@@ -1853,13 +2013,16 @@ class CssParser extends Parser {
1853
2013
  */
1854
2014
  const stripBareMarker = (colon, ident, after) => {
1855
2015
  const afterIsWhitespace = Boolean(
1856
- after && after.type === NodeType.Whitespace
2016
+ after && A.type(after) === NodeType.Whitespace
1857
2017
  );
2018
+ const identEnd = A.end(ident);
1858
2019
  const stripEnd =
1859
- afterIsWhitespace && after.start === ident.end ? after.end : ident.end;
2020
+ afterIsWhitespace && A.start(after) === identEnd
2021
+ ? A.end(after)
2022
+ : identEnd;
1860
2023
  if (isModules) {
1861
2024
  module.addPresentationalDependency(
1862
- new ConstDependency("", [colon.start, stripEnd])
2025
+ new ConstDependency("", [A.start(colon), stripEnd])
1863
2026
  );
1864
2027
  }
1865
2028
  return afterIsWhitespace;
@@ -1874,17 +2037,18 @@ class CssParser extends Parser {
1874
2037
  */
1875
2038
  const stripFunctionMarker = (colon, fn, isLocal) => {
1876
2039
  if (!isModules) return;
1877
- let stripLeadEnd = fn.end - 1;
1878
- for (const arg of fn.value) {
1879
- if (arg.type !== NodeType.Whitespace) {
1880
- stripLeadEnd = arg.start;
2040
+ const fnEnd = A.end(fn);
2041
+ let stripLeadEnd = fnEnd - 1;
2042
+ for (const arg of A.children(fn)) {
2043
+ if (A.type(arg) !== NodeType.Whitespace) {
2044
+ stripLeadEnd = A.start(arg);
1881
2045
  break;
1882
2046
  }
1883
2047
  }
1884
2048
  module.addPresentationalDependency(
1885
- new ConstDependency("", [colon.start, stripLeadEnd])
2049
+ new ConstDependency("", [A.start(colon), stripLeadEnd])
1886
2050
  );
1887
- let trailStart = fn.end - 1; // position of `)`
2051
+ let trailStart = fnEnd - 1; // position of `)`
1888
2052
  if (isLocal) {
1889
2053
  while (
1890
2054
  trailStart > 0 &&
@@ -1894,7 +2058,7 @@ class CssParser extends Parser {
1894
2058
  }
1895
2059
  }
1896
2060
  module.addPresentationalDependency(
1897
- new ConstDependency("", [trailStart, fn.end])
2061
+ new ConstDependency("", [trailStart, fnEnd])
1898
2062
  );
1899
2063
  };
1900
2064
 
@@ -1904,37 +2068,33 @@ class CssParser extends Parser {
1904
2068
  * @returns {void}
1905
2069
  */
1906
2070
  const handleAttributeSelector = (block) => {
1907
- const attrParts = block.value;
2071
+ const attrParts = A.children(block);
1908
2072
  let ai = 0;
1909
2073
  while (
1910
2074
  ai < attrParts.length &&
1911
- attrParts[ai].type === NodeType.Whitespace
2075
+ A.type(attrParts[ai]) === NodeType.Whitespace
1912
2076
  ) {
1913
2077
  ai++;
1914
2078
  }
1915
2079
  const attrNameNode = attrParts[ai];
1916
- if (!attrNameNode || attrNameNode.type !== NodeType.Ident) return;
1917
- const attrName = /** @type {Token} */ (attrNameNode).unescaped;
2080
+ if (!attrNameNode || A.type(attrNameNode) !== NodeType.Ident) return;
2081
+ const attrName = A.unescaped(attrNameNode);
1918
2082
  if (!equalsLowerCase(attrName, "class")) return;
1919
2083
  ai++;
1920
2084
  while (
1921
2085
  ai < attrParts.length &&
1922
- attrParts[ai].type === NodeType.Whitespace
2086
+ A.type(attrParts[ai]) === NodeType.Whitespace
1923
2087
  ) {
1924
2088
  ai++;
1925
2089
  }
1926
2090
  // `=` or `~=` (two `Delim` tokens for the latter).
1927
2091
  const op1 = attrParts[ai];
1928
- if (!op1 || op1.type !== NodeType.Delim) return;
1929
- const op1v = /** @type {Token} */ (op1).value;
2092
+ if (!op1 || A.type(op1) !== NodeType.Delim) return;
2093
+ const op1v = A.value(op1);
1930
2094
  if (op1v === "~") {
1931
2095
  ai++;
1932
2096
  const op2 = attrParts[ai];
1933
- if (
1934
- !op2 ||
1935
- op2.type !== NodeType.Delim ||
1936
- /** @type {Token} */ (op2).value !== "="
1937
- ) {
2097
+ if (!op2 || A.type(op2) !== NodeType.Delim || A.value(op2) !== "=") {
1938
2098
  return;
1939
2099
  }
1940
2100
  } else if (op1v !== "=") {
@@ -1943,7 +2103,7 @@ class CssParser extends Parser {
1943
2103
  ai++;
1944
2104
  while (
1945
2105
  ai < attrParts.length &&
1946
- attrParts[ai].type === NodeType.Whitespace
2106
+ A.type(attrParts[ai]) === NodeType.Whitespace
1947
2107
  ) {
1948
2108
  ai++;
1949
2109
  }
@@ -1953,12 +2113,12 @@ class CssParser extends Parser {
1953
2113
  let classNameStart;
1954
2114
  /** @type {number} */
1955
2115
  let classNameEnd;
1956
- if (attrValueNode.type === NodeType.String) {
1957
- classNameStart = attrValueNode.start + 1;
1958
- classNameEnd = attrValueNode.end - 1;
1959
- } else if (attrValueNode.type === NodeType.Ident) {
1960
- classNameStart = attrValueNode.start;
1961
- classNameEnd = attrValueNode.end;
2116
+ if (A.type(attrValueNode) === NodeType.String) {
2117
+ classNameStart = A.start(attrValueNode) + 1;
2118
+ classNameEnd = A.end(attrValueNode) - 1;
2119
+ } else if (A.type(attrValueNode) === NodeType.Ident) {
2120
+ classNameStart = A.start(attrValueNode);
2121
+ classNameEnd = A.end(attrValueNode);
1962
2122
  } else {
1963
2123
  return;
1964
2124
  }
@@ -1998,7 +2158,7 @@ class CssParser extends Parser {
1998
2158
  }
1999
2159
  for (let i = 0; i < values.length; i++) {
2000
2160
  const v = values[i];
2001
- switch (v.type) {
2161
+ switch (A.type(v)) {
2002
2162
  case NodeType.Whitespace:
2003
2163
  break;
2004
2164
  case NodeType.Comma:
@@ -2013,8 +2173,9 @@ class CssParser extends Parser {
2013
2173
  // Look ahead for `:local` / `:global` markers; other pseudos fall through.
2014
2174
  const next = values[i + 1];
2015
2175
  if (!next) break;
2016
- if (next.type === NodeType.Ident) {
2017
- const raw = /** @type {Token} */ (next).value;
2176
+ const nextType = A.type(next);
2177
+ if (nextType === NodeType.Ident) {
2178
+ const raw = A.value(next);
2018
2179
  const isLocal = equalsLowerCase(raw, "local");
2019
2180
  if (isLocal || equalsLowerCase(raw, "global")) {
2020
2181
  const id = isLocal ? "local" : "global";
@@ -2029,12 +2190,12 @@ class CssParser extends Parser {
2029
2190
  this._emitWarning(
2030
2191
  state,
2031
2192
  `Missing whitespace after ':${id}' in '${source.slice(
2032
- v.start,
2033
- findLeftCurly(source, next.end) + 1
2193
+ A.start(v),
2194
+ findLeftCurly(source, A.end(next)) + 1
2034
2195
  )}'`,
2035
2196
  locConverter,
2036
- v.start,
2037
- next.end
2197
+ A.start(v),
2198
+ A.end(next)
2038
2199
  );
2039
2200
  }
2040
2201
  segmentMode = id;
@@ -2042,14 +2203,18 @@ class CssParser extends Parser {
2042
2203
  // Skip past the colon + ident.
2043
2204
  i += 1;
2044
2205
  }
2045
- } else if (next.type === NodeType.Function) {
2206
+ } else if (nextType === NodeType.Function) {
2046
2207
  const fn = /** @type {FunctionNode} */ (next);
2047
- const rawName = fn.unescapedName;
2208
+ const rawName = A.unescapedName(fn);
2048
2209
  const isLocal = equalsLowerCase(rawName, "local");
2049
2210
  if (isLocal || equalsLowerCase(rawName, "global")) {
2050
2211
  // `:local(…)` / `:global(…)`: scope mode to the args and strip the `:name(` … `)` wrapper.
2051
2212
  stripFunctionMarker(v, fn, isLocal);
2052
- walkSelectorList(fn.value, isLocal ? "local" : "global", false);
2213
+ walkSelectorList(
2214
+ A.children(fn),
2215
+ isLocal ? "local" : "global",
2216
+ false
2217
+ );
2053
2218
  // Skip past the colon + function.
2054
2219
  i += 1;
2055
2220
  }
@@ -2058,26 +2223,17 @@ class CssParser extends Parser {
2058
2223
  }
2059
2224
  case NodeType.Function:
2060
2225
  // Any other function (`:not(…)`, `:is(…)`, …): recurse with the segment mode preserved (only `:local(…)` / `:global(…)`, handled above, switch mode).
2061
- walkSelectorList(
2062
- /** @type {FunctionNode} */ (v).value,
2063
- segmentMode,
2064
- false
2065
- );
2226
+ walkSelectorList(A.children(v), segmentMode, false);
2066
2227
  break;
2067
2228
  case NodeType.Hash: {
2068
- if (
2069
- /** @type {HashToken} */ (v).typeFlag !== "id" ||
2070
- segmentMode !== "local"
2071
- ) {
2229
+ if (A.typeFlag(v) !== "id" || segmentMode !== "local") {
2072
2230
  break;
2073
2231
  }
2074
2232
  // ID selectors emit the ICSS export but aren't a `composes:` anchor.
2075
- const idValueStart = v.start + 1;
2076
- const idName = /** @type {Token} */ (v).unescaped;
2077
- const {
2078
- start: { line: idSl, column: idSc },
2079
- end: { line: idEl, column: idEc }
2080
- } = v.loc;
2233
+ const idValueStart = A.start(v) + 1;
2234
+ const idName = A.unescaped(v);
2235
+ const { line: idSl, column: idSc } = locConverter.get(A.start(v));
2236
+ const { line: idEl, column: idEc } = locConverter.get(A.end(v));
2081
2237
  addCssExport(
2082
2238
  idSl,
2083
2239
  idSc,
@@ -2085,7 +2241,7 @@ class CssParser extends Parser {
2085
2241
  idEc,
2086
2242
  idName,
2087
2243
  getReexport(idName),
2088
- [idValueStart, v.end],
2244
+ [idValueStart, A.end(v)],
2089
2245
  true,
2090
2246
  CssIcssExportDependency.EXPORT_MODE.ONCE
2091
2247
  );
@@ -2094,17 +2250,18 @@ class CssParser extends Parser {
2094
2250
  }
2095
2251
  case NodeType.SimpleBlock: {
2096
2252
  const block = /** @type {SimpleBlock} */ (v);
2097
- if (block.token === "[") {
2253
+ const bt = A.blockToken(block);
2254
+ if (bt === "[") {
2098
2255
  // Attribute selectors `[class="foo"]` localize only in local mode.
2099
2256
  if (segmentMode === "local") handleAttributeSelector(block);
2100
- } else if (block.token === "(") {
2257
+ } else if (bt === "(") {
2101
2258
  // `@scope (.foo)` and other parenthesised selector wrappers — recurse into the `(…)` block.
2102
- walkSelectorList(block.value, segmentMode, false);
2259
+ walkSelectorList(A.children(block), segmentMode, false);
2103
2260
  }
2104
2261
  break;
2105
2262
  }
2106
2263
  case NodeType.Delim: {
2107
- const delim = /** @type {Token} */ (v).value;
2264
+ const delim = A.value(v);
2108
2265
  if (delim === "&") {
2109
2266
  // Pure-mode: a nesting `&` inherits a pure ancestor's purity.
2110
2267
  if (topLevel && pure.ancestorHadLocal()) pure.markLocal();
@@ -2112,14 +2269,13 @@ class CssParser extends Parser {
2112
2269
  }
2113
2270
  if (delim !== ".") break;
2114
2271
  const next = values[i + 1];
2115
- if (!next || next.type !== NodeType.Ident) break;
2272
+ if (!next || A.type(next) !== NodeType.Ident) break;
2116
2273
  if (segmentMode === "local") {
2117
2274
  // `.<ident>` in local mode is a class selector (dep covers the ident bytes only).
2118
- const name = /** @type {Token} */ (next).unescaped;
2119
- const {
2120
- start: { line: sl, column: sc },
2121
- end: { line: el, column: ec }
2122
- } = next.loc;
2275
+ const name = A.unescaped(next);
2276
+ // Cursor reads instead of `A.loc` — this is the hottest export site.
2277
+ const { line: sl, column: sc } = locConverter.get(A.start(next));
2278
+ const { line: el, column: ec } = locConverter.get(A.end(next));
2123
2279
  addCssExport(
2124
2280
  sl,
2125
2281
  sc,
@@ -2127,18 +2283,18 @@ class CssParser extends Parser {
2127
2283
  ec,
2128
2284
  name,
2129
2285
  getReexport(name),
2130
- [next.start, next.end],
2286
+ [A.start(next), A.end(next)],
2131
2287
  true,
2132
2288
  CssIcssExportDependency.EXPORT_MODE.ONCE
2133
2289
  );
2134
2290
  currentRule.hasLocalAnchor = true;
2135
2291
  currentRule.localIdentifiers.push(name);
2136
2292
  pure.markLocal();
2137
- } else {
2293
+ } else if (icssDefinitions.size !== 0) {
2138
2294
  // `.<ident>` in global mode: not localized, but the ident may be `@value`-defined and need ICSS rewrite.
2139
- const ident = /** @type {Token} */ (next).value;
2295
+ const ident = A.value(next);
2140
2296
  if (!isDashedIdentifier(ident) && icssDefinitions.has(ident)) {
2141
- emitICSSSymbol(ident, next.start, next.end);
2297
+ emitICSSSymbol(ident, A.start(next), A.end(next));
2142
2298
  }
2143
2299
  }
2144
2300
  // Skip the consumed ident.
@@ -2146,10 +2302,11 @@ class CssParser extends Parser {
2146
2302
  break;
2147
2303
  }
2148
2304
  case NodeType.Ident: {
2149
- // ICSS rewrite for a bare `@value`-defined ident used as a type-style selector.
2150
- const ident = /** @type {Token} */ (v).value;
2305
+ // ICSS rewrite for a bare `@value`-defined ident used as a type-style selector; only worth the slice when definitions exist.
2306
+ if (icssDefinitions.size === 0) break;
2307
+ const ident = A.value(v);
2151
2308
  if (!isDashedIdentifier(ident) && icssDefinitions.has(ident)) {
2152
- emitICSSSymbol(ident, v.start, v.end);
2309
+ emitICSSSymbol(ident, A.start(v), A.end(v));
2153
2310
  }
2154
2311
  break;
2155
2312
  }
@@ -2172,11 +2329,9 @@ class CssParser extends Parser {
2172
2329
  */
2173
2330
  const urlActive = () => {
2174
2331
  if (!this.options.url || !currentStructural) return false;
2175
- if (currentStructural.type === NodeType.AtRule) {
2176
- const at = /** @type {AtRule & { urlRecovery?: boolean }} */ (
2177
- currentStructural
2178
- );
2179
- return !equalsLowerCase(at.name, "import") || Boolean(at.urlRecovery);
2332
+ if (A.type(currentStructural) === NodeType.AtRule) {
2333
+ // `currentAtRuleName` is cached on at-rule enter no per-value slice.
2334
+ return currentAtRuleName !== "@import" || currentUrlRecovery;
2180
2335
  }
2181
2336
  return true;
2182
2337
  };
@@ -2204,10 +2359,11 @@ class CssParser extends Parser {
2204
2359
  */
2205
2360
  const localGlobalActive = () => {
2206
2361
  if (!isModules || !currentStructural) return false;
2207
- if (currentStructural.type === NodeType.AtRule) {
2362
+ const t = A.type(currentStructural);
2363
+ if (t === NodeType.AtRule) {
2208
2364
  return !isLocalHandledAtRule(currentAtRuleName);
2209
2365
  }
2210
- if (currentStructural.type === NodeType.Declaration) {
2366
+ if (t === NodeType.Declaration) {
2211
2367
  return !currentDeclComposesSkip;
2212
2368
  }
2213
2369
  return false;
@@ -2219,12 +2375,13 @@ class CssParser extends Parser {
2219
2375
  */
2220
2376
  const icssActive = () => {
2221
2377
  if (!isModules || !currentStructural) return false;
2222
- if (currentStructural.type === NodeType.AtRule) {
2378
+ const t = A.type(currentStructural);
2379
+ if (t === NodeType.AtRule) {
2223
2380
  return (
2224
2381
  currentAtRuleName !== "@value" && currentAtRuleName !== "@import"
2225
2382
  );
2226
2383
  }
2227
- if (currentStructural.type === NodeType.Declaration) {
2384
+ if (t === NodeType.Declaration) {
2228
2385
  return !currentDeclComposesSkip && !currentDeclIsKnownProperty;
2229
2386
  }
2230
2387
  return false;
@@ -2237,8 +2394,7 @@ class CssParser extends Parser {
2237
2394
  * @returns {void}
2238
2395
  */
2239
2396
  const finishTopLevelRule = (node, blockBearing) => {
2240
- const at = /** @type {AtRule} */ (node);
2241
- if (!blockBearing || at.declarations || at.childRules) {
2397
+ if (!blockBearing || A.declarations(node) || A.childRules(node)) {
2242
2398
  allowImport = false;
2243
2399
  }
2244
2400
  pure.markSeenTopLevelRule();
@@ -2383,8 +2539,8 @@ class CssParser extends Parser {
2383
2539
  '", "'
2384
2540
  )}"`,
2385
2541
  locConverter,
2386
- decl.start,
2387
- decl.end
2542
+ A.start(decl),
2543
+ A.end(decl)
2388
2544
  );
2389
2545
  return;
2390
2546
  }
@@ -2395,8 +2551,8 @@ class CssParser extends Parser {
2395
2551
  const groups = [];
2396
2552
  /** @type {AstNode[]} */
2397
2553
  let currentGroup = [];
2398
- for (const cv of decl.value) {
2399
- if (cv.type === NodeType.Comma) {
2554
+ for (const cv of A.children(decl)) {
2555
+ if (A.type(cv) === NodeType.Comma) {
2400
2556
  groups.push(currentGroup);
2401
2557
  currentGroup = [];
2402
2558
  } else {
@@ -2419,20 +2575,20 @@ class CssParser extends Parser {
2419
2575
 
2420
2576
  for (let i = 0; i < group.length; i++) {
2421
2577
  const cv = group[i];
2422
- if (cv.type === NodeType.Whitespace) continue;
2578
+ if (A.type(cv) === NodeType.Whitespace) continue;
2423
2579
 
2424
2580
  if (phase === "expecting-source") {
2425
- if (cv.type === NodeType.String) {
2581
+ if (A.type(cv) === NodeType.String) {
2426
2582
  fromSource = {
2427
2583
  kind: "string",
2428
- path: source.slice(cv.start + 1, cv.end - 1)
2584
+ path: source.slice(A.start(cv) + 1, A.end(cv) - 1)
2429
2585
  };
2430
2586
  phase = "done";
2431
2587
  continue;
2432
2588
  }
2433
2589
  if (
2434
- cv.type === NodeType.Ident &&
2435
- equalsLowerCase(/** @type {Token} */ (cv).value, "global")
2590
+ A.type(cv) === NodeType.Ident &&
2591
+ equalsLowerCase(A.value(cv), "global")
2436
2592
  ) {
2437
2593
  fromSource = { kind: "global" };
2438
2594
  phase = "done";
@@ -2448,8 +2604,8 @@ class CssParser extends Parser {
2448
2604
  continue;
2449
2605
  }
2450
2606
 
2451
- if (cv.type === NodeType.Ident) {
2452
- const identValue = /** @type {Token} */ (cv).value;
2607
+ if (A.type(cv) === NodeType.Ident) {
2608
+ const identValue = A.value(cv);
2453
2609
  if (
2454
2610
  equalsLowerCase(identValue, "from") &&
2455
2611
  classNames.length > 0 &&
@@ -2459,21 +2615,21 @@ class CssParser extends Parser {
2459
2615
  continue;
2460
2616
  }
2461
2617
  classNames.push({
2462
- start: cv.start,
2463
- end: cv.end,
2618
+ start: A.start(cv),
2619
+ end: A.end(cv),
2464
2620
  isGlobal: false
2465
2621
  });
2466
2622
  continue;
2467
2623
  }
2468
2624
 
2469
- if (cv.type === NodeType.Function) {
2625
+ if (A.type(cv) === NodeType.Function) {
2470
2626
  const fn = /** @type {FunctionNode} */ (cv);
2471
- const isGlobal = equalsLowerCase(fn.unescapedName, "global");
2472
- for (const inner of fn.value) {
2473
- if (inner.type === NodeType.Ident) {
2627
+ const isGlobal = equalsLowerCase(A.unescapedName(fn), "global");
2628
+ for (const inner of A.children(fn)) {
2629
+ if (A.type(inner) === NodeType.Ident) {
2474
2630
  classNames.push({
2475
- start: inner.start,
2476
- end: inner.end,
2631
+ start: A.start(inner),
2632
+ end: A.end(inner),
2477
2633
  isGlobal
2478
2634
  });
2479
2635
  break;
@@ -2500,8 +2656,8 @@ class CssParser extends Parser {
2500
2656
  state,
2501
2657
  errorMessage,
2502
2658
  locConverter,
2503
- errorToken.start,
2504
- errorToken.end
2659
+ A.start(errorToken),
2660
+ A.end(errorToken)
2505
2661
  );
2506
2662
  return;
2507
2663
  }
@@ -2512,81 +2668,87 @@ class CssParser extends Parser {
2512
2668
  }
2513
2669
 
2514
2670
  // Strip the whole `composes: …;` (property name included) plus trailing same-line whitespace. The `;` and that whitespace aren't AST nodes (a block's contents drop them), so scan the source here.
2515
- let resumeAt = decl.end;
2516
- if (source.charCodeAt(decl.end) === CC_SEMICOLON) {
2517
- resumeAt = decl.end + 1;
2671
+ let resumeAt = A.end(decl);
2672
+ if (source.charCodeAt(A.end(decl)) === CC_SEMICOLON) {
2673
+ resumeAt = A.end(decl) + 1;
2518
2674
  while (isCssWhitespace(source.charCodeAt(resumeAt))) resumeAt++;
2519
2675
  }
2520
2676
  module.addPresentationalDependency(
2521
- new ConstDependency("", [decl.nameStart, resumeAt])
2677
+ new ConstDependency("", [A.nameStart(decl), resumeAt])
2522
2678
  );
2523
2679
  };
2524
2680
 
2525
2681
  /**
2526
2682
  * Emit url() deps for a `url(...)` / `src(...)` / `image-set(...)` value function.
2527
2683
  * @param {FunctionNode} fn the function node
2528
- * @param {string} fname the function name, lower-cased
2684
+ * @param {string} fname the unescaped function name (any case)
2529
2685
  */
2530
2686
  const emitUrlFunction = (fn, fname) => {
2531
- if (fname === "url" || fname === "src") {
2687
+ if (equalsLowerCase(fname, "url") || equalsLowerCase(fname, "src")) {
2532
2688
  // Quoted `url("…")` / `src("…")`: first non-whitespace value must be the string token.
2533
- const first = fn.value[nextNonWhitespace(fn.value, 0)];
2534
- if (!first || first.type !== NodeType.String) return;
2689
+ const first = A.children(fn)[nextNonWhitespace(A.children(fn), 0)];
2690
+ if (!first || A.type(first) !== NodeType.String) return;
2535
2691
  const string = /** @type {Token} */ (first);
2536
2692
  if (
2537
2693
  webpackIgnored(
2538
- [lastTokenEndForComments, fn.start],
2539
- string.start,
2540
- string.end
2694
+ [lastTokenEndForComments, A.start(fn)],
2695
+ A.start(string),
2696
+ A.end(string)
2541
2697
  )
2542
2698
  ) {
2543
2699
  return;
2544
2700
  }
2545
2701
  const value = normalizeUrl(
2546
- input.slice(string.start + 1, string.end - 1),
2702
+ input.slice(A.start(string) + 1, A.end(string) - 1),
2547
2703
  true
2548
2704
  );
2549
2705
  // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
2550
2706
  if (value.length === 0) return;
2551
2707
  const dep = new CssUrlDependency(
2552
2708
  value,
2553
- [string.start, string.end],
2709
+ [A.start(string), A.end(string)],
2554
2710
  "string"
2555
2711
  );
2556
- setDepLoc(dep, string.start, string.end);
2712
+ setDepLoc(dep, A.start(string), A.end(string));
2557
2713
  module.addDependency(dep);
2558
2714
  module.addCodeGenerationDependency(dep);
2559
2715
  } else if (IMAGE_SET_FUNCTION.test(fname)) {
2560
2716
  // `image-set(…)`: each comma segment's first string is the URL; advance the magic-comment fence per string.
2561
- lastTokenEndForComments = fn.nameEnd + 1;
2562
- let prevStringEnd = fn.start;
2717
+ lastTokenEndForComments = A.nameEnd(fn) + 1;
2718
+ let prevStringEnd = A.start(fn);
2563
2719
  let firstInSegment = true;
2564
- for (const cv of fn.value) {
2565
- if (cv.type === NodeType.Comma) {
2720
+ for (const cv of A.children(fn)) {
2721
+ if (A.type(cv) === NodeType.Comma) {
2566
2722
  firstInSegment = true;
2567
2723
  continue;
2568
2724
  }
2569
- if (cv.type === NodeType.Whitespace) continue;
2725
+ if (A.type(cv) === NodeType.Whitespace) continue;
2570
2726
  const wasFirst = firstInSegment;
2571
2727
  firstInSegment = false;
2572
- if (!wasFirst || cv.type !== NodeType.String) continue;
2728
+ if (!wasFirst || A.type(cv) !== NodeType.String) continue;
2573
2729
  const string = /** @type {Token} */ (cv);
2574
2730
  const start = prevStringEnd;
2575
- prevStringEnd = string.end;
2731
+ prevStringEnd = A.end(string);
2576
2732
  const value = normalizeUrl(
2577
- input.slice(string.start + 1, string.end - 1),
2733
+ input.slice(A.start(string) + 1, A.end(string) - 1),
2578
2734
  true
2579
2735
  );
2580
2736
  if (value.length === 0) continue;
2581
- if (webpackIgnored([start, string.end], string.start, string.end)) {
2737
+ if (
2738
+ webpackIgnored(
2739
+ [start, A.end(string)],
2740
+ A.start(string),
2741
+ A.end(string)
2742
+ )
2743
+ ) {
2582
2744
  continue;
2583
2745
  }
2584
2746
  const dep = new CssUrlDependency(
2585
2747
  value,
2586
- [string.start, string.end],
2748
+ [A.start(string), A.end(string)],
2587
2749
  "url"
2588
2750
  );
2589
- setDepLoc(dep, string.start, string.end);
2751
+ setDepLoc(dep, A.start(string), A.end(string));
2590
2752
  module.addDependency(dep);
2591
2753
  module.addCodeGenerationDependency(dep);
2592
2754
  }
@@ -2605,24 +2767,24 @@ class CssParser extends Parser {
2605
2767
  state,
2606
2768
  "Any '@import' rules must precede all other rules",
2607
2769
  locConverter,
2608
- at.start,
2609
- at.nameEnd
2770
+ A.start(at),
2771
+ A.nameEnd(at)
2610
2772
  );
2611
2773
  return;
2612
2774
  }
2613
- const importStart = at.start;
2614
- const importNameEnd = at.nameEnd;
2775
+ const importStart = A.start(at);
2776
+ const importNameEnd = A.nameEnd(at);
2615
2777
  // We only accept `;`-terminated @import; block / EOF / `}` ends are silent bails.
2616
- if (source.charCodeAt(at.end) !== CC_SEMICOLON) return;
2778
+ if (source.charCodeAt(A.end(at)) !== CC_SEMICOLON) return;
2617
2779
 
2618
2780
  // Walk the prelude in spec order (URL → layer? → supports? → media query); anything else joins the media query.
2619
2781
  const { urlNode, layerNode, supportsNode } = parseImportPrelude(
2620
- at.prelude
2782
+ A.prelude(at)
2621
2783
  );
2622
2784
 
2623
- const semi = at.end + 1; // position past `;`
2785
+ const semi = A.end(at) + 1; // position past `;`
2624
2786
 
2625
- if (!urlNode || (urlNode.type === NodeType.Ident && !isModules)) {
2787
+ if (!urlNode || (A.type(urlNode) === NodeType.Ident && !isModules)) {
2626
2788
  this._emitWarning(
2627
2789
  state,
2628
2790
  `Expected URL in '${input.slice(importStart, semi)}'`,
@@ -2630,17 +2792,16 @@ class CssParser extends Parser {
2630
2792
  importStart,
2631
2793
  semi
2632
2794
  );
2633
- // A malformed `@import` still emits orphan url() deps from its prelude — flag the node so the value visitors enable url().
2634
- /** @type {AtRule & { urlRecovery?: boolean }} */ (at).urlRecovery =
2635
- true;
2795
+ // A malformed `@import` still emits orphan url() deps from its prelude — flag it so the value visitors enable url().
2796
+ currentUrlRecovery = true;
2636
2797
  return;
2637
2798
  }
2638
2799
 
2639
2800
  /** @type {string} */
2640
2801
  let url;
2641
- if (urlNode.type === NodeType.Ident) {
2802
+ if (A.type(urlNode) === NodeType.Ident) {
2642
2803
  // URL given as identifier — resolve via CSS Modules `@value`.
2643
- const identName = /** @type {Token} */ (urlNode).value;
2804
+ const identName = A.value(urlNode);
2644
2805
  const def = icssDefinitions.get(identName);
2645
2806
  if (!def) {
2646
2807
  this._emitWarning(
@@ -2676,21 +2837,24 @@ class CssParser extends Parser {
2676
2837
  (raw.startsWith("'") && raw.endsWith("'"))
2677
2838
  ? normalizeUrl(raw.slice(1, -1), true)
2678
2839
  : normalizeUrl(raw, false);
2679
- } else if (urlNode.type === NodeType.Url) {
2840
+ } else if (A.type(urlNode) === NodeType.Url) {
2680
2841
  const ut = /** @type {UrlToken} */ (urlNode);
2681
- url = normalizeUrl(input.slice(ut.contentStart, ut.contentEnd), false);
2682
- } else if (urlNode.type === NodeType.String) {
2683
2842
  url = normalizeUrl(
2684
- input.slice(urlNode.start + 1, urlNode.end - 1),
2843
+ input.slice(A.contentStart(ut), A.contentEnd(ut)),
2844
+ false
2845
+ );
2846
+ } else if (A.type(urlNode) === NodeType.String) {
2847
+ url = normalizeUrl(
2848
+ input.slice(A.start(urlNode) + 1, A.end(urlNode) - 1),
2685
2849
  true
2686
2850
  );
2687
2851
  } else {
2688
2852
  // url(...) function — first non-whitespace child is the string.
2689
2853
  /** @type {Token | undefined} */
2690
2854
  let string;
2691
- for (const inner of /** @type {FunctionNode} */ (urlNode).value) {
2692
- if (inner.type === NodeType.Whitespace) continue;
2693
- if (inner.type === NodeType.String) {
2855
+ for (const inner of A.children(urlNode)) {
2856
+ if (A.type(inner) === NodeType.Whitespace) continue;
2857
+ if (A.type(inner) === NodeType.String) {
2694
2858
  string = /** @type {Token} */ (inner);
2695
2859
  }
2696
2860
  break;
@@ -2705,11 +2869,16 @@ class CssParser extends Parser {
2705
2869
  );
2706
2870
  return;
2707
2871
  }
2708
- url = normalizeUrl(input.slice(string.start + 1, string.end - 1), true);
2872
+ url = normalizeUrl(
2873
+ input.slice(A.start(string) + 1, A.end(string) - 1),
2874
+ true
2875
+ );
2709
2876
  }
2710
2877
 
2711
2878
  const newline = skipWhiteLine(input, semi);
2712
- if (webpackIgnored([importNameEnd, urlNode.end], importStart, newline)) {
2879
+ if (
2880
+ webpackIgnored([importNameEnd, A.end(urlNode)], importStart, newline)
2881
+ ) {
2713
2882
  return;
2714
2883
  }
2715
2884
  if (url.length === 0) {
@@ -2723,10 +2892,10 @@ class CssParser extends Parser {
2723
2892
  /** @type {undefined | string} */
2724
2893
  let layer;
2725
2894
  if (layerNode) {
2726
- if (layerNode.type === NodeType.Function) {
2895
+ if (A.type(layerNode) === NodeType.Function) {
2727
2896
  // `layer(<ident>)` — extract content between `(` and `)`.
2728
2897
  const fn = /** @type {FunctionNode} */ (layerNode);
2729
- layer = input.slice(fn.nameEnd + 1, fn.end - 1).trim();
2898
+ layer = input.slice(A.nameEnd(fn) + 1, A.end(fn) - 1).trim();
2730
2899
  } else {
2731
2900
  // Bare `layer` ident — anonymous layer.
2732
2901
  layer = "";
@@ -2737,21 +2906,23 @@ class CssParser extends Parser {
2737
2906
  let supports;
2738
2907
  if (supportsNode) {
2739
2908
  supports = input
2740
- .slice(supportsNode.nameEnd + 1, supportsNode.end - 1)
2909
+ .slice(A.nameEnd(supportsNode) + 1, A.end(supportsNode) - 1)
2741
2910
  .trim();
2742
2911
  }
2743
2912
 
2744
2913
  // Media query = whatever sits between the last url/layer/supports part and the closing `;`, trimmed. Start at the next non-whitespace prelude node (skips the gap, comments included).
2745
2914
  const lastPrefixPart = supportsNode || layerNode || urlNode;
2746
2915
  const afterIdx =
2747
- /** @type {AstNode[]} */ (at.prelude).indexOf(lastPrefixPart) + 1;
2748
- const nextIdx = nextNonWhitespace(at.prelude, afterIdx);
2916
+ /** @type {AstNode[]} */ (A.prelude(at)).indexOf(lastPrefixPart) + 1;
2917
+ const nextIdx = nextNonWhitespace(A.prelude(at), afterIdx);
2749
2918
  const mediaStart =
2750
- nextIdx < at.prelude.length ? at.prelude[nextIdx].start : at.end;
2919
+ nextIdx < A.prelude(at).length
2920
+ ? A.start(A.prelude(at)[nextIdx])
2921
+ : A.end(at);
2751
2922
  /** @type {undefined | string} */
2752
2923
  let media;
2753
- if (mediaStart !== at.end) {
2754
- media = input.slice(mediaStart, at.end).trim();
2924
+ if (mediaStart !== A.end(at)) {
2925
+ media = input.slice(mediaStart, A.end(at)).trim();
2755
2926
  }
2756
2927
 
2757
2928
  const { line: sl, column: sc } = locConverter.get(importStart);
@@ -2793,9 +2964,9 @@ class CssParser extends Parser {
2793
2964
  * @param {AtRule} at the `@value` at-rule
2794
2965
  */
2795
2966
  const handleValueAtRule = (at) => {
2796
- const start = at.start;
2797
- const nameEnd = at.nameEnd;
2798
- const semi = at.end;
2967
+ const start = A.start(at);
2968
+ const nameEnd = A.nameEnd(at);
2969
+ const semi = A.end(at);
2799
2970
  const atRuleEnd =
2800
2971
  source.charCodeAt(semi) === CC_SEMICOLON ? semi + 1 : semi;
2801
2972
  const params = input.slice(nameEnd, semi);
@@ -2956,12 +3127,12 @@ class CssParser extends Parser {
2956
3127
  /** @type {(cvs: AstNode[]) => void} */
2957
3128
  const walkExports = (cvs) => {
2958
3129
  for (const cv of cvs) {
2959
- switch (cv.type) {
3130
+ switch (A.type(cv)) {
2960
3131
  case NodeType.Comma:
2961
3132
  parsedKeywords = Object.create(null);
2962
3133
  break;
2963
3134
  case NodeType.Delim:
2964
- afterExclamation = /** @type {Token} */ (cv).value === "!";
3135
+ afterExclamation = A.value(cv) === "!";
2965
3136
  break;
2966
3137
  case NodeType.Ident: {
2967
3138
  if (isGridTemplate) break;
@@ -2969,8 +3140,9 @@ class CssParser extends Parser {
2969
3140
  afterExclamation = false;
2970
3141
  break;
2971
3142
  }
2972
- const identifier = /** @type {Token} */ (cv).value;
2973
- const keyword = identifier.toLowerCase();
3143
+ const identifier = A.value(cv);
3144
+ // Values are almost always lowercase already — avoid the copy.
3145
+ const keyword = toLowerCaseIfNeeded(identifier);
2974
3146
  parsedKeywords[keyword] =
2975
3147
  typeof parsedKeywords[keyword] !== "undefined"
2976
3148
  ? parsedKeywords[keyword] + 1
@@ -2981,7 +3153,7 @@ class CssParser extends Parser {
2981
3153
  ) {
2982
3154
  break;
2983
3155
  }
2984
- emit(cv.start, cv.end);
3156
+ emit(A.start(cv), A.end(cv));
2985
3157
  break;
2986
3158
  }
2987
3159
  case NodeType.String: {
@@ -2989,17 +3161,17 @@ class CssParser extends Parser {
2989
3161
  declPropertyName === "animation" ||
2990
3162
  declPropertyName === "animation-name"
2991
3163
  ) {
2992
- emit(cv.start, cv.end, true);
3164
+ emit(A.start(cv), A.end(cv), true);
2993
3165
  }
2994
3166
  if (
2995
3167
  declPropertyName === "grid" ||
2996
3168
  declPropertyName === "grid-template" ||
2997
3169
  declPropertyName === "grid-template-areas"
2998
3170
  ) {
2999
- const areas = /** @type {Token} */ (cv).unescaped;
3171
+ const areas = A.unescaped(cv);
3000
3172
  const matches = matchAll(/\b\w+\b/g, areas);
3001
3173
  for (const match of matches) {
3002
- const areaStart = cv.start + 1 + match.index;
3174
+ const areaStart = A.start(cv) + 1 + match.index;
3003
3175
  emit(areaStart, areaStart + match[0].length, false);
3004
3176
  }
3005
3177
  }
@@ -3007,28 +3179,28 @@ class CssParser extends Parser {
3007
3179
  }
3008
3180
  case NodeType.SimpleBlock: {
3009
3181
  const block = /** @type {SimpleBlock} */ (cv);
3010
- if (block.token === "[") {
3182
+ if (A.blockToken(block) === "[") {
3011
3183
  // Collect identifiers until the first non-ident token (`<line-names> = '[' <custom-ident>* ']'`).
3012
- for (const inner of block.value) {
3013
- if (inner.type === NodeType.Whitespace) continue;
3014
- if (inner.type !== NodeType.Ident) break;
3015
- emit(inner.start, inner.end);
3184
+ for (const inner of A.children(block)) {
3185
+ if (A.type(inner) === NodeType.Whitespace) continue;
3186
+ if (A.type(inner) !== NodeType.Ident) break;
3187
+ emit(A.start(inner), A.end(inner));
3016
3188
  }
3017
3189
  } else if (isGridTemplate) {
3018
- walkExports(block.value);
3190
+ walkExports(A.children(block));
3019
3191
  }
3020
3192
  break;
3021
3193
  }
3022
3194
  case NodeType.Function:
3023
3195
  if (isGridTemplate) {
3024
- walkExports(/** @type {FunctionNode} */ (cv).value);
3196
+ walkExports(A.children(cv));
3025
3197
  }
3026
3198
  break;
3027
3199
  // Other types carry no ICSS-export information.
3028
3200
  }
3029
3201
  }
3030
3202
  };
3031
- walkExports(decl.value);
3203
+ walkExports(A.children(decl));
3032
3204
  };
3033
3205
 
3034
3206
  /**
@@ -3047,27 +3219,26 @@ class CssParser extends Parser {
3047
3219
  ) => {
3048
3220
  const acceptIdent = isKeyframes || isCounterStyle || isContainer;
3049
3221
  const acceptString = isKeyframes;
3050
- for (const cv of at.prelude) {
3051
- if (cv.type === NodeType.Whitespace) continue;
3052
- if (cv.type === NodeType.String) {
3222
+ for (const cv of A.prelude(at)) {
3223
+ const cvType = A.type(cv);
3224
+ if (cvType === NodeType.Whitespace) continue;
3225
+ if (cvType === NodeType.String) {
3053
3226
  if (acceptString) pure.markLocal();
3054
3227
  break;
3055
3228
  }
3056
- if (cv.type === NodeType.Ident) {
3229
+ if (cvType === NodeType.Ident) {
3057
3230
  if (!acceptIdent) break;
3058
- if (isContainer && isContainerKeyword(source, cv.start, cv.end)) {
3231
+ if (
3232
+ isContainer &&
3233
+ isContainerKeyword(source, A.start(cv), A.end(cv))
3234
+ ) {
3059
3235
  continue;
3060
3236
  }
3061
3237
  pure.markLocal();
3062
3238
  break;
3063
3239
  }
3064
- if (cv.type === NodeType.Function) {
3065
- if (
3066
- equalsLowerCase(
3067
- /** @type {FunctionNode} */ (cv).unescapedName,
3068
- "local"
3069
- )
3070
- ) {
3240
+ if (cvType === NodeType.Function) {
3241
+ if (equalsLowerCase(A.unescapedName(cv), "local")) {
3071
3242
  pure.markLocal();
3072
3243
  }
3073
3244
  break;
@@ -3078,12 +3249,15 @@ class CssParser extends Parser {
3078
3249
  // Drive the walk through SourceProcessor: structural enter / exit map to the `walkAst…Enter` / `…Exit` halves; value visitors handle url / ICSS / local-global.
3079
3250
  /** @type {VisitorMap} */
3080
3251
  const visitors = {
3252
+ [NodeType.Comment]: commentVisitor,
3081
3253
  [NodeType.AtRule]: {
3082
3254
  // At-rule enter: scope save, name dispatch, prelude value context, pure-block push.
3083
- enter: (node, parent) => {
3255
+ enter: (path) => {
3256
+ const node = path.node;
3084
3257
  const at = /** @type {AtRule} */ (node);
3085
- const topLevel = parent === null;
3086
- advanceCommentCursor(at.start);
3258
+ const topLevel = path.parent === null;
3259
+ currentUrlRecovery = false;
3260
+ advanceCommentCursor(A.start(at));
3087
3261
  const savedAnchor = currentRule.hasLocalAnchor;
3088
3262
  const savedLocalIdentifiers = currentRule.localIdentifiers;
3089
3263
  // Only modules-mode walks mutate `localIdentifiers`; otherwise share
@@ -3091,33 +3265,32 @@ class CssParser extends Parser {
3091
3265
  currentRule.localIdentifiers = isModules
3092
3266
  ? [...savedLocalIdentifiers]
3093
3267
  : savedLocalIdentifiers;
3094
- const name = `@${at.name.toLowerCase()}`;
3268
+ const name = `@${toLowerCaseIfNeeded(A.name(at))}`;
3095
3269
  switch (name) {
3096
3270
  case "@namespace": {
3097
3271
  this._emitWarning(
3098
3272
  state,
3099
3273
  "'@namespace' is not supported in bundled CSS",
3100
3274
  locConverter,
3101
- at.start,
3102
- at.nameEnd
3275
+ A.start(at),
3276
+ A.nameEnd(at)
3103
3277
  );
3104
3278
  break;
3105
3279
  }
3106
3280
  case "@charset": {
3107
3281
  if (/** @type {CssModule} */ (module).exportType !== "style") {
3282
+ const atEnd = A.end(at);
3108
3283
  const atRuleEnd =
3109
- source.charCodeAt(at.end) === CC_SEMICOLON
3110
- ? at.end + 1
3111
- : at.end;
3112
- const dep = new ConstDependency("", [at.start, atRuleEnd]);
3284
+ source.charCodeAt(atEnd) === CC_SEMICOLON ? atEnd + 1 : atEnd;
3285
+ const dep = new ConstDependency("", [A.start(at), atRuleEnd]);
3113
3286
  module.addPresentationalDependency(dep);
3114
- const string = at.prelude.find(
3115
- (v) => v.type !== NodeType.Whitespace
3287
+ const string = A.prelude(at).find(
3288
+ (v) => A.type(v) !== NodeType.Whitespace
3116
3289
  );
3117
- if (string && string.type === NodeType.String) {
3290
+ if (string && A.type(string) === NodeType.String) {
3118
3291
  /** @type {CssModuleBuildInfo} */
3119
3292
  (module.buildInfo).charset = source
3120
- .slice(string.start + 1, string.end - 1)
3293
+ .slice(A.start(string) + 1, A.end(string) - 1)
3121
3294
  .toUpperCase();
3122
3295
  }
3123
3296
  }
@@ -3158,11 +3331,11 @@ class CssParser extends Parser {
3158
3331
  // `@scope (.x) to (.y)` — walk the prelude as a selector list.
3159
3332
  if (
3160
3333
  isModules &&
3161
- equalsLowerCase(at.name, "scope") &&
3162
- at.prelude.length > 0
3334
+ equalsLowerCase(A.name(at), "scope") &&
3335
+ A.prelude(at).length > 0
3163
3336
  ) {
3164
3337
  walkSelectorList(
3165
- at.prelude,
3338
+ A.prelude(at),
3166
3339
  /** @type {"local" | "global"} */ (
3167
3340
  mode === "local" ? "local" : "global"
3168
3341
  )
@@ -3175,14 +3348,9 @@ class CssParser extends Parser {
3175
3348
  currentStructural = at;
3176
3349
  currentAtRuleName = name;
3177
3350
  dashed.active = false;
3178
- // `@import` url() is the import target — only walk its prelude for url deps on malformed-import recovery (flagged on the node).
3179
- const atWithRecovery =
3180
- /** @type {AtRule & { urlRecovery?: boolean }} */ (at);
3181
- if (
3182
- this.options.url &&
3183
- (name !== "@import" || atWithRecovery.urlRecovery)
3184
- ) {
3185
- lastTokenEndForComments = at.nameEnd;
3351
+ // `@import` url() is the import target — only walk its prelude for url deps on malformed-import recovery.
3352
+ if (this.options.url && (name !== "@import" || currentUrlRecovery)) {
3353
+ lastTokenEndForComments = A.nameEnd(at);
3186
3354
  }
3187
3355
  // Dashed-ident scoping over the prelude (the Ident / Function visitors emit).
3188
3356
  dashed.active = Boolean(
@@ -3219,18 +3387,21 @@ class CssParser extends Parser {
3219
3387
  }
3220
3388
 
3221
3389
  // pure.stack push for block-bearing at-rules.
3222
- const atHasBlock = Boolean(at.declarations || at.childRules);
3223
- if (atHasBlock && at.blockStart !== -1) {
3390
+ const atDecls = A.declarations(at);
3391
+ const atChildRules = A.childRules(at);
3392
+ const atHasBlock = Boolean(atDecls || atChildRules);
3393
+ const atBlockStart = A.blockStart(at);
3394
+ if (atHasBlock && atBlockStart !== -1) {
3224
3395
  const isAtRulePrelude = isPureBodyAtRule(name);
3225
3396
  if (isAtRulePrelude) pure.finalizeSelector();
3226
3397
  pure.enterBlock({
3227
3398
  isRulePrelude: isAtRulePrelude,
3228
3399
  treatAsLeaf: atTreatAsLeaf,
3229
3400
  ownSkip: atSkipChildren,
3230
- declarations: at.declarations,
3231
- childRules: at.childRules,
3232
- preludeStart: at.start,
3233
- preludeEnd: at.blockStart
3401
+ declarations: atDecls,
3402
+ childRules: atChildRules,
3403
+ preludeStart: A.start(at),
3404
+ preludeEnd: atBlockStart
3234
3405
  });
3235
3406
  }
3236
3407
 
@@ -3239,11 +3410,12 @@ class CssParser extends Parser {
3239
3410
  savedLocalIdentifiers,
3240
3411
  name,
3241
3412
  hasBlock: atHasBlock,
3242
- endsWithSemicolon: source.charCodeAt(at.end) === CC_SEMICOLON
3413
+ endsWithSemicolon: source.charCodeAt(A.end(at)) === CC_SEMICOLON
3243
3414
  });
3244
3415
  },
3245
3416
  // At-rule exit: pure-frame finalization, `suppressNextRulePrelude`, scope restore, top-level reset.
3246
- exit: (node, parent) => {
3417
+ exit: (path) => {
3418
+ const node = path.node;
3247
3419
  const state = atRuleStateStack.pop();
3248
3420
  if (!state) return;
3249
3421
  if (state.hasBlock) {
@@ -3258,33 +3430,36 @@ class CssParser extends Parser {
3258
3430
  }
3259
3431
  currentRule.hasLocalAnchor = state.savedAnchor;
3260
3432
  currentRule.localIdentifiers = state.savedLocalIdentifiers;
3261
- if (parent === null) finishTopLevelRule(node, true);
3433
+ if (path.parent === null) finishTopLevelRule(node, true);
3262
3434
  }
3263
3435
  },
3264
3436
  [NodeType.QualifiedRule]: {
3265
- // Qualified-rule enter: scope setup, selector + prelude context, pure-block push; `:import` / `:export` bail via `ctx.skipChildren()`.
3266
- enter: (node, parent, ctx) => {
3437
+ // Qualified-rule enter: scope setup, selector + prelude context, pure-block push; `:import` / `:export` bail via `path.skipChildren()`.
3438
+ enter: (path) => {
3439
+ const node = path.node;
3267
3440
  const rule = /** @type {QualifiedRule} */ (node);
3268
- const topLevel = parent === null;
3269
- advanceCommentCursor(rule.start);
3441
+ const topLevel = path.parent === null;
3442
+ advanceCommentCursor(A.start(rule));
3270
3443
  // `:import(…) { … }` / `:export { … }` ICSS pseudo-rules are processed inline at top level; nested ones bail out.
3271
3444
  if (isModules) {
3272
- const firstIdx = nextNonWhitespace(rule.prelude, 0);
3445
+ const rulePrelude = A.prelude(rule);
3446
+ const firstIdx = nextNonWhitespace(rulePrelude, 0);
3273
3447
  if (
3274
- firstIdx + 1 < rule.prelude.length &&
3275
- rule.prelude[firstIdx].type === NodeType.Colon
3448
+ firstIdx + 1 < rulePrelude.length &&
3449
+ A.type(rulePrelude[firstIdx]) === NodeType.Colon
3276
3450
  ) {
3277
- const second = rule.prelude[firstIdx + 1];
3451
+ const second = rulePrelude[firstIdx + 1];
3452
+ const secondType = A.type(second);
3278
3453
  const rawName =
3279
- second.type === NodeType.Ident
3280
- ? /** @type {Token} */ (second).value
3281
- : second.type === NodeType.Function
3282
- ? /** @type {FunctionNode} */ (second).name
3454
+ secondType === NodeType.Ident
3455
+ ? A.value(second)
3456
+ : secondType === NodeType.Function
3457
+ ? A.name(second)
3283
3458
  : "";
3284
3459
  const isImport = equalsLowerCase(rawName, "import");
3285
3460
  if (isImport || equalsLowerCase(rawName, "export")) {
3286
3461
  if (topLevel) {
3287
- const startColon = rule.prelude[firstIdx].start;
3462
+ const startColon = A.start(rulePrelude[firstIdx]);
3288
3463
  const endAfterBody = processImportOrExport(
3289
3464
  isImport ? 0 : 1,
3290
3465
  second,
@@ -3293,14 +3468,16 @@ class CssParser extends Parser {
3293
3468
  module.addPresentationalDependency(
3294
3469
  new ConstDependency("", [startColon, endAfterBody])
3295
3470
  );
3296
- if (rule.blockStart !== -1) rule.blockEnd = endAfterBody;
3297
- rule.end = endAfterBody;
3298
- } else if (rule.blockStart !== -1) {
3471
+ if (A.blockStart(rule) !== -1) {
3472
+ A.setBlockEnd(rule, endAfterBody);
3473
+ }
3474
+ A.setEnd(rule, endAfterBody);
3475
+ } else if (A.blockStart(rule) !== -1) {
3299
3476
  // Nested `:import` / `:export` — leave the body alone.
3300
- rule.end = rule.blockEnd;
3477
+ A.setEnd(rule, A.blockEnd(rule));
3301
3478
  }
3302
3479
  // Don't recurse into the body — handled inline above.
3303
- if (ctx) ctx.skipChildren();
3480
+ path.skipChildren();
3304
3481
  qualifiedRuleStateStack.push({ bailed: true });
3305
3482
  return;
3306
3483
  }
@@ -3327,9 +3504,10 @@ class CssParser extends Parser {
3327
3504
  savedComposesFiles
3328
3505
  });
3329
3506
  // Selectors are only CSS-Modules-relevant when `isModules` holds.
3507
+ const rulePrelude = A.prelude(rule);
3330
3508
  if (isModules) {
3331
3509
  walkSelectorList(
3332
- rule.prelude,
3510
+ rulePrelude,
3333
3511
  /** @type {"local" | "global"} */ (
3334
3512
  mode === "local" ? "local" : "global"
3335
3513
  )
@@ -3339,20 +3517,20 @@ class CssParser extends Parser {
3339
3517
  currentStructural = rule;
3340
3518
  dashed.active = false;
3341
3519
  dashed.emit = false;
3342
- if (this.options.url && rule.prelude.length > 0) {
3343
- lastTokenEndForComments = rule.prelude[0].start;
3520
+ if (this.options.url && rulePrelude.length > 0) {
3521
+ lastTokenEndForComments = A.start(rulePrelude[0]);
3344
3522
  }
3345
3523
  // Dashed-ident scoping for the deprecated `--foo: { … }` custom-property-set syntax (prelude starts with a dashed-ident).
3346
3524
  if (
3347
3525
  this.options.dashedIdents &&
3348
3526
  isModules &&
3349
- rule.prelude.length > 0
3527
+ rulePrelude.length > 0
3350
3528
  ) {
3351
- const first = rule.prelude[nextNonWhitespace(rule.prelude, 0)];
3529
+ const first = rulePrelude[nextNonWhitespace(rulePrelude, 0)];
3352
3530
  if (
3353
3531
  first &&
3354
- first.type === NodeType.Ident &&
3355
- isDashedIdentifier(/** @type {Token} */ (first).value)
3532
+ A.type(first) === NodeType.Ident &&
3533
+ isDashedIdentifier(A.value(first))
3356
3534
  ) {
3357
3535
  const effectiveLocalMode = isEffectivelyLocal();
3358
3536
  if (effectiveLocalMode) {
@@ -3362,18 +3540,20 @@ class CssParser extends Parser {
3362
3540
  }
3363
3541
  }
3364
3542
  // Pure-mode: report an impure prelude (if leaf-ish) and push the inherited-context frame before walking the body.
3543
+ const ruleBlockStart = A.blockStart(rule);
3365
3544
  pure.enterBlock({
3366
3545
  isRulePrelude: true,
3367
3546
  treatAsLeaf: false,
3368
3547
  ownSkip: false,
3369
- declarations: rule.declarations,
3370
- childRules: rule.childRules,
3371
- preludeStart: rule.start,
3372
- preludeEnd: rule.blockStart !== -1 ? rule.blockStart : rule.end
3548
+ declarations: A.declarations(rule),
3549
+ childRules: A.childRules(rule),
3550
+ preludeStart: A.start(rule),
3551
+ preludeEnd: ruleBlockStart !== -1 ? ruleBlockStart : A.end(rule)
3373
3552
  });
3374
3553
  },
3375
3554
  // Qualified-rule exit: pure-frame finalization, scope restore, top-level reset; no-op for bailed ICSS.
3376
- exit: (node, parent) => {
3555
+ exit: (path) => {
3556
+ const node = path.node;
3377
3557
  const state = qualifiedRuleStateStack.pop();
3378
3558
  if (!state || state.bailed) return;
3379
3559
  pure.exitBlock();
@@ -3381,23 +3561,19 @@ class CssParser extends Parser {
3381
3561
  currentRule.localIdentifiers = state.savedLocalIdentifiers;
3382
3562
  currentRule.composesPrevFile = state.savedPrevComposesFile;
3383
3563
  currentRule.composesFiles = state.savedComposesFiles;
3384
- if (parent === null) finishTopLevelRule(node, false);
3564
+ if (path.parent === null) finishTopLevelRule(node, false);
3385
3565
  }
3386
3566
  },
3387
3567
  // Top-level declarations are parse errors (dropped by `parseAStylesheet`), so a declaration's parent is always a block.
3388
- [NodeType.Declaration]: (node) => {
3568
+ [NodeType.Declaration]: (path) => {
3569
+ const node = path.node;
3389
3570
  const decl = /** @type {Declaration} */ (node);
3390
3571
  // Reset value-visitor context, read by the value visitors below.
3391
3572
  currentStructural = decl;
3392
3573
  dashed.active = false;
3393
3574
  dashed.emit = false;
3394
- const declPropertyName = decl.name
3395
- .replace(/^(-\w+-)/, "")
3396
- .toLowerCase();
3397
- // Cache the known-property flag so the per-token value visitors don't recompute it.
3398
- currentDeclIsKnownProperty = knownProperties.has(declPropertyName);
3399
- // Position `lastTokenEndForComments` just past the `:` so a magic comment before the url() is found.
3400
- let colonPos = decl.nameEnd;
3575
+ // Position `lastTokenEndForComments` just past the `:` so a magic comment before a url() is found (every mode).
3576
+ let colonPos = A.nameEnd(decl);
3401
3577
  while (
3402
3578
  colonPos < source.length &&
3403
3579
  source.charCodeAt(colonPos) !== CC_COLON
@@ -3405,49 +3581,92 @@ class CssParser extends Parser {
3405
3581
  colonPos++;
3406
3582
  }
3407
3583
  lastTokenEndForComments = colonPos + 1;
3584
+ currentDeclIsKnownProperty = false;
3585
+ currentDeclComposesSkip = false;
3586
+ // Property-name analysis and value exports are CSS-Modules-only; a plain
3587
+ // stylesheet's declarations need no per-decl name slice / known-property lookup.
3588
+ if (!isModules) return;
3589
+ const nameStart = A.nameStart(decl);
3590
+ const nameEnd = A.nameEnd(decl);
3591
+ // Range-based name analysis — the common declaration never
3592
+ // slices its name out of the source.
3593
+ /** @type {string | undefined} */
3594
+ let declName;
3595
+ /** @type {string | undefined} */
3596
+ let declPropertyName;
3597
+ if (source.charCodeAt(nameStart) === CC_HYPHEN_MINUS) {
3598
+ // Vendor-prefixed or custom property — rare, take the string path.
3599
+ declName = A.name(decl);
3600
+ declPropertyName = toLowerCaseIfNeeded(
3601
+ declName.replace(VENDOR_PREFIX, "")
3602
+ );
3603
+ currentDeclIsKnownProperty = knownProperties.has(declPropertyName);
3604
+ } else {
3605
+ declPropertyName = knownPropertyForRange(
3606
+ knownPropertyIndex,
3607
+ source,
3608
+ nameStart,
3609
+ nameEnd
3610
+ );
3611
+ // Cache the known-property flag so the per-token value visitors don't recompute it.
3612
+ currentDeclIsKnownProperty = declPropertyName !== undefined;
3613
+ }
3408
3614
  const effectiveLocalMode = isEffectivelyLocal();
3409
3615
  // `composes:` with a local anchor: its strip-dep covers the whole declaration, so suppress the value's local/global/dashed/ICSS rewrites.
3410
3616
  currentDeclComposesSkip =
3411
3617
  currentRule.hasLocalAnchor &&
3412
- COMPOSES_PROPERTY.test(declPropertyName);
3618
+ (declPropertyName !== undefined
3619
+ ? COMPOSES_PROPERTY.test(declPropertyName)
3620
+ : rangeEqualsLowerCase(source, nameStart, nameEnd, "composes") ||
3621
+ rangeEqualsLowerCase(source, nameStart, nameEnd, "compose-with"));
3413
3622
  if (currentDeclComposesSkip) emitComposesWithAnchor(decl);
3414
3623
  const skipForComposes = currentDeclComposesSkip;
3415
3624
  // Known-property value localization (`animation-name: foo` exports `foo`).
3416
- if (isModules && effectiveLocalMode && currentDeclIsKnownProperty) {
3417
- emitKnownPropertyExports(decl, declPropertyName);
3625
+ if (effectiveLocalMode && currentDeclIsKnownProperty) {
3626
+ emitKnownPropertyExports(
3627
+ decl,
3628
+ /** @type {string} */ (declPropertyName)
3629
+ );
3418
3630
  }
3419
3631
  // Dashed-ident (custom-property) export of the property name; the value's dashed idents are scoped by the Ident / Function visitors (top-level only for unknown properties).
3420
3632
  if (
3421
3633
  this.options.dashedIdents &&
3422
- isModules &&
3423
3634
  effectiveLocalMode &&
3424
3635
  !skipForComposes
3425
3636
  ) {
3426
- if (isDashedIdentifier(decl.name)) {
3427
- emitDashedIdentExport(decl.nameStart, decl.nameEnd);
3637
+ // Only the `-`-prefixed path can carry a dashed ident.
3638
+ if (declName !== undefined && isDashedIdentifier(declName)) {
3639
+ emitDashedIdentExport(nameStart, nameEnd);
3428
3640
  }
3429
3641
  dashed.active = true;
3430
3642
  dashed.emit = !currentDeclIsKnownProperty;
3431
3643
  }
3432
- // ICSS-symbol rewrite (`color: foo` when `foo` is `@value`-defined), skipping known properties, the composes anchor, and dashed idents (handled above).
3644
+ // ICSS-symbol rewrite (`color: foo` when `foo` is `@value`-defined), skipping known properties, the composes anchor, and dashed idents (handled above). Nothing to rewrite without definitions — don't slice the name.
3433
3645
  if (
3434
- isModules &&
3435
3646
  !skipForComposes &&
3436
3647
  !currentDeclIsKnownProperty &&
3437
- !(dashed.active && isDashedIdentifier(decl.name)) &&
3438
- icssDefinitions.has(decl.name)
3648
+ icssDefinitions.size !== 0
3439
3649
  ) {
3440
- emitICSSSymbol(decl.name, decl.nameStart, decl.nameEnd);
3650
+ if (declName === undefined) {
3651
+ declName = source.slice(nameStart, nameEnd);
3652
+ }
3653
+ if (
3654
+ !(dashed.active && isDashedIdentifier(declName)) &&
3655
+ icssDefinitions.has(declName)
3656
+ ) {
3657
+ emitICSSSymbol(declName, nameStart, nameEnd);
3658
+ }
3441
3659
  }
3442
3660
  },
3443
3661
  // Value-level visitors decide handling from the enclosing node via `urlActive()` / `localGlobalActive()` / `icssActive()`.
3444
- [NodeType.Url]: (node) => {
3662
+ [NodeType.Url]: (path) => {
3663
+ const node = path.node;
3445
3664
  const url = /** @type {UrlToken} */ (node);
3446
3665
  if (!urlActive()) return;
3447
3666
  // Skip bare url-tokens for a known property in CSS-Modules local mode.
3448
3667
  if (
3449
3668
  currentStructural &&
3450
- currentStructural.type === NodeType.Declaration &&
3669
+ A.type(currentStructural) === NodeType.Declaration &&
3451
3670
  isModules &&
3452
3671
  currentDeclIsKnownProperty &&
3453
3672
  isEffectivelyLocal()
@@ -3456,15 +3675,15 @@ class CssParser extends Parser {
3456
3675
  }
3457
3676
  if (
3458
3677
  webpackIgnored(
3459
- [lastTokenEndForComments, node.end],
3678
+ [lastTokenEndForComments, A.end(node)],
3460
3679
  lastTokenEndForComments,
3461
- node.end
3680
+ A.end(node)
3462
3681
  )
3463
3682
  ) {
3464
3683
  return;
3465
3684
  }
3466
3685
  let value = normalizeUrl(
3467
- input.slice(url.contentStart, url.contentEnd),
3686
+ input.slice(A.contentStart(url), A.contentEnd(url)),
3468
3687
  false
3469
3688
  );
3470
3689
  // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
@@ -3485,55 +3704,64 @@ class CssParser extends Parser {
3485
3704
  state,
3486
3705
  `'@value' identifier '${value}' was imported from another module and cannot be used inside 'url()' — only locally defined values are supported here`,
3487
3706
  locConverter,
3488
- node.start,
3489
- node.end
3707
+ A.start(node),
3708
+ A.end(node)
3490
3709
  );
3491
3710
  return;
3492
3711
  }
3493
3712
  }
3494
3713
  }
3495
- const dep = new CssUrlDependency(value, [node.start, node.end], "url");
3496
- setDepLoc(dep, node.start, node.end);
3714
+ const dep = new CssUrlDependency(
3715
+ value,
3716
+ [A.start(node), A.end(node)],
3717
+ "url"
3718
+ );
3719
+ setDepLoc(dep, A.start(node), A.end(node));
3497
3720
  module.addDependency(dep);
3498
3721
  module.addCodeGenerationDependency(dep);
3499
3722
  },
3500
- [NodeType.Comma](node) {
3501
- if (urlActive()) lastTokenEndForComments = node.start;
3723
+ [NodeType.Comma](path) {
3724
+ const node = path.node;
3725
+ if (urlActive()) lastTokenEndForComments = A.start(node);
3502
3726
  },
3503
3727
  [NodeType.Function]: {
3504
- enter: (node) => {
3728
+ enter: (path) => {
3729
+ const node = path.node;
3505
3730
  const fn = /** @type {FunctionNode} */ (node);
3506
- const fnameRaw = fn.unescapedName;
3507
- const fname = fnameRaw.toLowerCase();
3508
- if (urlActive()) emitUrlFunction(fn, fname);
3509
- if (
3510
- localGlobalActive() &&
3511
- (fname === "local" || fname === "global")
3512
- ) {
3513
- processLocalOrGlobalFunction(fn, fname === "local" ? 1 : 2);
3731
+ const fnameRaw = A.unescapedName(fn);
3732
+ // `equalsLowerCase` keeps this visitor free of a per-function
3733
+ // lowercased copy — functions are the densest value nodes.
3734
+ const isLocalFn = equalsLowerCase(fnameRaw, "local");
3735
+ const isGlobalFn = !isLocalFn && equalsLowerCase(fnameRaw, "global");
3736
+ if (urlActive()) emitUrlFunction(fn, fnameRaw);
3737
+ if (localGlobalActive() && (isLocalFn || isGlobalFn)) {
3738
+ processLocalOrGlobalFunction(fn, isLocalFn ? 1 : 2);
3514
3739
  }
3515
3740
  if (
3516
3741
  icssActive() &&
3517
- fname !== "local" &&
3518
- fname !== "global" &&
3742
+ !isLocalFn &&
3743
+ !isGlobalFn &&
3519
3744
  !(dashed.active && isDashedIdentifier(fnameRaw)) &&
3520
3745
  icssDefinitions.has(fnameRaw)
3521
3746
  ) {
3522
- emitICSSSymbol(fnameRaw, fn.nameStart, fn.nameEnd);
3747
+ emitICSSSymbol(fnameRaw, A.nameStart(fn), A.nameEnd(fn));
3523
3748
  }
3524
3749
  // Dashed-ident scoping: handle this function, then set the child nesting level's state for the walk.
3525
3750
  dashed.push();
3526
3751
  if (dashed.active) {
3527
- if (fname === "local" || fname === "global") {
3752
+ if (isLocalFn || isGlobalFn) {
3528
3753
  // `local()` / `global()` dashed args go through the ICSS path above, not here.
3529
3754
  dashed.active = false;
3530
- } else if (fname === "var" || fname === "style") {
3755
+ } else if (
3756
+ equalsLowerCase(fnameRaw, "var") ||
3757
+ equalsLowerCase(fnameRaw, "style")
3758
+ ) {
3531
3759
  // `var(--foo, …)` / `style(--foo, …)`: emit the first ident; the fallback doesn't self-emit.
3532
3760
  processDashedIdentInVarFunction(fn);
3533
3761
  dashed.emit = false;
3534
- } else if (dashed.emit && isDashedIdentifier(fn.name)) {
3762
+ } else if (dashed.emit && isDashedIdentifier(A.name(fn))) {
3535
3763
  // Custom-function call `--my-func(args)` — the name is the exported dashed-ident.
3536
- emitDashedIdentExport(fn.nameStart, fn.nameEnd);
3764
+ emitDashedIdentExport(A.nameStart(fn), A.nameEnd(fn));
3537
3765
  }
3538
3766
  }
3539
3767
  },
@@ -3541,61 +3769,83 @@ class CssParser extends Parser {
3541
3769
  dashed.pop();
3542
3770
  }
3543
3771
  },
3544
- [NodeType.Ident](node, parent) {
3545
- const identValue = /** @type {Token} */ (node).value;
3546
- if (dashed.active && isDashedIdentifier(identValue)) {
3772
+ [NodeType.Ident](path) {
3773
+ const node = path.node;
3774
+ // Fast exit before slicing the ident value: outside dashed-ident scoping
3775
+ // and ICSS context (any non-CSS-Modules stylesheet) a bare ident carries
3776
+ // no work, and idents are the most common node, so skipping the per-ident
3777
+ // `value` slice matters. With no `@value`/`:import` definitions the ICSS
3778
+ // probe can never hit, so it doesn't warrant the slice either.
3779
+ const dashedActive = dashed.active;
3780
+ const icss = icssActive() && icssDefinitions.size !== 0;
3781
+ if (!dashedActive && !icss) return;
3782
+ const identValue = A.value(node);
3783
+ if (dashedActive && isDashedIdentifier(identValue)) {
3547
3784
  // Dashed idents are scoped here, never `@value` ICSS-rewritten.
3548
3785
  if (!dashed.emit) return;
3549
3786
  // Resolve the `--foo from "./x.css"` / `--foo from global` import suffix via sibling lookahead.
3550
- const siblings = childrenOf(parent);
3787
+ const siblings = childrenOf(path.parent);
3551
3788
  const i = siblings ? siblings.indexOf(node) : -1;
3552
3789
  if (siblings && i !== -1) {
3553
3790
  const j = nextNonWhitespace(siblings, i + 1);
3554
3791
  if (
3555
3792
  j < siblings.length &&
3556
- siblings[j].type === NodeType.Ident &&
3557
- equalsLowerCase(/** @type {Token} */ (siblings[j]).value, "from")
3793
+ A.type(siblings[j]) === NodeType.Ident &&
3794
+ rangeEqualsLowerCase(
3795
+ input,
3796
+ A.start(siblings[j]),
3797
+ A.end(siblings[j]),
3798
+ "from"
3799
+ )
3558
3800
  ) {
3559
3801
  const fromIdent = siblings[j];
3560
3802
  const sourceNode = siblings[nextNonWhitespace(siblings, j + 1)];
3561
3803
  if (
3562
3804
  sourceNode &&
3563
- sourceNode.type === NodeType.Ident &&
3564
- /** @type {Token} */ (sourceNode).value === "global"
3805
+ A.type(sourceNode) === NodeType.Ident &&
3806
+ rangeEquals(
3807
+ input,
3808
+ A.start(sourceNode),
3809
+ A.end(sourceNode),
3810
+ "global"
3811
+ )
3565
3812
  ) {
3566
- emitDashedIdentFromGlobal(node.end, sourceNode.end);
3813
+ emitDashedIdentFromGlobal(A.end(node), A.end(sourceNode));
3567
3814
  return;
3568
3815
  }
3569
- if (sourceNode && sourceNode.type === NodeType.String) {
3816
+ if (sourceNode && A.type(sourceNode) === NodeType.String) {
3570
3817
  emitDashedIdentImport(
3571
- node.start,
3572
- node.end,
3573
- fromIdent.start,
3574
- sourceNode.end,
3575
- input.slice(sourceNode.start + 1, sourceNode.end - 1)
3818
+ A.start(node),
3819
+ A.end(node),
3820
+ A.start(fromIdent),
3821
+ A.end(sourceNode),
3822
+ input.slice(A.start(sourceNode) + 1, A.end(sourceNode) - 1)
3576
3823
  );
3577
3824
  return;
3578
3825
  }
3579
3826
  }
3580
3827
  }
3581
- emitDashedIdentExport(node.start, node.end);
3828
+ emitDashedIdentExport(A.start(node), A.end(node));
3582
3829
  return;
3583
3830
  }
3584
- if (!icssActive()) return;
3831
+ if (!icss) return;
3585
3832
  if (icssDefinitions.has(identValue)) {
3586
- emitICSSSymbol(identValue, node.start, node.end);
3833
+ emitICSSSymbol(identValue, A.start(node), A.end(node));
3587
3834
  }
3588
3835
  }
3589
3836
  };
3590
3837
  // `as` selects the top-level production (§5.3): a `style` attribute is a
3591
- // block's contents, everything else a full stylesheet (the default).
3592
- new SourceProcessor()
3838
+ // block's contents, everything else a full stylesheet (the default). `as`
3839
+ // and `skip` are per-parse config on the processor; only `locConverter`
3840
+ // (per-source) is passed to `process`. One skip set: selector prelude +
3841
+ // unread value leaves (non-modules only; CSS Modules needs its selectors
3842
+ // and ICSS-captured values).
3843
+ new SourceProcessor({
3844
+ as: /** @type {"stylesheet" | "block-contents"} */ (this.options.as),
3845
+ skip: isModules ? undefined : SKIP_NON_MODULES
3846
+ })
3593
3847
  .use(/** @type {VisitorMap} */ (visitors))
3594
- .process(source, {
3595
- locConverter,
3596
- comment,
3597
- as: /** @type {"stylesheet" | "block-contents"} */ (this.options.as)
3598
- });
3848
+ .process(source, { locConverter });
3599
3849
 
3600
3850
  /** @type {BuildInfo} */
3601
3851
  (module.buildInfo).strict = true;