source: frontend/node_modules/webpack/lib/css/CssParser.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

  • Property mode set to 100644
File size: 86.2 KB
Line 
1/*
2 MIT License http://www.opensource.org/licenses/mit-license.php
3 Author Tobias Koppers @sokra
4*/
5
6"use strict";
7
8const path = require("path");
9const vm = require("vm");
10const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
11const Parser = require("../Parser");
12const ConstDependency = require("../dependencies/ConstDependency");
13const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
14const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
15const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
16const CssImportDependency = require("../dependencies/CssImportDependency");
17const CssUrlDependency = require("../dependencies/CssUrlDependency");
18const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
19const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
20const ModuleDependencyWarning = require("../errors/ModuleDependencyWarning");
21const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
22const WebpackError = require("../errors/WebpackError");
23const LocConverter = require("../util/LocConverter");
24const binarySearchBounds = require("../util/binarySearchBounds");
25const { parseResource } = require("../util/identifier");
26const {
27 createMagicCommentContext,
28 webpackCommentRegExp
29} = require("../util/magicComment");
30const topologicalSort = require("../util/topologicalSort");
31const walkCssTokens = require("./walkCssTokens");
32
33/** @typedef {import("../Module").BuildInfo} BuildInfo */
34/** @typedef {import("../Module").BuildMeta} BuildMeta */
35/** @typedef {import("../Parser").ParserState} ParserState */
36/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
37/** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */
38/** @typedef {import("../../declarations/WebpackOptions").CssAutoOrModuleParserOptions} CssAutoOrModuleParserOptions */
39/** @typedef {import("../../declarations/WebpackOptions").CssModuleParserOptions} CssModuleParserOptions */
40/** @typedef {import("./CssModule")} CssModule */
41
42/** @typedef {[number, number]} Range */
43/** @typedef {{ line: number, column: number }} Position */
44/** @typedef {{ value: string, range: Range, loc: { start: Position, end: Position } }} Comment */
45
46const CC_COLON = ":".charCodeAt(0);
47const CC_SEMICOLON = ";".charCodeAt(0);
48const CC_COMMA = ",".charCodeAt(0);
49const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
50const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
51const CC_LOWER_F = "f".charCodeAt(0);
52const CC_UPPER_F = "F".charCodeAt(0);
53const CC_RIGHT_CURLY = "}".charCodeAt(0);
54const CC_HYPHEN_MINUS = "-".charCodeAt(0);
55const CC_TILDE = "~".charCodeAt(0);
56const CC_EQUAL = "=".charCodeAt(0);
57const CC_FULL_STOP = ".".charCodeAt(0);
58const CC_EXCLAMATION = "!".charCodeAt(0);
59const CC_AMPERSAND = "&".charCodeAt(0);
60
61// https://www.w3.org/TR/css-syntax-3/#newline
62// We don't have `preprocessing` stage, so we need specify all of them
63const STRING_MULTILINE = /\\[\n\r\f]/g;
64// https://www.w3.org/TR/css-syntax-3/#whitespace
65const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
66const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
67const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
68const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
69const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
70const IS_MODULES = /\.modules?\.[^.]+$/i;
71const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;
72
73/**
74 * Returns matches.
75 * @param {RegExp} regexp a regexp
76 * @param {string} str a string
77 * @returns {RegExpExecArray[]} matches
78 */
79const matchAll = (regexp, str) => {
80 /** @type {RegExpExecArray[]} */
81 const result = [];
82
83 /** @type {null | RegExpExecArray} */
84 let match;
85
86 // Use a while loop with exec() to find all matches
87 while ((match = regexp.exec(str)) !== null) {
88 result.push(match);
89 }
90 // Return an array to be easily iterable (note: a true spec-compliant polyfill
91 // returns an iterator object, but an array spread often suffices for basic use)
92 return result;
93};
94
95/**
96 * Returns normalized url.
97 * @param {string} str url string
98 * @param {boolean} isString is url wrapped in quotes
99 * @returns {string} normalized url
100 */
101const normalizeUrl = (str, isString) => {
102 // Remove extra spaces and newlines:
103 // `url("im\
104 // g.png")`
105 if (isString) {
106 str = str.replace(STRING_MULTILINE, "");
107 }
108
109 str = str
110 // Remove unnecessary spaces from `url(" img.png ")`
111 .replace(TRIM_WHITE_SPACES, "")
112 // Unescape
113 .replace(UNESCAPE, (match) => {
114 if (match.length > 2) {
115 return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
116 }
117 return match[1];
118 });
119
120 if (/^data:/i.test(str)) {
121 return str;
122 }
123
124 if (str.includes("%")) {
125 // Convert `url('%2E/img.png')` -> `url('./img.png')`
126 try {
127 str = decodeURIComponent(str);
128 } catch (_err) {
129 // Ignore
130 }
131 }
132
133 return str;
134};
135
136const { escapeIdentifier, unescapeIdentifier } = walkCssTokens;
137
138/**
139 * A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
140 * The <custom-property-name> production corresponds to this:
141 * it’s defined as any <dashed-ident> (a valid identifier that starts with two dashes),
142 * except -- itself, which is reserved for future use by CSS.
143 * @param {string} identifier identifier
144 * @returns {boolean} true when identifier is dashed, otherwise false
145 */
146const isDashedIdentifier = (identifier) =>
147 identifier.startsWith("--") && identifier.length >= 3;
148
149/** @type {Record<string, number>} */
150const PREDEFINED_COUNTER_STYLES = {
151 decimal: 1,
152 "decimal-leading-zero": 1,
153 "arabic-indic": 1,
154 armenian: 1,
155 "upper-armenian": 1,
156 "lower-armenian": 1,
157 bengali: 1,
158 cambodian: 1,
159 khmer: 1,
160 "cjk-decimal": 1,
161 devanagari: 1,
162 georgian: 1,
163 gujarati: 1,
164 /* cspell:disable-next-line */
165 gurmukhi: 1,
166 hebrew: 1,
167 kannada: 1,
168 lao: 1,
169 malayalam: 1,
170 mongolian: 1,
171 myanmar: 1,
172 oriya: 1,
173 persian: 1,
174 "lower-roman": 1,
175 "upper-roman": 1,
176 tamil: 1,
177 telugu: 1,
178 thai: 1,
179 tibetan: 1,
180
181 "lower-alpha": 1,
182 "lower-latin": 1,
183 "upper-alpha": 1,
184 "upper-latin": 1,
185 "lower-greek": 1,
186 hiragana: 1,
187 /* cspell:disable-next-line */
188 "hiragana-iroha": 1,
189 katakana: 1,
190 /* cspell:disable-next-line */
191 "katakana-iroha": 1,
192
193 disc: 1,
194 circle: 1,
195 square: 1,
196 "disclosure-open": 1,
197 "disclosure-closed": 1,
198
199 "cjk-earthly-branch": 1,
200 "cjk-heavenly-stem": 1,
201
202 "japanese-informal": 1,
203 "japanese-formal": 1,
204
205 "korean-hangul-formal": 1,
206 /* cspell:disable-next-line */
207 "korean-hanja-informal": 1,
208 /* cspell:disable-next-line */
209 "korean-hanja-formal": 1,
210
211 "simp-chinese-informal": 1,
212 "simp-chinese-formal": 1,
213 "trad-chinese-informal": 1,
214 "trad-chinese-formal": 1,
215 "cjk-ideographic": 1,
216
217 "ethiopic-numeric": 1
218};
219
220/** @type {Record<string, number>} */
221const GLOBAL_VALUES = {
222 // Global values
223 initial: Infinity,
224 inherit: Infinity,
225 unset: Infinity,
226 revert: Infinity,
227 "revert-layer": Infinity
228};
229
230/** @type {Record<string, number>} */
231const GRID_AREA_OR_COLUMN_OR_ROW = {
232 auto: Infinity,
233 span: Infinity,
234 ...GLOBAL_VALUES
235};
236
237/** @type {Record<string, number>} */
238const GRID_AUTO_COLUMNS_OR_ROW = {
239 "min-content": Infinity,
240 "max-content": Infinity,
241 auto: Infinity,
242 ...GLOBAL_VALUES
243};
244
245/** @type {Record<string, number>} */
246const GRID_AUTO_FLOW = {
247 row: 1,
248 column: 1,
249 dense: 1,
250 ...GLOBAL_VALUES
251};
252
253/** @type {Record<string, number>} */
254const GRID_TEMPLATE_AREAS = {
255 // Special
256 none: 1,
257 ...GLOBAL_VALUES
258};
259
260/** @type {Record<string, number>} */
261const GRID_TEMPLATE_COLUMNS_OR_ROWS = {
262 none: 1,
263 subgrid: 1,
264 masonry: 1,
265 "max-content": Infinity,
266 "min-content": Infinity,
267 auto: Infinity,
268 ...GLOBAL_VALUES
269};
270
271/** @type {Record<string, number>} */
272const GRID_TEMPLATE = {
273 ...GRID_TEMPLATE_AREAS,
274 ...GRID_TEMPLATE_COLUMNS_OR_ROWS
275};
276
277/** @type {Record<string, number>} */
278const GRID = {
279 "auto-flow": 1,
280 dense: 1,
281 ...GRID_AUTO_COLUMNS_OR_ROW,
282 ...GRID_AUTO_FLOW,
283 ...GRID_TEMPLATE_AREAS,
284 ...GRID_TEMPLATE_COLUMNS_OR_ROWS
285};
286
287/**
288 * Gets known properties.
289 * @param {{ animation?: boolean, container?: boolean, customIdents?: boolean, grid?: boolean }=} options options
290 * @returns {Map<string, Record<string, number>>} list of known properties
291 */
292const getKnownProperties = (options = {}) => {
293 /** @type {Map<string, Record<string, number>>} */
294 const knownProperties = new Map();
295
296 if (options.animation) {
297 knownProperties.set("animation", {
298 // animation-direction
299 normal: 1,
300 reverse: 1,
301 alternate: 1,
302 "alternate-reverse": 1,
303 // animation-fill-mode
304 forwards: 1,
305 backwards: 1,
306 both: 1,
307 // animation-iteration-count
308 infinite: 1,
309 // animation-play-state
310 paused: 1,
311 running: 1,
312 // animation-timing-function
313 ease: 1,
314 "ease-in": 1,
315 "ease-out": 1,
316 "ease-in-out": 1,
317 linear: 1,
318 "step-end": 1,
319 "step-start": 1,
320 // Special
321 none: Infinity, // No matter how many times you write none, it will never be an animation name
322 ...GLOBAL_VALUES
323 });
324 knownProperties.set("animation-name", {
325 // Special
326 none: Infinity, // No matter how many times you write none, it will never be an animation name
327 ...GLOBAL_VALUES
328 });
329 }
330
331 if (options.container) {
332 knownProperties.set("container", {
333 // container-type
334 normal: 1,
335 size: 1,
336 "inline-size": 1,
337 "scroll-state": 1,
338 // Special
339 none: Infinity,
340 ...GLOBAL_VALUES
341 });
342 knownProperties.set("container-name", {
343 // Special
344 none: Infinity,
345 ...GLOBAL_VALUES
346 });
347 }
348
349 if (options.customIdents) {
350 knownProperties.set("list-style", {
351 // list-style-position
352 inside: 1,
353 outside: 1,
354 // list-style-type
355 ...PREDEFINED_COUNTER_STYLES,
356 // Special
357 none: Infinity,
358 ...GLOBAL_VALUES
359 });
360 knownProperties.set("list-style-type", {
361 // list-style-type
362 ...PREDEFINED_COUNTER_STYLES,
363 // Special
364 none: Infinity,
365 ...GLOBAL_VALUES
366 });
367 knownProperties.set("system", {
368 cyclic: 1,
369 numeric: 1,
370 alphabetic: 1,
371 symbolic: 1,
372 additive: 1,
373 fixed: 1,
374 extends: 1,
375 ...PREDEFINED_COUNTER_STYLES
376 });
377 knownProperties.set("fallback", {
378 ...PREDEFINED_COUNTER_STYLES
379 });
380 knownProperties.set("speak-as", {
381 auto: 1,
382 bullets: 1,
383 numbers: 1,
384 words: 1,
385 "spell-out": 1,
386 ...PREDEFINED_COUNTER_STYLES
387 });
388 }
389
390 if (options.grid) {
391 knownProperties.set("grid", GRID);
392 knownProperties.set("grid-area", GRID_AREA_OR_COLUMN_OR_ROW);
393 knownProperties.set("grid-column", GRID_AREA_OR_COLUMN_OR_ROW);
394 knownProperties.set("grid-column-end", GRID_AREA_OR_COLUMN_OR_ROW);
395 knownProperties.set("grid-column-start", GRID_AREA_OR_COLUMN_OR_ROW);
396 knownProperties.set("grid-row", GRID_AREA_OR_COLUMN_OR_ROW);
397 knownProperties.set("grid-row-end", GRID_AREA_OR_COLUMN_OR_ROW);
398 knownProperties.set("grid-row-start", GRID_AREA_OR_COLUMN_OR_ROW);
399 knownProperties.set("grid-template", GRID_TEMPLATE);
400 knownProperties.set("grid-template-areas", GRID_TEMPLATE_AREAS);
401 knownProperties.set("grid-template-columns", GRID_TEMPLATE_COLUMNS_OR_ROWS);
402 knownProperties.set("grid-template-rows", GRID_TEMPLATE_COLUMNS_OR_ROWS);
403 }
404
405 return knownProperties;
406};
407
408const EMPTY_COMMENT_OPTIONS = {
409 options: null,
410 errors: null
411};
412
413const CSS_MODE_TOP_LEVEL = 0;
414const CSS_MODE_IN_BLOCK = 1;
415
416const LOCAL_MODE = 0;
417const GLOBAL_MODE = 1;
418
419const eatUntilSemi = walkCssTokens.eatUntil(";");
420const eatUntilLeftCurly = walkCssTokens.eatUntil("{");
421
422/**
423 * Defines the css parser own options type used by this module.
424 * @typedef {object} CssParserOwnOptions
425 * @property {("pure" | "global" | "local" | "auto")=} defaultMode default mode
426 */
427
428/** @typedef {CssAutoOrModuleParserOptions & CssParserOwnOptions} CssParserOptions */
429
430class CssParser extends Parser {
431 /**
432 * Creates an instance of CssParser.
433 * @param {CssParserOptions=} options options
434 */
435 constructor(options = {}) {
436 super();
437 this.defaultMode =
438 typeof options.defaultMode !== "undefined" ? options.defaultMode : "pure";
439 this.options = {
440 url: true,
441 import: true,
442 namedExports: true,
443 animation: true,
444 container: true,
445 customIdents: true,
446 dashedIdents: true,
447 function: true,
448 grid: true,
449 ...options
450 };
451 /** @type {Comment[] | undefined} */
452 this.comments = undefined;
453 this.magicCommentContext = createMagicCommentContext();
454 }
455
456 /**
457 * Processes the provided state.
458 * @param {ParserState} state parser state
459 * @param {string} message warning message
460 * @param {LocConverter} locConverter location converter
461 * @param {number} start start offset
462 * @param {number} end end offset
463 */
464 _emitWarning(state, message, locConverter, start, end) {
465 const { line: sl, column: sc } = locConverter.get(start);
466 const { line: el, column: ec } = locConverter.get(end);
467
468 state.current.addWarning(
469 new ModuleDependencyWarning(state.module, new WebpackError(message), {
470 start: { line: sl, column: sc },
471 end: { line: el, column: ec }
472 })
473 );
474 }
475
476 /**
477 * Emits a build error for the provided range.
478 * @param {ParserState} state parser state
479 * @param {string} message error message
480 * @param {LocConverter} locConverter location converter
481 * @param {number} start start offset
482 * @param {number} end end offset
483 */
484 _emitError(state, message, locConverter, start, end) {
485 const { line: sl, column: sc } = locConverter.get(start);
486 const { line: el, column: ec } = locConverter.get(end);
487
488 const err = new WebpackError(message);
489 err.module = state.module;
490 err.loc = {
491 start: { line: sl, column: sc },
492 end: { line: el, column: ec }
493 };
494 state.module.addError(err);
495 }
496
497 /**
498 * Parses the provided source and updates the parser state.
499 * @param {string | Buffer | PreparsedAst} source the source to parse
500 * @param {ParserState} state the parser state
501 * @returns {ParserState} the parser state
502 */
503 parse(source, state) {
504 if (Buffer.isBuffer(source)) {
505 source = source.toString("utf8");
506 } else if (typeof source === "object") {
507 throw new Error("webpackAst is unexpected for the CssParser");
508 }
509 if (source[0] === "\uFEFF") {
510 source = source.slice(1);
511 }
512
513 const unescapeIdentifierCached = unescapeIdentifier.bindCache(
514 state.compilation.compiler.root
515 );
516
517 let mode = this.defaultMode;
518
519 const module = state.module;
520
521 if (
522 mode === "auto" &&
523 module.type === CSS_MODULE_TYPE_AUTO &&
524 IS_MODULES.test(
525 parseResource(/** @type {string} */ (module.getResource())).path
526 )
527 ) {
528 mode = "local";
529 }
530
531 const isModules = mode === "global" || mode === "local";
532
533 const parsedModuleResource = parseResource(
534 /** @type {string} */ (module.getResource())
535 );
536
537 /**
538 * Check whether a request points back to the current module
539 * (e.g. `composes: foo from "./self.module.css"` inside `self.module.css`).
540 * Only relative requests are checked — aliases / package / absolute requests
541 * fall through to the normal import path. Requests with a `?query` or
542 * `#fragment` are only treated as self when the parent module's resource
543 * has the same query/fragment, since `NormalModuleFactory` keys modules
544 * on the full resource string.
545 * @param {string} request request string from `from "<request>"`
546 * @returns {boolean} true if request resolves to the current module
547 */
548 const isSelfReferenceRequest = (request) => {
549 if (!/^\.{1,2}\//.test(request)) return false;
550 if (!module.context) return false;
551 const parsedRequest = parseResource(request);
552 if (parsedRequest.query !== parsedModuleResource.query) return false;
553 if (parsedRequest.fragment !== parsedModuleResource.fragment) {
554 return false;
555 }
556 try {
557 return (
558 path.resolve(module.context, parsedRequest.path) ===
559 parsedModuleResource.path
560 );
561 } catch (_err) {
562 return false;
563 }
564 };
565
566 const knownProperties = getKnownProperties({
567 animation: this.options.animation,
568 container: this.options.container,
569 customIdents: this.options.customIdents,
570 grid: this.options.grid
571 });
572
573 /** @type {BuildMeta} */
574 (module.buildMeta).isCssModule = isModules;
575 if (/** @type {CssModule} */ (module).exportType === "style") {
576 /** @type {BuildMeta} */
577 (module.buildMeta).needIdInConcatenation = true;
578 }
579
580 const locConverter = new LocConverter(source);
581
582 /** @type {number} */
583 let scope = CSS_MODE_TOP_LEVEL;
584 /** @type {boolean} */
585 let allowImportAtRule = true;
586 /** @type {[string, number, number, boolean?][]} */
587 const balanced = [];
588 let lastTokenEndForComments = 0;
589
590 /** @type {boolean} */
591 let isNextRulePrelude = isModules;
592 /** @type {number} */
593 let blockNestingLevel = 0;
594 /** @type {0 | 1 | undefined} */
595 let modeData;
596 /** @type {number} */
597 let counter = 0;
598
599 /** @type {string[]} */
600 let lastLocalIdentifiers = [];
601
602 const pureMode = isModules && Boolean(this.options.pure);
603 /** @type {boolean} */
604 let currentSelectorHasLocal = false;
605 /** Whether any comma-separated selector in the current rule's prelude was impure. */
606 let currentRuleHasImpureSelector = false;
607 /** Offset just after the previous `}` (or 0) — used as the prelude start. */
608 let currentRulePreludeStart = 0;
609 /** Pure-mode flags (only meaningful when `pureMode` is true). */
610 let pureNoCheck = false;
611 let pureIgnorePending = false;
612 let nextBlockChildrenSkip = false;
613 let nextBlockTreatAsLeaf = false;
614 let seenTopLevelRule = false;
615 // True after an at-rule keyword and before the next `{` or `;`. Used so
616 // identifiers inside the at-rule prelude (e.g. `min-width` inside
617 // `@media (min-width: 768px)`) don't get counted as declarations.
618 let inAtRulePrelude = false;
619 /**
620 * One entry per open block. `skipOwn` skips this rule's own check (set
621 * when the parent passed down `skipChildren`, e.g. `from`/`to` inside
622 * `@keyframes`). `skipChildren` is propagated to descendants. `ignored`
623 * is per-rule only (PCSL semantics for `cssmodules-pure-ignore`).
624 * `ancestorHadLocal` lets nested rules inherit purity from a
625 * local-bearing ancestor.
626 * @type {{
627 * ignored: boolean,
628 * skipOwn: boolean,
629 * skipChildren: boolean,
630 * treatAsLeaf: boolean,
631 * ancestorHadLocal: boolean,
632 * impure: boolean,
633 * hasDirectDecl: boolean,
634 * hasNestedBlock: boolean,
635 * isRulePrelude: boolean,
636 * preludeStart: number,
637 * preludeEnd: number,
638 * }[]}
639 */
640 const pureBlockStack = [];
641
642 const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
643 const PURE_NO_CHECK_RE = /^\s*cssmodules-pure-no-check(?:\s|$)/;
644
645 /**
646 * @returns {(typeof pureBlockStack)[number] | undefined} top of stack
647 */
648 const pureTop = () => pureBlockStack[pureBlockStack.length - 1];
649
650 /**
651 * Was the parent rule pure overall (its own selectors pure or any
652 * ancestor pure)? Used both for ancestor-inheritance and `&`-resolution.
653 * @returns {boolean} true if any ancestor (self inclusive) provided a local
654 */
655 const parentEffectivePure = () => {
656 const top = pureTop();
657 return top ? top.ancestorHadLocal : false;
658 };
659
660 /**
661 * Marks the just-finished comma-separated selector (or whole prelude
662 * at `{`) as impure if it lacks a local and no ancestor compensates.
663 */
664 const finalizeSelector = () => {
665 if (!currentSelectorHasLocal && !parentEffectivePure()) {
666 currentRuleHasImpureSelector = true;
667 }
668 currentSelectorHasLocal = false;
669 };
670
671 /**
672 * Reports a pure-mode violation covering the entire rule prelude.
673 * @param {number} start prelude start offset
674 * @param {number} end prelude end offset (`{` position)
675 */
676 const reportPureRule = (start, end) => {
677 const slice = source.slice(start, end);
678 const lead = /** @type {RegExpExecArray} */ (
679 /^(?:\s|\/\*[\s\S]*?\*\/)*/.exec(slice)
680 )[0].length;
681 const trail = /** @type {RegExpExecArray} */ (/\s*$/.exec(slice))[0]
682 .length;
683 const from = start + lead;
684 const to = end - trail;
685 if (to <= from) return;
686 this._emitError(
687 state,
688 `Selector "${source.slice(from, to)}" is not pure (pure selectors must contain at least one local class or id)`,
689 locConverter,
690 from,
691 to
692 );
693 };
694
695 /** @typedef {{ value?: string, importName?: string, localName?: string, request?: string }} IcssDefinition */
696 /** @type {Map<string, IcssDefinition>} */
697 const icssDefinitions = new Map();
698
699 // Tracks `composes: <name> from "<file>"` declarations to enforce a
700 // predictable file load order across rules (port of
701 // postcss-modules-extract-imports#138). Each rule's composes order
702 // is a partial ordering: if `.x` composes `b from "./b"` before
703 // `c from "./c"`, then `b.css` must load before `c.css` so `c` can
704 // override `b` in the cascade. Edges are added inline as the rule
705 // is parsed; at end-of-parse the first composes-import dep of each
706 // file is tagged with `sourceOrder` according to a topological
707 // sort (`NormalModule#build` reorders by `sourceOrder` for us).
708 /** @type {Map<string, Set<string>>} */
709 const composesGraph = new Map();
710 /** @type {Map<string, CssIcssImportDependency>} */
711 const composesFirstFileImport = new Map();
712 /** @type {string | undefined} */
713 let currentRulePrevComposesFile;
714 /** @type {Set<string>} */
715 const currentRuleComposesFiles = new Set();
716
717 /**
718 * Checks whether this css parser is next nested syntax.
719 * @param {string} input input
720 * @param {number} pos position
721 * @returns {boolean} true, when next is nested syntax
722 */
723 const isNextNestedSyntax = (input, pos) => {
724 pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
725
726 if (
727 input.charCodeAt(pos) === CC_RIGHT_CURLY ||
728 (input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
729 input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS)
730 ) {
731 return false;
732 }
733
734 const identifier = walkCssTokens.eatIdentSequence(input, pos);
735
736 if (!identifier) {
737 return true;
738 }
739
740 const leftCurly = eatUntilLeftCurly(input, pos);
741 const content = input.slice(identifier[0], leftCurly);
742
743 if (content.includes(";") || content.includes("}")) {
744 return false;
745 }
746
747 return true;
748 };
749 /**
750 * Checks whether this css parser is local mode.
751 * @returns {boolean} true, when in local scope
752 */
753 const isLocalMode = () =>
754 modeData === LOCAL_MODE || (mode === "local" && modeData === undefined);
755
756 /**
757 * Returns end.
758 * @param {string} input input
759 * @param {number} start start
760 * @param {number} end end
761 * @returns {number} end
762 */
763 const comment = (input, start, end) => {
764 if (!this.comments) this.comments = [];
765 const { line: sl, column: sc } = locConverter.get(start);
766 const { line: el, column: ec } = locConverter.get(end);
767
768 const value = input.slice(start + 2, end - 2);
769
770 /** @type {Comment} */
771 const comment = {
772 value,
773 range: [start, end],
774 loc: {
775 start: { line: sl, column: sc },
776 end: { line: el, column: ec }
777 }
778 };
779 this.comments.push(comment);
780
781 if (pureMode) {
782 if (PURE_IGNORE_RE.test(value)) {
783 pureIgnorePending = true;
784 } else if (
785 PURE_NO_CHECK_RE.test(value) &&
786 scope === CSS_MODE_TOP_LEVEL &&
787 !seenTopLevelRule
788 ) {
789 pureNoCheck = true;
790 }
791 }
792
793 return end;
794 };
795
796 // Vanilla CSS stuff
797
798 /**
799 * Processes the provided input.
800 * @param {string} input input
801 * @param {number} start name start position
802 * @param {number} end name end position
803 * @returns {number} position after handling
804 */
805 const processAtImport = (input, start, end) => {
806 const tokens = walkCssTokens.eatImportTokens(input, end, {
807 comment
808 });
809 if (!tokens[3]) return end;
810 const semi = tokens[3][1];
811 if (!tokens[0] || (tokens[0][4] && !isModules)) {
812 this._emitWarning(
813 state,
814 `Expected URL in '${input.slice(start, semi)}'`,
815 locConverter,
816 start,
817 semi
818 );
819 return end;
820 }
821
822 const urlToken = tokens[0];
823 /** @type {string} */
824 let url;
825 if (urlToken[4]) {
826 // URL given as identifier — resolve via CSS Modules @value.
827 const name = input.slice(urlToken[2], urlToken[3]);
828 const def = icssDefinitions.get(name);
829 if (!def) {
830 this._emitWarning(
831 state,
832 `Unknown '@value' identifier '${name}' in '${input.slice(start, semi)}'`,
833 locConverter,
834 start,
835 semi
836 );
837 // Consume the whole at-rule so the unresolved identifier
838 // doesn't get re-tokenized and accidentally substituted
839 // into a malformed `@import` in the output.
840 const dep = new ConstDependency("", [start, semi]);
841 module.addPresentationalDependency(dep);
842 return semi;
843 }
844 if (def.value === undefined) {
845 this._emitWarning(
846 state,
847 `'@value' identifier '${name}' was imported from another module and cannot be used as the URL of '@import' — only locally defined values are supported here`,
848 locConverter,
849 start,
850 semi
851 );
852 const dep = new ConstDependency("", [start, semi]);
853 module.addPresentationalDependency(dep);
854 return semi;
855 }
856 const raw = def.value.trim();
857 url =
858 (raw.startsWith('"') && raw.endsWith('"')) ||
859 (raw.startsWith("'") && raw.endsWith("'"))
860 ? normalizeUrl(raw.slice(1, -1), true)
861 : normalizeUrl(raw, false);
862 } else {
863 url = normalizeUrl(input.slice(urlToken[2], urlToken[3]), true);
864 }
865 const newline = walkCssTokens.eatWhiteLine(input, semi);
866 const { options, errors: commentErrors } = this.parseCommentOptions([
867 end,
868 urlToken[1]
869 ]);
870 if (commentErrors) {
871 for (const e of commentErrors) {
872 const { comment } = e;
873 state.module.addWarning(
874 new CommentCompilationWarning(
875 `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
876 comment.loc
877 )
878 );
879 }
880 }
881 if (options && options.webpackIgnore !== undefined) {
882 if (typeof options.webpackIgnore !== "boolean") {
883 const { line: sl, column: sc } = locConverter.get(start);
884 const { line: el, column: ec } = locConverter.get(newline);
885
886 state.module.addWarning(
887 new UnsupportedFeatureWarning(
888 `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
889 {
890 start: { line: sl, column: sc },
891 end: { line: el, column: ec }
892 }
893 )
894 );
895 } else if (options.webpackIgnore) {
896 return newline;
897 }
898 }
899 if (url.length === 0) {
900 const { line: sl, column: sc } = locConverter.get(start);
901 const { line: el, column: ec } = locConverter.get(newline);
902 const dep = new ConstDependency("", [start, newline]);
903 module.addPresentationalDependency(dep);
904 dep.setLoc(sl, sc, el, ec);
905
906 return newline;
907 }
908
909 /** @type {undefined | string} */
910 let layer;
911
912 if (tokens[1]) {
913 layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim();
914 }
915
916 /** @type {undefined | string} */
917 let supports;
918
919 if (tokens[2]) {
920 supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim();
921 }
922
923 const last = tokens[2] || tokens[1] || tokens[0];
924 const mediaStart = walkCssTokens.eatWhitespaceAndComments(
925 input,
926 last[1]
927 )[0];
928
929 /** @type {undefined | string} */
930 let media;
931
932 if (mediaStart !== semi - 1) {
933 media = input.slice(mediaStart, semi - 1).trim();
934 }
935
936 const { line: sl, column: sc } = locConverter.get(start);
937 const { line: el, column: ec } = locConverter.get(newline);
938 const dep = new CssImportDependency(
939 url,
940 [start, newline],
941 mode === "local" || mode === "global" ? mode : undefined,
942 layer,
943 supports && supports.length > 0 ? supports : undefined,
944 media && media.length > 0 ? media : undefined
945 );
946 dep.setLoc(sl, sc, el, ec);
947 module.addDependency(dep);
948 // `text` and `css-style-sheet` parents inline the imported
949 // module's rendered CSS at build time, which means we read the
950 // imported module's `codeGenerationResults` (and through it the
951 // results of any assets the import references). Registering this
952 // as a code-generation dependency tells the compilation scheduler
953 // to generate the imported subtree before us.
954 const exportType = /** @type {import("./CssModule")} */ (module)
955 .exportType;
956 if (exportType === "text" || exportType === "css-style-sheet") {
957 module.addCodeGenerationDependency(dep);
958 }
959
960 return newline;
961 };
962
963 /**
964 * Process url function.
965 * @param {string} input input
966 * @param {number} end end position
967 * @param {string} name the name of function
968 * @returns {number} position after handling
969 */
970 const processURLFunction = (input, end, name) => {
971 const string = walkCssTokens.eatString(input, end);
972 if (!string) return end;
973 const { options, errors: commentErrors } = this.parseCommentOptions([
974 lastTokenEndForComments,
975 end
976 ]);
977 if (commentErrors) {
978 for (const e of commentErrors) {
979 const { comment } = e;
980 state.module.addWarning(
981 new CommentCompilationWarning(
982 `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
983 comment.loc
984 )
985 );
986 }
987 }
988 if (options && options.webpackIgnore !== undefined) {
989 if (typeof options.webpackIgnore !== "boolean") {
990 const { line: sl, column: sc } = locConverter.get(string[0]);
991 const { line: el, column: ec } = locConverter.get(string[1]);
992
993 state.module.addWarning(
994 new UnsupportedFeatureWarning(
995 `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
996 {
997 start: { line: sl, column: sc },
998 end: { line: el, column: ec }
999 }
1000 )
1001 );
1002 } else if (options.webpackIgnore) {
1003 return end;
1004 }
1005 }
1006 const value = normalizeUrl(
1007 input.slice(string[0] + 1, string[1] - 1),
1008 true
1009 );
1010 // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
1011 if (value.length === 0) return end;
1012 const isUrl = name === "url" || name === "src";
1013 const dep = new CssUrlDependency(
1014 value,
1015 [string[0], string[1]],
1016 isUrl ? "string" : "url"
1017 );
1018 const { line: sl, column: sc } = locConverter.get(string[0]);
1019 const { line: el, column: ec } = locConverter.get(string[1]);
1020 dep.setLoc(sl, sc, el, ec);
1021 module.addDependency(dep);
1022 module.addCodeGenerationDependency(dep);
1023 return string[1];
1024 };
1025
1026 /**
1027 * Process old url function.
1028 * @param {string} input input
1029 * @param {number} start start position
1030 * @param {number} end end position
1031 * @param {number} contentStart start position
1032 * @param {number} contentEnd end position
1033 * @returns {number} position after handling
1034 */
1035 const processOldURLFunction = (
1036 input,
1037 start,
1038 end,
1039 contentStart,
1040 contentEnd
1041 ) => {
1042 const { options, errors: commentErrors } = this.parseCommentOptions([
1043 lastTokenEndForComments,
1044 end
1045 ]);
1046 if (commentErrors) {
1047 for (const e of commentErrors) {
1048 const { comment } = e;
1049 state.module.addWarning(
1050 new CommentCompilationWarning(
1051 `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
1052 comment.loc
1053 )
1054 );
1055 }
1056 }
1057 if (options && options.webpackIgnore !== undefined) {
1058 if (typeof options.webpackIgnore !== "boolean") {
1059 const { line: sl, column: sc } = locConverter.get(
1060 lastTokenEndForComments
1061 );
1062 const { line: el, column: ec } = locConverter.get(end);
1063
1064 state.module.addWarning(
1065 new UnsupportedFeatureWarning(
1066 `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
1067 {
1068 start: { line: sl, column: sc },
1069 end: { line: el, column: ec }
1070 }
1071 )
1072 );
1073 } else if (options.webpackIgnore) {
1074 return end;
1075 }
1076 }
1077 let value = normalizeUrl(input.slice(contentStart, contentEnd), false);
1078 // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
1079 if (value.length === 0) return end;
1080 if (isModules) {
1081 const def = icssDefinitions.get(value);
1082 if (def) {
1083 if (def.value !== undefined) {
1084 const raw = def.value.trim();
1085 value =
1086 (raw.startsWith('"') && raw.endsWith('"')) ||
1087 (raw.startsWith("'") && raw.endsWith("'"))
1088 ? normalizeUrl(raw.slice(1, -1), true)
1089 : normalizeUrl(raw, false);
1090 if (value.length === 0) return end;
1091 } else {
1092 this._emitWarning(
1093 state,
1094 `'@value' identifier '${value}' was imported from another module and cannot be used inside 'url()' — only locally defined values are supported here`,
1095 locConverter,
1096 start,
1097 end
1098 );
1099 return end;
1100 }
1101 }
1102 }
1103 const dep = new CssUrlDependency(value, [start, end], "url");
1104 const { line: sl, column: sc } = locConverter.get(start);
1105 const { line: el, column: ec } = locConverter.get(end);
1106 dep.setLoc(sl, sc, el, ec);
1107 module.addDependency(dep);
1108 module.addCodeGenerationDependency(dep);
1109 return end;
1110 };
1111
1112 /**
1113 * Process image set function.
1114 * @param {string} input input
1115 * @param {number} start start position
1116 * @param {number} end end position
1117 * @returns {number} position after handling
1118 */
1119 const processImageSetFunction = (input, start, end) => {
1120 lastTokenEndForComments = end;
1121 const values = walkCssTokens.eatImageSetStrings(input, end, {
1122 comment
1123 });
1124 if (values.length === 0) return end;
1125 for (const [index, string] of values.entries()) {
1126 const value = normalizeUrl(
1127 input.slice(string[0] + 1, string[1] - 1),
1128 true
1129 );
1130 if (value.length === 0) return end;
1131 const { options, errors: commentErrors } = this.parseCommentOptions([
1132 index === 0 ? start : values[index - 1][1],
1133 string[1]
1134 ]);
1135 if (commentErrors) {
1136 for (const e of commentErrors) {
1137 const { comment } = e;
1138 state.module.addWarning(
1139 new CommentCompilationWarning(
1140 `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
1141 comment.loc
1142 )
1143 );
1144 }
1145 }
1146 if (options && options.webpackIgnore !== undefined) {
1147 if (typeof options.webpackIgnore !== "boolean") {
1148 const { line: sl, column: sc } = locConverter.get(string[0]);
1149 const { line: el, column: ec } = locConverter.get(string[1]);
1150
1151 state.module.addWarning(
1152 new UnsupportedFeatureWarning(
1153 `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
1154 {
1155 start: { line: sl, column: sc },
1156 end: { line: el, column: ec }
1157 }
1158 )
1159 );
1160 } else if (options.webpackIgnore) {
1161 continue;
1162 }
1163 }
1164 const dep = new CssUrlDependency(value, [string[0], string[1]], "url");
1165 const { line: sl, column: sc } = locConverter.get(string[0]);
1166 const { line: el, column: ec } = locConverter.get(string[1]);
1167 dep.setLoc(sl, sc, el, ec);
1168 module.addDependency(dep);
1169 module.addCodeGenerationDependency(dep);
1170 }
1171 // Can contain `url()` inside, so let's return end to allow parse them
1172 return end;
1173 };
1174
1175 // CSS modules stuff
1176
1177 /**
1178 * Returns resolved reexport (localName and importName).
1179 * @param {string} value value to resolve
1180 * @param {string=} localName override local name
1181 * @param {boolean=} isCustomProperty true when it is custom property, otherwise false
1182 * @returns {string | [string, string] | [string, string, string]} resolved reexport (`localName`, `importName` and optional `request` of the active `@value` import)
1183 */
1184 const getReexport = (value, localName, isCustomProperty) => {
1185 const reexport = icssDefinitions.get(
1186 isCustomProperty ? `--${value}` : value
1187 );
1188
1189 if (reexport) {
1190 if (reexport.importName) {
1191 const resolvedLocalName =
1192 reexport.localName || (isCustomProperty ? `--${value}` : value);
1193 return reexport.request
1194 ? [resolvedLocalName, reexport.importName, reexport.request]
1195 : [resolvedLocalName, reexport.importName];
1196 }
1197
1198 if (isCustomProperty) {
1199 return /** @type {string} */ (reexport.value).slice(2);
1200 }
1201
1202 return /** @type {string} */ (reexport.value);
1203 }
1204
1205 if (localName) {
1206 return [localName, value];
1207 }
1208
1209 return value;
1210 };
1211
1212 /**
1213 * Process import or export.
1214 * @param {0 | 1} type import or export
1215 * @param {string} input input
1216 * @param {number} pos start position
1217 * @returns {number} position after parse
1218 */
1219 const processImportOrExport = (type, input, pos) => {
1220 pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
1221 /** @type {string | undefined} */
1222 let request;
1223 if (type === 0) {
1224 let cc = input.charCodeAt(pos);
1225 if (cc !== CC_LEFT_PARENTHESIS) {
1226 this._emitWarning(
1227 state,
1228 `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`,
1229 locConverter,
1230 pos,
1231 pos
1232 );
1233 return pos;
1234 }
1235 pos++;
1236 const stringStart = pos;
1237 const str = walkCssTokens.eatString(input, pos);
1238 if (!str) {
1239 this._emitWarning(
1240 state,
1241 `Unexpected '${input[pos]}' at ${pos} during parsing of '${type === 0 ? ":import" : ":export"}' (expected string)`,
1242 locConverter,
1243 stringStart,
1244 pos
1245 );
1246 return pos;
1247 }
1248 request = input.slice(str[0] + 1, str[1] - 1);
1249 pos = str[1];
1250 pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
1251 cc = input.charCodeAt(pos);
1252 if (cc !== CC_RIGHT_PARENTHESIS) {
1253 this._emitWarning(
1254 state,
1255 `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`,
1256 locConverter,
1257 pos,
1258 pos
1259 );
1260 return pos;
1261 }
1262 pos++;
1263 pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
1264 }
1265
1266 /**
1267 * Creates a dep from the provided name.
1268 * @param {string} name name
1269 * @param {string} value value
1270 * @param {number} start start of position
1271 * @param {number} end end of position
1272 */
1273 const createDep = (name, value, start, end) => {
1274 if (type === 0) {
1275 const dep = new CssIcssImportDependency(
1276 /** @type {string} */
1277 (request),
1278 [0, 0],
1279 /** @type {"local" | "global"} */
1280 (mode),
1281 value,
1282 name
1283 );
1284 const { line: sl, column: sc } = locConverter.get(start);
1285 const { line: el, column: ec } = locConverter.get(end);
1286 dep.setLoc(sl, sc, el, ec);
1287 module.addDependency(dep);
1288
1289 icssDefinitions.set(name, {
1290 importName: value,
1291 request: /** @type {string} */ (request)
1292 });
1293 } else if (type === 1) {
1294 const dep = new CssIcssExportDependency(name, getReexport(value));
1295 const { line: sl, column: sc } = locConverter.get(start);
1296 const { line: el, column: ec } = locConverter.get(end);
1297 dep.setLoc(sl, sc, el, ec);
1298 module.addDependency(dep);
1299 }
1300 };
1301
1302 let needTerminate = false;
1303 let balanced = 0;
1304 /** @type {undefined | 0 | 1 | 2} */
1305 let scope;
1306
1307 /** @typedef {[number, number]} Name */
1308
1309 /** @type {Name | undefined} */
1310 let name;
1311 /** @type {number | undefined} */
1312 let value;
1313
1314 /** @type {CssTokenCallbacks} */
1315 const callbacks = {
1316 leftCurlyBracket: (_input, _start, end) => {
1317 balanced++;
1318
1319 if (scope === undefined) {
1320 scope = 0;
1321 }
1322
1323 return end;
1324 },
1325 rightCurlyBracket: (_input, _start, end) => {
1326 balanced--;
1327
1328 if (scope === 2) {
1329 const [nameStart, nameEnd] = /** @type {Name} */ (name);
1330 createDep(
1331 input.slice(nameStart, nameEnd),
1332 input.slice(value, end - 1).trim(),
1333 nameEnd,
1334 end - 1
1335 );
1336 scope = 0;
1337 }
1338
1339 if (balanced === 0 && scope === 0) {
1340 needTerminate = true;
1341 }
1342
1343 return end;
1344 },
1345 identifier: (_input, start, end) => {
1346 if (scope === 0) {
1347 name = [start, end];
1348 scope = 1;
1349 }
1350
1351 return end;
1352 },
1353 colon: (_input, _start, end) => {
1354 if (scope === 1) {
1355 scope = 2;
1356 value = walkCssTokens.eatWhitespace(input, end);
1357 return value;
1358 }
1359
1360 return end;
1361 },
1362 semicolon: (input, _start, end) => {
1363 if (scope === 2) {
1364 const [nameStart, nameEnd] = /** @type {Name} */ (name);
1365 createDep(
1366 input.slice(nameStart, nameEnd),
1367 input.slice(value, end - 1),
1368 nameEnd,
1369 end - 1
1370 );
1371 scope = 0;
1372 }
1373
1374 return end;
1375 },
1376 needTerminate: () => needTerminate
1377 };
1378
1379 pos = walkCssTokens(input, pos, callbacks);
1380 pos = walkCssTokens.eatWhiteLine(input, pos);
1381
1382 return pos;
1383 };
1384
1385 /** @typedef {{ from: string, items: ({ localName: string, importName: string })[] }} ValueAtRuleImport */
1386 /** @typedef {{ localName: string, value: string }} ValueAtRuleValue */
1387 /**
1388 * Parses value at rule params.
1389 * @param {string} str value at-rule params
1390 * @returns {ValueAtRuleImport | ValueAtRuleValue} parsed result
1391 */
1392 const parseValueAtRuleParams = (str) => {
1393 if (/from(\/\*|\s)(?:[\s\S]+)$/i.test(str)) {
1394 str = str.replace(CSS_COMMENT, " ").trim().replace(/;$/, "");
1395 const fromIdx = str.lastIndexOf("from");
1396 const path = str
1397 .slice(fromIdx + 5)
1398 .trim()
1399 .replace(/['"]/g, "");
1400 let content = str.slice(0, fromIdx).trim();
1401
1402 if (content.startsWith("(") && content.endsWith(")")) {
1403 content = content.slice(1, -1);
1404 }
1405
1406 return {
1407 from: path,
1408 items: content.split(",").map((item) => {
1409 item = item.trim();
1410
1411 if (item.includes(":")) {
1412 const [local, remote] = item.split(":");
1413
1414 return { localName: local.trim(), importName: remote.trim() };
1415 }
1416
1417 const asParts = item.split(/\s+as\s+/);
1418
1419 if (asParts.length === 2) {
1420 return {
1421 localName: asParts[1].trim(),
1422 importName: asParts[0].trim()
1423 };
1424 }
1425
1426 return { localName: item, importName: item };
1427 })
1428 };
1429 }
1430
1431 /** @type {string} */
1432 let localName;
1433 /** @type {string} */
1434 let value;
1435
1436 const idx = str.indexOf(":");
1437
1438 if (idx !== -1) {
1439 localName = str.slice(0, idx).replace(CSS_COMMENT, "").trim();
1440 value = str.slice(idx + 1);
1441 } else {
1442 const mask = str.replace(CSS_COMMENT, (m) => " ".repeat(m.length));
1443 const idx = mask.search(/\S\s/) + 1;
1444
1445 localName = str.slice(0, idx).replace(CSS_COMMENT, "").trim();
1446 value = str.slice(idx + (str[idx] === " " ? 1 : 0));
1447 }
1448
1449 if (value.length > 0 && !/^\s+$/.test(value.replace(CSS_COMMENT, ""))) {
1450 value = value.trim();
1451 }
1452
1453 return { localName, value };
1454 };
1455
1456 /**
1457 * Processes the provided input.
1458 * @param {string} input input
1459 * @param {number} start name start position
1460 * @param {number} end name end position
1461 * @returns {number} position after handling
1462 */
1463 const processAtValue = (input, start, end) => {
1464 const semi = eatUntilSemi(input, end);
1465 const atRuleEnd = semi + 1;
1466 const params = input.slice(end, semi);
1467 const parsed = parseValueAtRuleParams(params);
1468
1469 if (
1470 typeof (/** @type {ValueAtRuleImport} */ (parsed).from) !== "undefined"
1471 ) {
1472 if (/** @type {ValueAtRuleImport} */ (parsed).from.length === 0) {
1473 this._emitWarning(
1474 state,
1475 `Broken '@value' at-rule: ${input.slice(start, atRuleEnd)}'`,
1476 locConverter,
1477 start,
1478 atRuleEnd
1479 );
1480
1481 const dep = new ConstDependency("", [start, atRuleEnd]);
1482 module.addPresentationalDependency(dep);
1483 return atRuleEnd;
1484 }
1485
1486 let { from, items } = /** @type {ValueAtRuleImport} */ (parsed);
1487
1488 for (const { importName, localName } of items) {
1489 {
1490 const reexport = icssDefinitions.get(from);
1491
1492 if (reexport && reexport.value) {
1493 from = reexport.value.slice(1, -1);
1494 }
1495
1496 const dep = new CssIcssImportDependency(
1497 from,
1498 [0, 0],
1499 /** @type {"local" | "global"} */
1500 (mode),
1501 importName,
1502 localName
1503 );
1504 const { line: sl, column: sc } = locConverter.get(start);
1505 const { line: el, column: ec } = locConverter.get(end);
1506 dep.setLoc(sl, sc, el, ec);
1507 module.addDependency(dep);
1508
1509 icssDefinitions.set(localName, { importName, request: from });
1510 }
1511
1512 {
1513 const dep = new CssIcssExportDependency(
1514 localName,
1515 getReexport(localName),
1516 undefined,
1517 false,
1518 CssIcssExportDependency.EXPORT_MODE.REPLACE
1519 );
1520 const { line: sl, column: sc } = locConverter.get(start);
1521 const { line: el, column: ec } = locConverter.get(end);
1522 dep.setLoc(sl, sc, el, ec);
1523 module.addDependency(dep);
1524 }
1525 }
1526 } else {
1527 if (/** @type {ValueAtRuleValue} */ (parsed).localName.length === 0) {
1528 this._emitWarning(
1529 state,
1530 `Broken '@value' at-rule: ${input.slice(start, atRuleEnd)}'`,
1531 locConverter,
1532 start,
1533 atRuleEnd
1534 );
1535
1536 const dep = new ConstDependency("", [start, atRuleEnd]);
1537 module.addPresentationalDependency(dep);
1538 return atRuleEnd;
1539 }
1540
1541 const { localName, value } = /** @type {ValueAtRuleValue} */ (parsed);
1542 const { line: sl, column: sc } = locConverter.get(start);
1543 const { line: el, column: ec } = locConverter.get(end);
1544
1545 if (icssDefinitions.has(value)) {
1546 const def =
1547 /** @type {IcssDefinition} */
1548 (icssDefinitions.get(value));
1549
1550 def.localName = value;
1551
1552 icssDefinitions.set(localName, def);
1553
1554 const dep = new CssIcssExportDependency(
1555 localName,
1556 getReexport(value)
1557 );
1558 dep.setLoc(sl, sc, el, ec);
1559 module.addDependency(dep);
1560 } else {
1561 icssDefinitions.set(localName, { value });
1562
1563 const dep = new CssIcssExportDependency(localName, value);
1564 dep.setLoc(sl, sc, el, ec);
1565 module.addDependency(dep);
1566 }
1567 }
1568
1569 const dep = new ConstDependency("", [start, atRuleEnd]);
1570 module.addPresentationalDependency(dep);
1571 return atRuleEnd;
1572 };
1573
1574 /**
1575 * Process icss symbol.
1576 * @param {string} name ICSS symbol name
1577 * @param {number} start start position
1578 * @param {number} end end position
1579 * @returns {number} position after handling
1580 */
1581 const processICSSSymbol = (name, start, end) => {
1582 const def =
1583 /** @type {IcssDefinition} */
1584 (icssDefinitions.get(name));
1585 const { line: sl, column: sc } = locConverter.get(start);
1586 const { line: el, column: ec } = locConverter.get(end);
1587 const dep = new CssIcssSymbolDependency(
1588 def.localName || name,
1589 [start, end],
1590 def.value,
1591 def.importName,
1592 def.request
1593 );
1594 dep.setLoc(sl, sc, el, ec);
1595 module.addDependency(dep);
1596 return end;
1597 };
1598
1599 /**
1600 * Process local or global function.
1601 * @param {string} input input
1602 * @param {1 | 2} type type of function
1603 * @param {number} start start position
1604 * @param {number} end end position
1605 * @returns {number} position after handling
1606 */
1607 const processLocalOrGlobalFunction = (input, type, start, end) => {
1608 // Replace `local(`/` or `global(` (handle legacy `:local(` or `:global(` too)
1609 {
1610 const isColon = input.charCodeAt(start - 1) === CC_COLON;
1611 const dep = new ConstDependency("", [isColon ? start - 1 : start, end]);
1612 module.addPresentationalDependency(dep);
1613 }
1614
1615 end = walkCssTokens.consumeUntil(
1616 input,
1617 start,
1618 {
1619 identifier(input, start, end) {
1620 if (type === 1) {
1621 let identifier = unescapeIdentifierCached(
1622 input.slice(start, end)
1623 );
1624 const { line: sl, column: sc } = locConverter.get(start);
1625 const { line: el, column: ec } = locConverter.get(end);
1626 const isDashedIdent = isDashedIdentifier(identifier);
1627
1628 if (isDashedIdent) {
1629 identifier = identifier.slice(2);
1630 }
1631
1632 const dep = new CssIcssExportDependency(
1633 identifier,
1634 getReexport(identifier),
1635 [start, end],
1636 true,
1637 CssIcssExportDependency.EXPORT_MODE.ONCE,
1638 isDashedIdent
1639 ? CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
1640 : CssIcssExportDependency.EXPORT_TYPE.NORMAL
1641 );
1642
1643 dep.setLoc(sl, sc, el, ec);
1644 module.addDependency(dep);
1645 }
1646
1647 return end;
1648 }
1649 },
1650 {},
1651 { onlyTopLevel: true, functionValue: true }
1652 );
1653
1654 {
1655 // Replace the last `)`
1656 const dep = new ConstDependency("", [end, end + 1]);
1657 module.addPresentationalDependency(dep);
1658 }
1659
1660 return end;
1661 };
1662
1663 /**
1664 * Process local at rule.
1665 * @param {string} input input
1666 * @param {number} end name end position
1667 * @param {{ string?: boolean, identifier?: boolean | RegExp }} options types which allowed to handle
1668 * @returns {number} position after handling
1669 */
1670 const processLocalAtRule = (input, end, options) => {
1671 let found = false;
1672
1673 return walkCssTokens.consumeUntil(
1674 input,
1675 end,
1676 {
1677 string(_input, start, end) {
1678 if (!found && options.string) {
1679 const value = unescapeIdentifierCached(
1680 input.slice(start + 1, end - 1)
1681 );
1682 const { line: sl, column: sc } = locConverter.get(start);
1683 const { line: el, column: ec } = locConverter.get(end);
1684 const dep = new CssIcssExportDependency(
1685 value,
1686 value,
1687 [start, end],
1688 true,
1689 CssIcssExportDependency.EXPORT_MODE.ONCE
1690 );
1691 dep.setLoc(sl, sc, el, ec);
1692 module.addDependency(dep);
1693 found = true;
1694 if (pureMode) currentSelectorHasLocal = true;
1695 }
1696 return end;
1697 },
1698 identifier(input, start, end) {
1699 if (!found) {
1700 const value = input.slice(start, end);
1701
1702 if (options.identifier) {
1703 const identifier = unescapeIdentifierCached(value);
1704
1705 if (
1706 options.identifier instanceof RegExp &&
1707 options.identifier.test(identifier)
1708 ) {
1709 return end;
1710 }
1711
1712 const { line: sl, column: sc } = locConverter.get(start);
1713 const { line: el, column: ec } = locConverter.get(end);
1714
1715 const dep = new CssIcssExportDependency(
1716 identifier,
1717 getReexport(identifier),
1718 [start, end],
1719 true,
1720 CssIcssExportDependency.EXPORT_MODE.ONCE,
1721 CssIcssExportDependency.EXPORT_TYPE.NORMAL
1722 );
1723 dep.setLoc(sl, sc, el, ec);
1724 module.addDependency(dep);
1725 found = true;
1726 if (pureMode) currentSelectorHasLocal = true;
1727 }
1728 }
1729 return end;
1730 }
1731 },
1732 {
1733 function: (input, start, end) => {
1734 // No need to handle `:` (COLON), because it's always a function
1735 const name = input
1736 .slice(start, end - 1)
1737 .replace(/\\/g, "")
1738 .toLowerCase();
1739
1740 const type =
1741 name === "local" ? 1 : name === "global" ? 2 : undefined;
1742
1743 if (!found && type) {
1744 found = true;
1745 if (type === 1 && pureMode) currentSelectorHasLocal = true;
1746 return processLocalOrGlobalFunction(input, type, start, end);
1747 }
1748
1749 if (
1750 this.options.dashedIdents &&
1751 isLocalMode() &&
1752 (name === "var" || name === "style")
1753 ) {
1754 return processDashedIdent(input, end, end);
1755 }
1756
1757 return end;
1758 }
1759 },
1760 { onlyTopLevel: true, atRulePrelude: true }
1761 );
1762 };
1763 /**
1764 * Process dashed ident.
1765 * @param {string} input input
1766 * @param {number} start start position
1767 * @param {number} end end position
1768 * @returns {number} position after handling
1769 */
1770 const processDashedIdent = (input, start, end) => {
1771 const customIdent = walkCssTokens.eatIdentSequence(input, start);
1772 if (!customIdent) return end;
1773 const identifier = unescapeIdentifierCached(
1774 input.slice(customIdent[0] + 2, customIdent[1])
1775 );
1776 const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments(
1777 input,
1778 customIdent[1]
1779 )[0];
1780 if (
1781 input.charCodeAt(afterCustomIdent) === CC_LOWER_F ||
1782 input.charCodeAt(afterCustomIdent) === CC_UPPER_F
1783 ) {
1784 const fromWord = walkCssTokens.eatIdentSequence(
1785 input,
1786 afterCustomIdent
1787 );
1788 if (
1789 !fromWord ||
1790 input.slice(fromWord[0], fromWord[1]).toLowerCase() !== "from"
1791 ) {
1792 return end;
1793 }
1794 const from = walkCssTokens.eatIdentSequenceOrString(
1795 input,
1796 walkCssTokens.eatWhitespaceAndComments(input, fromWord[1])[0]
1797 );
1798 if (!from) {
1799 return end;
1800 }
1801 const path = input.slice(from[0], from[1]);
1802 if (from[2] === true && path === "global") {
1803 const dep = new ConstDependency("", [customIdent[1], from[1]]);
1804 module.addPresentationalDependency(dep);
1805 return end;
1806 } else if (from[2] === false) {
1807 const { line: sl, column: sc } = locConverter.get(customIdent[0]);
1808 const { line: el, column: ec } = locConverter.get(from[1] - 1);
1809 const localName = `__ICSS_IMPORT_${counter++}__`;
1810
1811 {
1812 const dep = new CssIcssImportDependency(
1813 path.slice(1, -1),
1814 [customIdent[0], from[1] - 1],
1815 /** @type {"local" | "global"} */
1816 (mode),
1817 identifier,
1818 localName
1819 );
1820
1821 dep.setLoc(sl, sc, el, ec);
1822 module.addDependency(dep);
1823 }
1824
1825 {
1826 const dep = new CssIcssExportDependency(
1827 identifier,
1828 getReexport(identifier, localName, true),
1829 [customIdent[0], from[1] - 1],
1830 true,
1831 CssIcssExportDependency.EXPORT_MODE.ONCE,
1832 CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
1833 );
1834
1835 dep.setLoc(sl, sc, el, ec);
1836 module.addDependency(dep);
1837 }
1838
1839 {
1840 const dep = new ConstDependency("", [fromWord[0], from[1]]);
1841 module.addPresentationalDependency(dep);
1842 return end;
1843 }
1844 }
1845 } else {
1846 const { line: sl, column: sc } = locConverter.get(customIdent[0]);
1847 const { line: el, column: ec } = locConverter.get(customIdent[1]);
1848 const dep = new CssIcssExportDependency(
1849 identifier,
1850 getReexport(identifier, undefined, true),
1851 [customIdent[0], customIdent[1]],
1852 true,
1853 CssIcssExportDependency.EXPORT_MODE.ONCE,
1854 CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
1855 );
1856 dep.setLoc(sl, sc, el, ec);
1857 module.addDependency(dep);
1858 return end;
1859 }
1860
1861 return end;
1862 };
1863 /**
1864 * Process local declaration.
1865 * @param {string} input input
1866 * @param {number} pos name start position
1867 * @param {number} end name end position
1868 * @returns {number} position after handling
1869 */
1870 const processLocalDeclaration = (input, pos, end) => {
1871 pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
1872 const identifier = walkCssTokens.eatIdentSequence(input, pos);
1873
1874 if (!identifier) {
1875 return end;
1876 }
1877
1878 const propertyNameStart = identifier[0];
1879
1880 pos = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];
1881
1882 if (input.charCodeAt(pos) !== CC_COLON) {
1883 return end;
1884 }
1885
1886 pos += 1;
1887
1888 // Remove prefix and lowercase
1889 const propertyName = input
1890 .slice(identifier[0], identifier[1])
1891 .replace(/^(-\w+-)/, "")
1892 .toLowerCase();
1893
1894 if (isLocalMode() && knownProperties.has(propertyName)) {
1895 /** @type {[number, number, boolean?][]} */
1896 const values = [];
1897 /** @type {Record<string, number>} */
1898 let parsedKeywords = Object.create(null);
1899
1900 const isGridProperty = Boolean(propertyName.startsWith("grid"));
1901 const isGridTemplate = isGridProperty
1902 ? Boolean(
1903 propertyName === "grid" ||
1904 propertyName === "grid-template" ||
1905 propertyName === "grid-template-columns" ||
1906 propertyName === "grid-template-rows"
1907 )
1908 : false;
1909
1910 let afterExclamation = false;
1911
1912 const end = walkCssTokens.consumeUntil(
1913 input,
1914 pos,
1915 {
1916 delim(input, start, end) {
1917 afterExclamation = input.charCodeAt(start) === CC_EXCLAMATION;
1918 return end;
1919 },
1920 leftSquareBracket(input, start, end) {
1921 let i = end;
1922
1923 while (true) {
1924 i = walkCssTokens.eatWhitespaceAndComments(input, i)[0];
1925 const name = walkCssTokens.eatIdentSequence(input, i);
1926
1927 if (!name) {
1928 break;
1929 }
1930
1931 values.push(name);
1932 i = name[1];
1933 }
1934
1935 return end;
1936 },
1937 string(_input, start, end) {
1938 if (
1939 propertyName === "animation" ||
1940 propertyName === "animation-name"
1941 ) {
1942 values.push([start, end, true]);
1943 }
1944
1945 if (
1946 propertyName === "grid" ||
1947 propertyName === "grid-template" ||
1948 propertyName === "grid-template-areas"
1949 ) {
1950 const areas = unescapeIdentifierCached(
1951 input.slice(start + 1, end - 1)
1952 );
1953 const matches = matchAll(/\b\w+\b/g, areas);
1954
1955 for (const match of matches) {
1956 const areaStart = start + 1 + match.index;
1957 values.push([areaStart, areaStart + match[0].length, false]);
1958 }
1959 }
1960
1961 return end;
1962 },
1963 identifier(input, start, end) {
1964 if (isGridTemplate) {
1965 return end;
1966 }
1967
1968 if (afterExclamation) {
1969 afterExclamation = false;
1970 return end;
1971 }
1972
1973 const identifier = input.slice(start, end);
1974 const keyword = identifier.toLowerCase();
1975
1976 parsedKeywords[keyword] =
1977 typeof parsedKeywords[keyword] !== "undefined"
1978 ? parsedKeywords[keyword] + 1
1979 : 0;
1980 const keywords =
1981 /** @type {Record<string, number>} */
1982 (knownProperties.get(propertyName));
1983
1984 if (
1985 keywords[keyword] &&
1986 parsedKeywords[keyword] < keywords[keyword]
1987 ) {
1988 return end;
1989 }
1990
1991 values.push([start, end]);
1992 return end;
1993 },
1994 comma(_input, _start, end) {
1995 parsedKeywords = {};
1996
1997 return end;
1998 }
1999 },
2000 {
2001 function: (input, start, end) => {
2002 const name = input
2003 .slice(start, end - 1)
2004 .replace(/\\/g, "")
2005 .toLowerCase();
2006
2007 const type =
2008 name === "local" ? 1 : name === "global" ? 2 : undefined;
2009
2010 if (type) {
2011 return processLocalOrGlobalFunction(input, type, start, end);
2012 }
2013
2014 if (
2015 this.options.dashedIdents &&
2016 isLocalMode() &&
2017 name === "var"
2018 ) {
2019 return processDashedIdent(input, end, end);
2020 }
2021
2022 if (this.options.url) {
2023 if (name === "src" || name === "url") {
2024 return processURLFunction(input, end, name);
2025 } else if (IMAGE_SET_FUNCTION.test(name)) {
2026 return processImageSetFunction(input, start, end);
2027 }
2028 }
2029
2030 return end;
2031 }
2032 },
2033 {
2034 onlyTopLevel: !isGridTemplate,
2035 declarationValue: true
2036 }
2037 );
2038
2039 if (values.length > 0) {
2040 for (const value of values) {
2041 const { line: sl, column: sc } = locConverter.get(value[0]);
2042 const { line: el, column: ec } = locConverter.get(value[1]);
2043 const [start, end, isString] = value;
2044 const name = unescapeIdentifierCached(
2045 isString
2046 ? input.slice(start + 1, end - 1)
2047 : input.slice(start, end)
2048 );
2049 const dep = new CssIcssExportDependency(
2050 name,
2051 getReexport(name),
2052 [start, end],
2053 true,
2054 CssIcssExportDependency.EXPORT_MODE.ONCE,
2055 isGridProperty
2056 ? CssIcssExportDependency.EXPORT_TYPE.GRID_CUSTOM_IDENTIFIER
2057 : CssIcssExportDependency.EXPORT_TYPE.NORMAL
2058 );
2059 dep.setLoc(sl, sc, el, ec);
2060 module.addDependency(dep);
2061 }
2062 }
2063
2064 return end;
2065 } else if (COMPOSES_PROPERTY.test(propertyName)) {
2066 if (lastLocalIdentifiers.length > 1) {
2067 const end = eatUntilSemi(input, pos);
2068 this._emitWarning(
2069 state,
2070 `Composition is only allowed when selector is single local class name not in "${lastLocalIdentifiers.join('", "')}"`,
2071 locConverter,
2072 pos,
2073 end
2074 );
2075
2076 return end;
2077 }
2078
2079 if (lastLocalIdentifiers.length !== 1) return pos;
2080
2081 const lastLocalIdentifier = lastLocalIdentifiers[0];
2082 let end = pos;
2083
2084 /** @type {Set<[number, number, boolean]>} */
2085 const classNames = new Set();
2086
2087 while (true) {
2088 pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
2089
2090 let className = walkCssTokens.eatIdentSequence(input, pos);
2091
2092 const ifFunction =
2093 className && input.charCodeAt(className[1]) === CC_LEFT_PARENTHESIS;
2094 let isGlobalFunction = false;
2095
2096 if (className && ifFunction) {
2097 const name = input
2098 .slice(className[0], className[1])
2099 .replace(/\\/g, "")
2100 .toLowerCase();
2101
2102 isGlobalFunction = name === "global";
2103 pos = walkCssTokens.eatWhitespaceAndComments(
2104 input,
2105 className[1] + 1
2106 )[0];
2107 className = walkCssTokens.eatIdentSequence(input, pos);
2108 if (className) {
2109 pos = walkCssTokens.eatWhitespaceAndComments(
2110 input,
2111 className[1]
2112 )[0];
2113 pos += 1;
2114 }
2115 } else if (className) {
2116 pos = walkCssTokens.eatWhitespaceAndComments(
2117 input,
2118 className[1]
2119 )[0];
2120 pos = className[1];
2121 }
2122
2123 // True when we have multiple values
2124 const isComma = input.charCodeAt(pos) === CC_COMMA;
2125 const isSemicolon = input.charCodeAt(pos) === CC_SEMICOLON;
2126 const isRightCurly = input.charCodeAt(pos) === CC_RIGHT_CURLY;
2127
2128 if (isComma || isSemicolon || isRightCurly) {
2129 if (className) {
2130 classNames.add([className[0], className[1], isGlobalFunction]);
2131 }
2132
2133 for (const entry of classNames) {
2134 const [start, end, isGlobal] = entry;
2135 const identifier = unescapeIdentifierCached(
2136 input.slice(start, end)
2137 );
2138 const dep = new CssIcssExportDependency(
2139 lastLocalIdentifier,
2140 getReexport(identifier),
2141 [start, end],
2142 !isGlobal,
2143 isGlobal
2144 ? CssIcssExportDependency.EXPORT_MODE.APPEND
2145 : CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE,
2146 CssIcssExportDependency.EXPORT_TYPE.COMPOSES
2147 );
2148 const { line: sl, column: sc } = locConverter.get(start);
2149 const { line: el, column: ec } = locConverter.get(end);
2150 dep.setLoc(sl, sc, el, ec);
2151 module.addDependency(dep);
2152 }
2153
2154 classNames.clear();
2155
2156 if (isSemicolon || isRightCurly) {
2157 end = isSemicolon
2158 ? walkCssTokens.eatWhitespace(input, pos + 1)
2159 : pos;
2160 break;
2161 }
2162
2163 pos += 1;
2164 } else if (
2165 classNames.size > 0 &&
2166 className &&
2167 input.slice(className[0], className[1]).toLowerCase() === "from"
2168 ) {
2169 let from = walkCssTokens.eatString(input, pos);
2170
2171 if (from) {
2172 const request = input.slice(from[0] + 1, from[1] - 1);
2173 const selfReference = isSelfReferenceRequest(request);
2174
2175 if (!selfReference && !currentRuleComposesFiles.has(request)) {
2176 currentRuleComposesFiles.add(request);
2177 if (
2178 currentRulePrevComposesFile !== undefined &&
2179 currentRulePrevComposesFile !== request
2180 ) {
2181 let successors = composesGraph.get(
2182 currentRulePrevComposesFile
2183 );
2184 if (!successors) {
2185 successors = new Set();
2186 composesGraph.set(currentRulePrevComposesFile, successors);
2187 }
2188 successors.add(request);
2189 }
2190 currentRulePrevComposesFile = request;
2191 }
2192
2193 for (const entry of classNames) {
2194 const [start, end] = entry;
2195 const identifier = unescapeIdentifierCached(
2196 input.slice(start, end)
2197 );
2198 const { line: sl, column: sc } = locConverter.get(start);
2199 const { line: el, column: ec } = locConverter.get(end);
2200
2201 if (selfReference) {
2202 // `composes: foo from "./self.module.css"` from inside
2203 // `self.module.css` — collapse to a self-reference, like
2204 // `composes: foo` without `from`. When the composed name
2205 // equals the local class name, it's a true no-op.
2206 if (identifier === lastLocalIdentifier) continue;
2207 const dep = new CssIcssExportDependency(
2208 lastLocalIdentifier,
2209 getReexport(identifier),
2210 [start, end],
2211 true,
2212 CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE,
2213 CssIcssExportDependency.EXPORT_TYPE.COMPOSES
2214 );
2215 dep.setLoc(sl, sc, el, ec);
2216 module.addDependency(dep);
2217 continue;
2218 }
2219
2220 const localName = `__ICSS_IMPORT_${counter++}__`;
2221
2222 {
2223 const dep = new CssIcssImportDependency(
2224 request,
2225 [start, end],
2226 /** @type {"local" | "global"} */
2227 (mode),
2228 identifier,
2229 localName
2230 );
2231 dep.setLoc(sl, sc, el, ec);
2232 module.addDependency(dep);
2233 if (!composesFirstFileImport.has(request)) {
2234 composesFirstFileImport.set(request, dep);
2235 }
2236 }
2237
2238 {
2239 const dep = new CssIcssExportDependency(
2240 lastLocalIdentifier,
2241 getReexport(identifier, localName),
2242 [start, end],
2243 true,
2244 CssIcssExportDependency.EXPORT_MODE.APPEND,
2245 CssIcssExportDependency.EXPORT_TYPE.COMPOSES
2246 );
2247 dep.setLoc(sl, sc, el, ec);
2248 module.addDependency(dep);
2249 }
2250 }
2251
2252 classNames.clear();
2253 pos = from[1];
2254 } else {
2255 from = walkCssTokens.eatIdentSequence(input, pos);
2256
2257 if (from && input.slice(from[0], from[1]) === "global") {
2258 for (const entry of classNames) {
2259 const [start, end] = entry;
2260 const identifier = unescapeIdentifierCached(
2261 input.slice(start, end)
2262 );
2263 const dep = new CssIcssExportDependency(
2264 /** @type {string} */
2265 (lastLocalIdentifier),
2266 getReexport(identifier),
2267 [start, end],
2268 false,
2269 CssIcssExportDependency.EXPORT_MODE.APPEND,
2270 CssIcssExportDependency.EXPORT_TYPE.COMPOSES
2271 );
2272 const { line: sl, column: sc } = locConverter.get(start);
2273 const { line: el, column: ec } = locConverter.get(end);
2274 dep.setLoc(sl, sc, el, ec);
2275 module.addDependency(dep);
2276 }
2277
2278 classNames.clear();
2279 pos = from[1];
2280 } else {
2281 const end = eatUntilSemi(input, pos);
2282 this._emitWarning(
2283 state,
2284 "Incorrect composition, expected global keyword or string value",
2285 locConverter,
2286 pos,
2287 end
2288 );
2289 return end;
2290 }
2291 }
2292 } else if (className) {
2293 classNames.add([className[0], className[1], isGlobalFunction]);
2294 } else {
2295 const end = eatUntilSemi(input, pos);
2296 this._emitWarning(
2297 state,
2298 "Incorrect composition, expected class named",
2299 locConverter,
2300 pos,
2301 end
2302 );
2303 return end;
2304 }
2305 }
2306
2307 // Remove `composes` from source code
2308 const dep = new ConstDependency("", [propertyNameStart, end]);
2309 module.addPresentationalDependency(dep);
2310 }
2311
2312 return pos;
2313 };
2314
2315 /**
2316 * Process id selector.
2317 * @param {string} input input
2318 * @param {number} start start position
2319 * @param {number} end end position
2320 * @returns {number} position after handling
2321 */
2322 const processIdSelector = (input, start, end) => {
2323 const valueStart = start + 1;
2324 const name = unescapeIdentifierCached(input.slice(valueStart, end));
2325 const dep = new CssIcssExportDependency(
2326 name,
2327 getReexport(name),
2328 [valueStart, end],
2329 true,
2330 CssIcssExportDependency.EXPORT_MODE.ONCE
2331 );
2332 const { line: sl, column: sc } = locConverter.get(start);
2333 const { line: el, column: ec } = locConverter.get(end);
2334 dep.setLoc(sl, sc, el, ec);
2335 module.addDependency(dep);
2336 if (pureMode) currentSelectorHasLocal = true;
2337 return end;
2338 };
2339
2340 /**
2341 * Process class selector.
2342 * @param {string} input input
2343 * @param {number} start start position
2344 * @param {number} end end position
2345 * @returns {number} position after handling
2346 */
2347 const processClassSelector = (input, start, end) => {
2348 const ident = walkCssTokens.skipCommentsAndEatIdentSequence(input, end);
2349 if (!ident) return end;
2350 const name = unescapeIdentifierCached(input.slice(ident[0], ident[1]));
2351 lastLocalIdentifiers.push(name);
2352 const dep = new CssIcssExportDependency(
2353 name,
2354 getReexport(name),
2355 [ident[0], ident[1]],
2356 true,
2357 CssIcssExportDependency.EXPORT_MODE.ONCE
2358 );
2359 const { line: sl, column: sc } = locConverter.get(ident[0]);
2360 const { line: el, column: ec } = locConverter.get(ident[1]);
2361 dep.setLoc(sl, sc, el, ec);
2362 module.addDependency(dep);
2363 if (pureMode) currentSelectorHasLocal = true;
2364 return ident[1];
2365 };
2366
2367 /**
2368 * Process attribute selector.
2369 * @param {string} input input
2370 * @param {number} start start position
2371 * @param {number} end end position
2372 * @returns {number} position after handling
2373 */
2374 const processAttributeSelector = (input, start, end) => {
2375 end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
2376 const identifier = walkCssTokens.eatIdentSequence(input, end);
2377 if (!identifier) return end;
2378 const name = unescapeIdentifierCached(
2379 input.slice(identifier[0], identifier[1])
2380 );
2381 if (name.toLowerCase() !== "class") {
2382 return end;
2383 }
2384 end = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];
2385
2386 const isTilde = input.charCodeAt(end) === CC_TILDE;
2387
2388 if (
2389 input.charCodeAt(end) !== CC_EQUAL &&
2390 input.charCodeAt(end) !== CC_TILDE
2391 ) {
2392 return end;
2393 }
2394
2395 end += 1;
2396
2397 if (isTilde) {
2398 if (input.charCodeAt(end) !== CC_EQUAL) {
2399 return end;
2400 }
2401
2402 end += 1;
2403 }
2404
2405 end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
2406 const value = walkCssTokens.eatIdentSequenceOrString(input, end);
2407
2408 if (!value) {
2409 return end;
2410 }
2411
2412 const classNameStart = value[2] ? value[0] : value[0] + 1;
2413 const classNameEnd = value[2] ? value[1] : value[1] - 1;
2414 const className = unescapeIdentifierCached(
2415 input.slice(classNameStart, classNameEnd)
2416 );
2417 const dep = new CssIcssExportDependency(
2418 className,
2419 getReexport(className),
2420 [classNameStart, classNameEnd],
2421 true,
2422 CssIcssExportDependency.EXPORT_MODE.NONE
2423 );
2424 const { line: sl, column: sc } = locConverter.get(classNameStart);
2425 const { line: el, column: ec } = locConverter.get(classNameEnd);
2426 dep.setLoc(sl, sc, el, ec);
2427 module.addDependency(dep);
2428 return value[2] ? classNameEnd : classNameEnd + 1;
2429 };
2430
2431 walkCssTokens(source, 0, {
2432 comment,
2433 leftCurlyBracket: (input, start, end) => {
2434 const wasTopLevel = scope === CSS_MODE_TOP_LEVEL;
2435 if (wasTopLevel) {
2436 allowImportAtRule = false;
2437 scope = CSS_MODE_IN_BLOCK;
2438 } else if (scope !== CSS_MODE_IN_BLOCK) {
2439 return end;
2440 }
2441 if (!isModules) return end;
2442 if (pureMode) {
2443 inAtRulePrelude = false;
2444 if (wasTopLevel) seenTopLevelRule = true;
2445 const isRulePrelude = isNextRulePrelude;
2446 if (isRulePrelude) finalizeSelector();
2447 const top = pureTop();
2448 if (top) top.hasNestedBlock = true;
2449 const inheritedSkip = top ? top.skipChildren : false;
2450 pureBlockStack.push({
2451 ignored: pureIgnorePending,
2452 skipOwn: inheritedSkip,
2453 skipChildren: nextBlockChildrenSkip || inheritedSkip,
2454 treatAsLeaf: nextBlockTreatAsLeaf,
2455 // "this rule is fully pure" (no impure comma-segment) OR any
2456 // ancestor pure. Matches PCSL's `[isPureSelectorSymbol]`.
2457 ancestorHadLocal:
2458 parentEffectivePure() ||
2459 (isRulePrelude && !currentRuleHasImpureSelector),
2460 impure: isRulePrelude && currentRuleHasImpureSelector,
2461 hasDirectDecl: false,
2462 hasNestedBlock: false,
2463 isRulePrelude,
2464 preludeStart: currentRulePreludeStart,
2465 preludeEnd: start
2466 });
2467 pureIgnorePending = false;
2468 nextBlockChildrenSkip = false;
2469 nextBlockTreatAsLeaf = false;
2470 currentRuleHasImpureSelector = false;
2471 currentSelectorHasLocal = false;
2472 currentRulePreludeStart = end;
2473 }
2474 blockNestingLevel = wasTopLevel ? 1 : blockNestingLevel + 1;
2475 isNextRulePrelude = isNextNestedSyntax(input, end);
2476 return end;
2477 },
2478 rightCurlyBracket: (input, start, end) => {
2479 if (scope !== CSS_MODE_IN_BLOCK) return end;
2480 const closing = blockNestingLevel === 1;
2481 if (closing) {
2482 scope = CSS_MODE_TOP_LEVEL;
2483 blockNestingLevel = 0;
2484 if (!isModules) return end;
2485 isNextRulePrelude = true;
2486 modeData = undefined;
2487 lastLocalIdentifiers = [];
2488 currentRulePrevComposesFile = undefined;
2489 currentRuleComposesFiles.clear();
2490 } else {
2491 blockNestingLevel--;
2492 if (!isModules) return end;
2493 isNextRulePrelude = isNextNestedSyntax(input, end);
2494 }
2495 if (pureMode) {
2496 const frame = pureBlockStack.pop();
2497 if (frame) {
2498 // PCSL throws on impure rules whose body has any non-rule
2499 // content (declaration, empty body). Rules whose body is
2500 // only nested rules are skipped — child rules carry the
2501 // check themselves.
2502 if (
2503 !pureNoCheck &&
2504 !frame.ignored &&
2505 !frame.skipOwn &&
2506 frame.isRulePrelude &&
2507 frame.impure &&
2508 (frame.hasDirectDecl ||
2509 !frame.hasNestedBlock ||
2510 frame.treatAsLeaf)
2511 ) {
2512 reportPureRule(frame.preludeStart, frame.preludeEnd);
2513 }
2514 // Propagate "has direct declaration" through at-rule frames
2515 // so a parent rule containing only e.g. `@media { decl }` is
2516 // still treated as "rule with declarations".
2517 if (!frame.isRulePrelude && frame.hasDirectDecl) {
2518 const parent = pureTop();
2519 if (parent) parent.hasDirectDecl = true;
2520 }
2521 }
2522 currentRuleHasImpureSelector = false;
2523 currentSelectorHasLocal = false;
2524 currentRulePreludeStart = end;
2525 }
2526 return end;
2527 },
2528 url: (input, start, end, contentStart, contentEnd) => {
2529 if (!this.options.url) {
2530 return end;
2531 }
2532
2533 return processOldURLFunction(
2534 input,
2535 start,
2536 end,
2537 contentStart,
2538 contentEnd
2539 );
2540 },
2541 atKeyword: (input, start, end) => {
2542 const name = input.slice(start, end).toLowerCase();
2543 const wasTopLevel = scope === CSS_MODE_TOP_LEVEL;
2544 if (pureMode) {
2545 inAtRulePrelude = true;
2546 // Match PCSL's `isPureCheckDisabled`: any non-comment top-level
2547 // node (including `;`-terminated at-rules like `@import`) seals
2548 // the leading-comments window.
2549 if (wasTopLevel) seenTopLevelRule = true;
2550 }
2551
2552 let pos = end;
2553 switch (name) {
2554 case "@namespace": {
2555 this._emitWarning(
2556 state,
2557 "'@namespace' is not supported in bundled CSS",
2558 locConverter,
2559 start,
2560 end
2561 );
2562
2563 pos = eatUntilSemi(input, start);
2564 break;
2565 }
2566 case "@charset": {
2567 const atRuleEnd = eatUntilSemi(input, start);
2568
2569 if (/** @type {CssModule} */ (module).exportType === "style") {
2570 pos = atRuleEnd;
2571 break;
2572 }
2573
2574 const dep = new ConstDependency("", [start, atRuleEnd + 1]);
2575 module.addPresentationalDependency(dep);
2576
2577 const value = walkCssTokens.eatString(input, end);
2578
2579 if (!value) {
2580 pos = atRuleEnd;
2581 break;
2582 }
2583
2584 /** @type {BuildInfo} */
2585 (module.buildInfo).charset = input
2586 .slice(value[0] + 1, value[1] - 1)
2587 .toUpperCase();
2588
2589 pos = atRuleEnd;
2590 break;
2591 }
2592 case "@import": {
2593 if (!this.options.import) {
2594 pos = eatUntilSemi(input, end);
2595 break;
2596 }
2597
2598 if (!allowImportAtRule) {
2599 this._emitWarning(
2600 state,
2601 "Any '@import' rules must precede all other rules",
2602 locConverter,
2603 start,
2604 end
2605 );
2606 pos = eatUntilSemi(input, end);
2607 break;
2608 }
2609
2610 pos = processAtImport(input, start, end);
2611 break;
2612 }
2613 default: {
2614 if (isModules) {
2615 if (name === "@value") {
2616 pos = processAtValue(input, start, end);
2617 break;
2618 } else if (
2619 this.options.animation &&
2620 OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) &&
2621 isLocalMode()
2622 ) {
2623 if (pureMode) {
2624 nextBlockChildrenSkip = true;
2625 nextBlockTreatAsLeaf = true;
2626 }
2627 pos = processLocalAtRule(input, end, {
2628 string: true,
2629 identifier: true
2630 });
2631 break;
2632 } else if (
2633 this.options.customIdents &&
2634 name === "@counter-style" &&
2635 isLocalMode()
2636 ) {
2637 if (pureMode) {
2638 nextBlockChildrenSkip = true;
2639 nextBlockTreatAsLeaf = true;
2640 }
2641 pos = processLocalAtRule(input, end, {
2642 identifier: true
2643 });
2644 break;
2645 } else if (
2646 this.options.container &&
2647 name === "@container" &&
2648 isLocalMode()
2649 ) {
2650 pos = processLocalAtRule(input, end, {
2651 identifier: /^(none|and|or|not)$/
2652 });
2653 break;
2654 } else if (name === "@scope") {
2655 isNextRulePrelude = true;
2656 break;
2657 }
2658
2659 isNextRulePrelude = false;
2660 }
2661 }
2662 }
2663
2664 // If the at-rule consumed its own `;` (for `@import`/`@value`/
2665 // `@charset`/`@namespace`), advance the prelude pointer so a
2666 // later impure rule's reported selector doesn't include this
2667 // at-rule's text. Body-bearing at-rules return at `{` — let
2668 // `leftCurlyBracket` handle those.
2669 if (pureMode && wasTopLevel && pos > end) {
2670 let probe = pos - 1;
2671 while (
2672 probe > end &&
2673 walkCssTokens.isWhiteSpace(input.charCodeAt(probe))
2674 ) {
2675 probe--;
2676 }
2677 if (input.charCodeAt(probe) === CC_SEMICOLON) {
2678 currentRulePreludeStart = pos;
2679 }
2680 }
2681
2682 return pos;
2683 },
2684 semicolon: (input, start, end) => {
2685 if (isModules && scope === CSS_MODE_IN_BLOCK) {
2686 isNextRulePrelude = isNextNestedSyntax(input, end);
2687 }
2688 if (pureMode) {
2689 if (scope === CSS_MODE_IN_BLOCK) {
2690 if (
2691 balanced.length === 0 &&
2692 !isNextRulePrelude &&
2693 !inAtRulePrelude
2694 ) {
2695 const top = pureTop();
2696 if (top) top.hasDirectDecl = true;
2697 }
2698 } else if (scope === CSS_MODE_TOP_LEVEL && balanced.length === 0) {
2699 // Top-level `;` ends a statement (e.g. `@import "x";`).
2700 // Advance the prelude pointer so a later impure rule's
2701 // reported selector doesn't include the preceding text.
2702 currentRulePreludeStart = end;
2703 }
2704 inAtRulePrelude = false;
2705 }
2706 return end;
2707 },
2708 identifier: (input, start, end) => {
2709 if (isModules) {
2710 const identifier = input.slice(start, end);
2711
2712 if (
2713 this.options.dashedIdents &&
2714 isLocalMode() &&
2715 isDashedIdentifier(identifier)
2716 ) {
2717 return processDashedIdent(input, start, end);
2718 }
2719
2720 if (icssDefinitions.has(identifier)) {
2721 return processICSSSymbol(identifier, start, end);
2722 }
2723
2724 switch (scope) {
2725 case CSS_MODE_IN_BLOCK: {
2726 if (isModules && !isNextRulePrelude) {
2727 if (balanced.length === 0 && !inAtRulePrelude) {
2728 const top = pureTop();
2729 if (top) top.hasDirectDecl = true;
2730 }
2731 // Handle only top level values and not inside functions
2732 return processLocalDeclaration(input, start, end);
2733 }
2734 break;
2735 }
2736 }
2737 }
2738
2739 return end;
2740 },
2741 delim: (input, start, end) => {
2742 const ch = input.charCodeAt(start);
2743 if (ch === CC_FULL_STOP && isNextRulePrelude && isLocalMode()) {
2744 return processClassSelector(input, start, end);
2745 }
2746 if (
2747 ch === CC_AMPERSAND &&
2748 isNextRulePrelude &&
2749 parentEffectivePure() &&
2750 pureMode
2751 ) {
2752 currentSelectorHasLocal = true;
2753 }
2754
2755 return end;
2756 },
2757 hash: (input, start, end, isID) => {
2758 if (isNextRulePrelude && isLocalMode() && isID) {
2759 return processIdSelector(input, start, end);
2760 }
2761
2762 return end;
2763 },
2764 colon: (input, start, end) => {
2765 if (isModules) {
2766 const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
2767 input,
2768 end
2769 );
2770 if (!ident) return end;
2771 const name = input.slice(ident[0], ident[1]).toLowerCase();
2772
2773 switch (scope) {
2774 case CSS_MODE_TOP_LEVEL: {
2775 if (name === "import") {
2776 const pos = processImportOrExport(0, input, ident[1]);
2777 const dep = new ConstDependency("", [start, pos]);
2778 module.addPresentationalDependency(dep);
2779 return pos;
2780 } else if (name === "export") {
2781 const pos = processImportOrExport(1, input, ident[1]);
2782 const dep = new ConstDependency("", [start, pos]);
2783 module.addPresentationalDependency(dep);
2784 return pos;
2785 }
2786 }
2787 // falls through
2788 default: {
2789 if (isNextRulePrelude) {
2790 const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS;
2791
2792 if (isFn && name === "local") {
2793 // Eat extra whitespace
2794 const end = walkCssTokens.eatWhitespaceAndComments(
2795 input,
2796 ident[1] + 1
2797 )[0];
2798 modeData = LOCAL_MODE;
2799 const dep = new ConstDependency("", [start, end]);
2800 module.addPresentationalDependency(dep);
2801 balanced.push([":local", start, end, true]);
2802 return end;
2803 } else if (name === "local") {
2804 modeData = LOCAL_MODE;
2805 const found = walkCssTokens.eatWhitespaceAndComments(
2806 input,
2807 ident[1]
2808 );
2809
2810 if (!found[1]) {
2811 this._emitWarning(
2812 state,
2813 `Missing whitespace after ':local' in '${input.slice(
2814 start,
2815 eatUntilLeftCurly(input, end) + 1
2816 )}'`,
2817 locConverter,
2818 start,
2819 end
2820 );
2821 }
2822
2823 end = walkCssTokens.eatWhitespace(input, ident[1]);
2824 const dep = new ConstDependency("", [start, end]);
2825 module.addPresentationalDependency(dep);
2826 return end;
2827 } else if (isFn && name === "global") {
2828 // Eat extra whitespace
2829 const end = walkCssTokens.eatWhitespaceAndComments(
2830 input,
2831 ident[1] + 1
2832 )[0];
2833 modeData = GLOBAL_MODE;
2834 const dep = new ConstDependency("", [start, end]);
2835 module.addPresentationalDependency(dep);
2836 balanced.push([":global", start, end, true]);
2837 return end;
2838 } else if (name === "global") {
2839 modeData = GLOBAL_MODE;
2840 // Eat extra whitespace
2841 const found = walkCssTokens.eatWhitespaceAndComments(
2842 input,
2843 ident[1]
2844 );
2845
2846 if (!found[1]) {
2847 this._emitWarning(
2848 state,
2849 `Missing whitespace after ':global' in '${input.slice(
2850 start,
2851 eatUntilLeftCurly(input, end) + 1
2852 )}'`,
2853 locConverter,
2854 start,
2855 end
2856 );
2857 }
2858
2859 end = walkCssTokens.eatWhitespace(input, ident[1]);
2860 const dep = new ConstDependency("", [start, end]);
2861 module.addPresentationalDependency(dep);
2862 return end;
2863 }
2864 }
2865 }
2866 }
2867 }
2868
2869 lastTokenEndForComments = end;
2870
2871 return end;
2872 },
2873 function: (input, start, end) => {
2874 const name = input
2875 .slice(start, end - 1)
2876 .replace(/\\/g, "")
2877 .toLowerCase();
2878
2879 balanced.push([name, start, end]);
2880
2881 switch (name) {
2882 case "src":
2883 case "url": {
2884 if (!this.options.url) {
2885 return end;
2886 }
2887
2888 return processURLFunction(input, end, name);
2889 }
2890 default: {
2891 if (this.options.url && IMAGE_SET_FUNCTION.test(name)) {
2892 return processImageSetFunction(input, start, end);
2893 }
2894
2895 if (isModules) {
2896 if (
2897 this.options.function &&
2898 isLocalMode() &&
2899 isDashedIdentifier(name)
2900 ) {
2901 return processDashedIdent(input, start, end);
2902 }
2903
2904 const type =
2905 name === "local" ? 1 : name === "global" ? 2 : undefined;
2906
2907 if (type && !isNextRulePrelude) {
2908 return processLocalOrGlobalFunction(input, type, start, end);
2909 }
2910 }
2911 }
2912 }
2913
2914 return end;
2915 },
2916 leftSquareBracket: (input, start, end) => {
2917 if (isNextRulePrelude && isLocalMode()) {
2918 return processAttributeSelector(input, start, end);
2919 }
2920 return end;
2921 },
2922 leftParenthesis: (input, start, end) => {
2923 balanced.push(["(", start, end]);
2924
2925 return end;
2926 },
2927 rightParenthesis: (input, start, end) => {
2928 const popped = balanced.pop();
2929
2930 if (isModules && popped) {
2931 const isLocal = popped[0] === ":local";
2932 const isGlobal = popped[0] === ":global";
2933 if (isLocal || isGlobal) {
2934 modeData = balanced[balanced.length - 1]
2935 ? balanced[balanced.length - 1][0] === ":local"
2936 ? LOCAL_MODE
2937 : balanced[balanced.length - 1][0] === ":global"
2938 ? GLOBAL_MODE
2939 : undefined
2940 : undefined;
2941 if (popped[3] && isLocal) {
2942 while (walkCssTokens.isWhiteSpace(input.charCodeAt(start - 1))) {
2943 start -= 1;
2944 }
2945 }
2946 const dep = new ConstDependency("", [start, end]);
2947 module.addPresentationalDependency(dep);
2948 } else if (isNextRulePrelude) {
2949 modeData = undefined;
2950 }
2951 }
2952
2953 return end;
2954 },
2955 comma: (input, start, end) => {
2956 if (isModules && balanced.length === 0) {
2957 // Reset stack for `:global .class :local .class-other` selector after
2958 modeData = undefined;
2959 if (pureMode && isNextRulePrelude) finalizeSelector();
2960 }
2961
2962 lastTokenEndForComments = start;
2963
2964 return end;
2965 }
2966 });
2967
2968 /** @type {BuildInfo} */
2969 (module.buildInfo).strict = true;
2970
2971 // Topologically sort the files referenced by `composes ... from`
2972 // declarations and tag each file's first import dep with the
2973 // resulting `sourceOrder`. `NormalModule#build` then reorders the
2974 // deps via `sortWithSourceOrder` so the bundle loads them in
2975 // cascade-correct order. Files stuck in a cycle are not visited
2976 // and keep their natural loc-based position.
2977 if (composesFirstFileImport.size > 1) {
2978 topologicalSort(
2979 composesGraph,
2980 [...composesFirstFileImport.keys()],
2981 (file, i) => {
2982 /** @type {CssIcssImportDependency} */
2983 (composesFirstFileImport.get(file)).sourceOrder = i;
2984 }
2985 );
2986 }
2987
2988 const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
2989
2990 buildMeta.exportsType = this.options.namedExports ? "namespace" : "default";
2991 buildMeta.defaultObject = this.options.namedExports
2992 ? false
2993 : "redirect-warn";
2994
2995 if (
2996 /** @type {CssModule} */ (module).exportType === "text" ||
2997 /** @type {CssModule} */ (module).exportType === "css-style-sheet"
2998 ) {
2999 module.addDependency(new StaticExportsDependency(["default"], true));
3000 } else {
3001 module.addDependency(new StaticExportsDependency([], true));
3002 }
3003
3004 return state;
3005 }
3006
3007 /**
3008 * Returns comments in the range.
3009 * @param {Range} range range
3010 * @returns {Comment[]} comments in the range
3011 */
3012 getComments(range) {
3013 if (!this.comments) return [];
3014 const [rangeStart, rangeEnd] = range;
3015 /**
3016 * Returns compared.
3017 * @param {Comment} comment comment
3018 * @param {number} needle needle
3019 * @returns {number} compared
3020 */
3021 const compare = (comment, needle) =>
3022 /** @type {Range} */ (comment.range)[0] - needle;
3023 const comments = /** @type {Comment[]} */ (this.comments);
3024 let idx = binarySearchBounds.ge(comments, rangeStart, compare);
3025 /** @type {Comment[]} */
3026 const commentsInRange = [];
3027 while (
3028 comments[idx] &&
3029 /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
3030 ) {
3031 commentsInRange.push(comments[idx]);
3032 idx++;
3033 }
3034
3035 return commentsInRange;
3036 }
3037
3038 /**
3039 * Parses comment options.
3040 * @param {Range} range range of the comment
3041 * @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
3042 */
3043 parseCommentOptions(range) {
3044 const comments = this.getComments(range);
3045 if (comments.length === 0) {
3046 return EMPTY_COMMENT_OPTIONS;
3047 }
3048 /** @type {Record<string, EXPECTED_ANY>} */
3049 const options = {};
3050 /** @type {(Error & { comment: Comment })[]} */
3051 const errors = [];
3052 for (const comment of comments) {
3053 const { value } = comment;
3054 if (value && webpackCommentRegExp.test(value)) {
3055 // try compile only if webpack options comment is present
3056 try {
3057 for (let [key, val] of Object.entries(
3058 vm.runInContext(
3059 `(function(){return {${value}};})()`,
3060 this.magicCommentContext
3061 )
3062 )) {
3063 if (typeof val === "object" && val !== null) {
3064 val =
3065 val.constructor.name === "RegExp"
3066 ? new RegExp(val)
3067 : JSON.parse(JSON.stringify(val));
3068 }
3069 options[key] = val;
3070 }
3071 } catch (err) {
3072 const newErr = new Error(String(/** @type {Error} */ (err).message));
3073 newErr.stack = String(/** @type {Error} */ (err).stack);
3074 Object.assign(newErr, { comment });
3075 errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
3076 }
3077 }
3078 }
3079 return { options, errors };
3080 }
3081}
3082
3083module.exports = CssParser;
3084module.exports.escapeIdentifier = escapeIdentifier;
3085module.exports.unescapeIdentifier = unescapeIdentifier;
Note: See TracBrowser for help on using the repository browser.