πŸ“„ CssParser.js
/home/palash/git/site/node_modules/webpack/lib/css/CssParser.js
Language: js β€’ Lines: 3085
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const path = require("path");
const vm = require("vm");
const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
const Parser = require("../Parser");
const ConstDependency = require("../dependencies/ConstDependency");
const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const CommentCompilationWarning = require("../errors/CommentCompilationWarning");
const ModuleDependencyWarning = require("../errors/ModuleDependencyWarning");
const UnsupportedFeatureWarning = require("../errors/UnsupportedFeatureWarning");
const WebpackError = require("../errors/WebpackError");
const LocConverter = require("../util/LocConverter");
const binarySearchBounds = require("../util/binarySearchBounds");
const { parseResource } = require("../util/identifier");
const {
	createMagicCommentContext,
	webpackCommentRegExp
} = require("../util/magicComment");
const topologicalSort = require("../util/topologicalSort");
const walkCssTokens = require("./walkCssTokens");

/** @typedef {import("../Module").BuildInfo} BuildInfo */
/** @typedef {import("../Module").BuildMeta} BuildMeta */
/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
/** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */
/** @typedef {import("../../declarations/WebpackOptions").CssAutoOrModuleParserOptions} CssAutoOrModuleParserOptions */
/** @typedef {import("../../declarations/WebpackOptions").CssModuleParserOptions} CssModuleParserOptions */
/** @typedef {import("./CssModule")} CssModule */

/** @typedef {[number, number]} Range */
/** @typedef {{ line: number, column: number }} Position */
/** @typedef {{ value: string, range: Range, loc: { start: Position, end: Position } }} Comment */

const CC_COLON = ":".charCodeAt(0);
const CC_SEMICOLON = ";".charCodeAt(0);
const CC_COMMA = ",".charCodeAt(0);
const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
const CC_LOWER_F = "f".charCodeAt(0);
const CC_UPPER_F = "F".charCodeAt(0);
const CC_RIGHT_CURLY = "}".charCodeAt(0);
const CC_HYPHEN_MINUS = "-".charCodeAt(0);
const CC_TILDE = "~".charCodeAt(0);
const CC_EQUAL = "=".charCodeAt(0);
const CC_FULL_STOP = ".".charCodeAt(0);
const CC_EXCLAMATION = "!".charCodeAt(0);
const CC_AMPERSAND = "&".charCodeAt(0);

// https://www.w3.org/TR/css-syntax-3/#newline
// We don't have `preprocessing` stage, so we need specify all of them
const STRING_MULTILINE = /\\[\n\r\f]/g;
// https://www.w3.org/TR/css-syntax-3/#whitespace
const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
const IS_MODULES = /\.modules?\.[^.]+$/i;
const CSS_COMMENT = /\/\*((?!\*\/)[\s\S]*?)\*\//g;

/**
 * Returns matches.
 * @param {RegExp} regexp a regexp
 * @param {string} str a string
 * @returns {RegExpExecArray[]} matches
 */
const matchAll = (regexp, str) => {
	/** @type {RegExpExecArray[]} */
	const result = [];

	/** @type {null | RegExpExecArray} */
	let match;

	// Use a while loop with exec() to find all matches
	while ((match = regexp.exec(str)) !== null) {
		result.push(match);
	}
	// Return an array to be easily iterable (note: a true spec-compliant polyfill
	// returns an iterator object, but an array spread often suffices for basic use)
	return result;
};

/**
 * Returns normalized url.
 * @param {string} str url string
 * @param {boolean} isString is url wrapped in quotes
 * @returns {string} normalized url
 */
const normalizeUrl = (str, isString) => {
	// Remove extra spaces and newlines:
	// `url("im\
	// g.png")`
	if (isString) {
		str = str.replace(STRING_MULTILINE, "");
	}

	str = str
		// Remove unnecessary spaces from `url("   img.png	 ")`
		.replace(TRIM_WHITE_SPACES, "")
		// Unescape
		.replace(UNESCAPE, (match) => {
			if (match.length > 2) {
				return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
			}
			return match[1];
		});

	if (/^data:/i.test(str)) {
		return str;
	}

	if (str.includes("%")) {
		// Convert `url('%2E/img.png')` -> `url('./img.png')`
		try {
			str = decodeURIComponent(str);
		} catch (_err) {
			// Ignore
		}
	}

	return str;
};

const { escapeIdentifier, unescapeIdentifier } = walkCssTokens;

/**
 * A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
 * The <custom-property-name> production corresponds to this:
 * it’s defined as any <dashed-ident> (a valid identifier that starts with two dashes),
 * except -- itself, which is reserved for future use by CSS.
 * @param {string} identifier identifier
 * @returns {boolean} true when identifier is dashed, otherwise false
 */
const isDashedIdentifier = (identifier) =>
	identifier.startsWith("--") && identifier.length >= 3;

/** @type {Record<string, number>} */
const PREDEFINED_COUNTER_STYLES = {
	decimal: 1,
	"decimal-leading-zero": 1,
	"arabic-indic": 1,
	armenian: 1,
	"upper-armenian": 1,
	"lower-armenian": 1,
	bengali: 1,
	cambodian: 1,
	khmer: 1,
	"cjk-decimal": 1,
	devanagari: 1,
	georgian: 1,
	gujarati: 1,
	/* cspell:disable-next-line */
	gurmukhi: 1,
	hebrew: 1,
	kannada: 1,
	lao: 1,
	malayalam: 1,
	mongolian: 1,
	myanmar: 1,
	oriya: 1,
	persian: 1,
	"lower-roman": 1,
	"upper-roman": 1,
	tamil: 1,
	telugu: 1,
	thai: 1,
	tibetan: 1,

	"lower-alpha": 1,
	"lower-latin": 1,
	"upper-alpha": 1,
	"upper-latin": 1,
	"lower-greek": 1,
	hiragana: 1,
	/* cspell:disable-next-line */
	"hiragana-iroha": 1,
	katakana: 1,
	/* cspell:disable-next-line */
	"katakana-iroha": 1,

	disc: 1,
	circle: 1,
	square: 1,
	"disclosure-open": 1,
	"disclosure-closed": 1,

	"cjk-earthly-branch": 1,
	"cjk-heavenly-stem": 1,

	"japanese-informal": 1,
	"japanese-formal": 1,

	"korean-hangul-formal": 1,
	/* cspell:disable-next-line */
	"korean-hanja-informal": 1,
	/* cspell:disable-next-line */
	"korean-hanja-formal": 1,

	"simp-chinese-informal": 1,
	"simp-chinese-formal": 1,
	"trad-chinese-informal": 1,
	"trad-chinese-formal": 1,
	"cjk-ideographic": 1,

	"ethiopic-numeric": 1
};

/** @type {Record<string, number>} */
const GLOBAL_VALUES = {
	// Global values
	initial: Infinity,
	inherit: Infinity,
	unset: Infinity,
	revert: Infinity,
	"revert-layer": Infinity
};

/** @type {Record<string, number>} */
const GRID_AREA_OR_COLUMN_OR_ROW = {
	auto: Infinity,
	span: Infinity,
	...GLOBAL_VALUES
};

/** @type {Record<string, number>} */
const GRID_AUTO_COLUMNS_OR_ROW = {
	"min-content": Infinity,
	"max-content": Infinity,
	auto: Infinity,
	...GLOBAL_VALUES
};

/** @type {Record<string, number>} */
const GRID_AUTO_FLOW = {
	row: 1,
	column: 1,
	dense: 1,
	...GLOBAL_VALUES
};

/** @type {Record<string, number>} */
const GRID_TEMPLATE_AREAS = {
	// Special
	none: 1,
	...GLOBAL_VALUES
};

/** @type {Record<string, number>} */
const GRID_TEMPLATE_COLUMNS_OR_ROWS = {
	none: 1,
	subgrid: 1,
	masonry: 1,
	"max-content": Infinity,
	"min-content": Infinity,
	auto: Infinity,
	...GLOBAL_VALUES
};

/** @type {Record<string, number>} */
const GRID_TEMPLATE = {
	...GRID_TEMPLATE_AREAS,
	...GRID_TEMPLATE_COLUMNS_OR_ROWS
};

/** @type {Record<string, number>} */
const GRID = {
	"auto-flow": 1,
	dense: 1,
	...GRID_AUTO_COLUMNS_OR_ROW,
	...GRID_AUTO_FLOW,
	...GRID_TEMPLATE_AREAS,
	...GRID_TEMPLATE_COLUMNS_OR_ROWS
};

/**
 * Gets known properties.
 * @param {{ animation?: boolean, container?: boolean, customIdents?: boolean, grid?: boolean }=} options options
 * @returns {Map<string, Record<string, number>>} list of known properties
 */
const getKnownProperties = (options = {}) => {
	/** @type {Map<string, Record<string, number>>} */
	const knownProperties = new Map();

	if (options.animation) {
		knownProperties.set("animation", {
			// animation-direction
			normal: 1,
			reverse: 1,
			alternate: 1,
			"alternate-reverse": 1,
			// animation-fill-mode
			forwards: 1,
			backwards: 1,
			both: 1,
			// animation-iteration-count
			infinite: 1,
			// animation-play-state
			paused: 1,
			running: 1,
			// animation-timing-function
			ease: 1,
			"ease-in": 1,
			"ease-out": 1,
			"ease-in-out": 1,
			linear: 1,
			"step-end": 1,
			"step-start": 1,
			// Special
			none: Infinity, // No matter how many times you write none, it will never be an animation name
			...GLOBAL_VALUES
		});
		knownProperties.set("animation-name", {
			// Special
			none: Infinity, // No matter how many times you write none, it will never be an animation name
			...GLOBAL_VALUES
		});
	}

	if (options.container) {
		knownProperties.set("container", {
			// container-type
			normal: 1,
			size: 1,
			"inline-size": 1,
			"scroll-state": 1,
			// Special
			none: Infinity,
			...GLOBAL_VALUES
		});
		knownProperties.set("container-name", {
			// Special
			none: Infinity,
			...GLOBAL_VALUES
		});
	}

	if (options.customIdents) {
		knownProperties.set("list-style", {
			// list-style-position
			inside: 1,
			outside: 1,
			// list-style-type
			...PREDEFINED_COUNTER_STYLES,
			// Special
			none: Infinity,
			...GLOBAL_VALUES
		});
		knownProperties.set("list-style-type", {
			// list-style-type
			...PREDEFINED_COUNTER_STYLES,
			// Special
			none: Infinity,
			...GLOBAL_VALUES
		});
		knownProperties.set("system", {
			cyclic: 1,
			numeric: 1,
			alphabetic: 1,
			symbolic: 1,
			additive: 1,
			fixed: 1,
			extends: 1,
			...PREDEFINED_COUNTER_STYLES
		});
		knownProperties.set("fallback", {
			...PREDEFINED_COUNTER_STYLES
		});
		knownProperties.set("speak-as", {
			auto: 1,
			bullets: 1,
			numbers: 1,
			words: 1,
			"spell-out": 1,
			...PREDEFINED_COUNTER_STYLES
		});
	}

	if (options.grid) {
		knownProperties.set("grid", GRID);
		knownProperties.set("grid-area", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-column", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-column-end", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-column-start", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-row", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-row-end", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-row-start", GRID_AREA_OR_COLUMN_OR_ROW);
		knownProperties.set("grid-template", GRID_TEMPLATE);
		knownProperties.set("grid-template-areas", GRID_TEMPLATE_AREAS);
		knownProperties.set("grid-template-columns", GRID_TEMPLATE_COLUMNS_OR_ROWS);
		knownProperties.set("grid-template-rows", GRID_TEMPLATE_COLUMNS_OR_ROWS);
	}

	return knownProperties;
};

const EMPTY_COMMENT_OPTIONS = {
	options: null,
	errors: null
};

const CSS_MODE_TOP_LEVEL = 0;
const CSS_MODE_IN_BLOCK = 1;

const LOCAL_MODE = 0;
const GLOBAL_MODE = 1;

const eatUntilSemi = walkCssTokens.eatUntil(";");
const eatUntilLeftCurly = walkCssTokens.eatUntil("{");

/**
 * Defines the css parser own options type used by this module.
 * @typedef {object} CssParserOwnOptions
 * @property {("pure" | "global" | "local" | "auto")=} defaultMode default mode
 */

/** @typedef {CssAutoOrModuleParserOptions & CssParserOwnOptions} CssParserOptions */

class CssParser extends Parser {
	/**
	 * Creates an instance of CssParser.
	 * @param {CssParserOptions=} options options
	 */
	constructor(options = {}) {
		super();
		this.defaultMode =
			typeof options.defaultMode !== "undefined" ? options.defaultMode : "pure";
		this.options = {
			url: true,
			import: true,
			namedExports: true,
			animation: true,
			container: true,
			customIdents: true,
			dashedIdents: true,
			function: true,
			grid: true,
			...options
		};
		/** @type {Comment[] | undefined} */
		this.comments = undefined;
		this.magicCommentContext = createMagicCommentContext();
	}

	/**
	 * Processes the provided state.
	 * @param {ParserState} state parser state
	 * @param {string} message warning message
	 * @param {LocConverter} locConverter location converter
	 * @param {number} start start offset
	 * @param {number} end end offset
	 */
	_emitWarning(state, message, locConverter, start, end) {
		const { line: sl, column: sc } = locConverter.get(start);
		const { line: el, column: ec } = locConverter.get(end);

		state.current.addWarning(
			new ModuleDependencyWarning(state.module, new WebpackError(message), {
				start: { line: sl, column: sc },
				end: { line: el, column: ec }
			})
		);
	}

	/**
	 * Emits a build error for the provided range.
	 * @param {ParserState} state parser state
	 * @param {string} message error message
	 * @param {LocConverter} locConverter location converter
	 * @param {number} start start offset
	 * @param {number} end end offset
	 */
	_emitError(state, message, locConverter, start, end) {
		const { line: sl, column: sc } = locConverter.get(start);
		const { line: el, column: ec } = locConverter.get(end);

		const err = new WebpackError(message);
		err.module = state.module;
		err.loc = {
			start: { line: sl, column: sc },
			end: { line: el, column: ec }
		};
		state.module.addError(err);
	}

	/**
	 * Parses the provided source and updates the parser state.
	 * @param {string | Buffer | PreparsedAst} source the source to parse
	 * @param {ParserState} state the parser state
	 * @returns {ParserState} the parser state
	 */
	parse(source, state) {
		if (Buffer.isBuffer(source)) {
			source = source.toString("utf8");
		} else if (typeof source === "object") {
			throw new Error("webpackAst is unexpected for the CssParser");
		}
		if (source[0] === "\uFEFF") {
			source = source.slice(1);
		}

		const unescapeIdentifierCached = unescapeIdentifier.bindCache(
			state.compilation.compiler.root
		);

		let mode = this.defaultMode;

		const module = state.module;

		if (
			mode === "auto" &&
			module.type === CSS_MODULE_TYPE_AUTO &&
			IS_MODULES.test(
				parseResource(/** @type {string} */ (module.getResource())).path
			)
		) {
			mode = "local";
		}

		const isModules = mode === "global" || mode === "local";

		const parsedModuleResource = parseResource(
			/** @type {string} */ (module.getResource())
		);

		/**
		 * Check whether a request points back to the current module
		 * (e.g. `composes: foo from "./self.module.css"` inside `self.module.css`).
		 * Only relative requests are checked β€” aliases / package / absolute requests
		 * fall through to the normal import path. Requests with a `?query` or
		 * `#fragment` are only treated as self when the parent module's resource
		 * has the same query/fragment, since `NormalModuleFactory` keys modules
		 * on the full resource string.
		 * @param {string} request request string from `from "<request>"`
		 * @returns {boolean} true if request resolves to the current module
		 */
		const isSelfReferenceRequest = (request) => {
			if (!/^\.{1,2}\//.test(request)) return false;
			if (!module.context) return false;
			const parsedRequest = parseResource(request);
			if (parsedRequest.query !== parsedModuleResource.query) return false;
			if (parsedRequest.fragment !== parsedModuleResource.fragment) {
				return false;
			}
			try {
				return (
					path.resolve(module.context, parsedRequest.path) ===
					parsedModuleResource.path
				);
			} catch (_err) {
				return false;
			}
		};

		const knownProperties = getKnownProperties({
			animation: this.options.animation,
			container: this.options.container,
			customIdents: this.options.customIdents,
			grid: this.options.grid
		});

		/** @type {BuildMeta} */
		(module.buildMeta).isCssModule = isModules;
		if (/** @type {CssModule} */ (module).exportType === "style") {
			/** @type {BuildMeta} */
			(module.buildMeta).needIdInConcatenation = true;
		}

		const locConverter = new LocConverter(source);

		/** @type {number} */
		let scope = CSS_MODE_TOP_LEVEL;
		/** @type {boolean} */
		let allowImportAtRule = true;
		/** @type {[string, number, number, boolean?][]} */
		const balanced = [];
		let lastTokenEndForComments = 0;

		/** @type {boolean} */
		let isNextRulePrelude = isModules;
		/** @type {number} */
		let blockNestingLevel = 0;
		/** @type {0 | 1 | undefined} */
		let modeData;
		/** @type {number} */
		let counter = 0;

		/** @type {string[]} */
		let lastLocalIdentifiers = [];

		const pureMode = isModules && Boolean(this.options.pure);
		/** @type {boolean} */
		let currentSelectorHasLocal = false;
		/** Whether any comma-separated selector in the current rule's prelude was impure. */
		let currentRuleHasImpureSelector = false;
		/** Offset just after the previous `}` (or 0) β€” used as the prelude start. */
		let currentRulePreludeStart = 0;
		/** Pure-mode flags (only meaningful when `pureMode` is true). */
		let pureNoCheck = false;
		let pureIgnorePending = false;
		let nextBlockChildrenSkip = false;
		let nextBlockTreatAsLeaf = false;
		let seenTopLevelRule = false;
		// True after an at-rule keyword and before the next `{` or `;`. Used so
		// identifiers inside the at-rule prelude (e.g. `min-width` inside
		// `@media (min-width: 768px)`) don't get counted as declarations.
		let inAtRulePrelude = false;
		/**
		 * One entry per open block. `skipOwn` skips this rule's own check (set
		 * when the parent passed down `skipChildren`, e.g. `from`/`to` inside
		 * `@keyframes`). `skipChildren` is propagated to descendants. `ignored`
		 * is per-rule only (PCSL semantics for `cssmodules-pure-ignore`).
		 * `ancestorHadLocal` lets nested rules inherit purity from a
		 * local-bearing ancestor.
		 * @type {{
		 * ignored: boolean,
		 * skipOwn: boolean,
		 * skipChildren: boolean,
		 * treatAsLeaf: boolean,
		 * ancestorHadLocal: boolean,
		 * impure: boolean,
		 * hasDirectDecl: boolean,
		 * hasNestedBlock: boolean,
		 * isRulePrelude: boolean,
		 * preludeStart: number,
		 * preludeEnd: number,
		 * }[]}
		 */
		const pureBlockStack = [];

		const PURE_IGNORE_RE = /^\s*cssmodules-pure-ignore(?:\s|$)/;
		const PURE_NO_CHECK_RE = /^\s*cssmodules-pure-no-check(?:\s|$)/;

		/**
		 * @returns {(typeof pureBlockStack)[number] | undefined} top of stack
		 */
		const pureTop = () => pureBlockStack[pureBlockStack.length - 1];

		/**
		 * Was the parent rule pure overall (its own selectors pure or any
		 * ancestor pure)? Used both for ancestor-inheritance and `&`-resolution.
		 * @returns {boolean} true if any ancestor (self inclusive) provided a local
		 */
		const parentEffectivePure = () => {
			const top = pureTop();
			return top ? top.ancestorHadLocal : false;
		};

		/**
		 * Marks the just-finished comma-separated selector (or whole prelude
		 * at `{`) as impure if it lacks a local and no ancestor compensates.
		 */
		const finalizeSelector = () => {
			if (!currentSelectorHasLocal && !parentEffectivePure()) {
				currentRuleHasImpureSelector = true;
			}
			currentSelectorHasLocal = false;
		};

		/**
		 * Reports a pure-mode violation covering the entire rule prelude.
		 * @param {number} start prelude start offset
		 * @param {number} end prelude end offset (`{` position)
		 */
		const reportPureRule = (start, end) => {
			const slice = source.slice(start, end);
			const lead = /** @type {RegExpExecArray} */ (
				/^(?:\s|\/\*[\s\S]*?\*\/)*/.exec(slice)
			)[0].length;
			const trail = /** @type {RegExpExecArray} */ (/\s*$/.exec(slice))[0]
				.length;
			const from = start + lead;
			const to = end - trail;
			if (to <= from) return;
			this._emitError(
				state,
				`Selector "${source.slice(from, to)}" is not pure (pure selectors must contain at least one local class or id)`,
				locConverter,
				from,
				to
			);
		};

		/** @typedef {{ value?: string, importName?: string, localName?: string, request?: string }} IcssDefinition */
		/** @type {Map<string, IcssDefinition>} */
		const icssDefinitions = new Map();

		// Tracks `composes: <name> from "<file>"` declarations to enforce a
		// predictable file load order across rules (port of
		// postcss-modules-extract-imports#138). Each rule's composes order
		// is a partial ordering: if `.x` composes `b from "./b"` before
		// `c from "./c"`, then `b.css` must load before `c.css` so `c` can
		// override `b` in the cascade. Edges are added inline as the rule
		// is parsed; at end-of-parse the first composes-import dep of each
		// file is tagged with `sourceOrder` according to a topological
		// sort (`NormalModule#build` reorders by `sourceOrder` for us).
		/** @type {Map<string, Set<string>>} */
		const composesGraph = new Map();
		/** @type {Map<string, CssIcssImportDependency>} */
		const composesFirstFileImport = new Map();
		/** @type {string | undefined} */
		let currentRulePrevComposesFile;
		/** @type {Set<string>} */
		const currentRuleComposesFiles = new Set();

		/**
		 * Checks whether this css parser is next nested syntax.
		 * @param {string} input input
		 * @param {number} pos position
		 * @returns {boolean} true, when next is nested syntax
		 */
		const isNextNestedSyntax = (input, pos) => {
			pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];

			if (
				input.charCodeAt(pos) === CC_RIGHT_CURLY ||
				(input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
					input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS)
			) {
				return false;
			}

			const identifier = walkCssTokens.eatIdentSequence(input, pos);

			if (!identifier) {
				return true;
			}

			const leftCurly = eatUntilLeftCurly(input, pos);
			const content = input.slice(identifier[0], leftCurly);

			if (content.includes(";") || content.includes("}")) {
				return false;
			}

			return true;
		};
		/**
		 * Checks whether this css parser is local mode.
		 * @returns {boolean} true, when in local scope
		 */
		const isLocalMode = () =>
			modeData === LOCAL_MODE || (mode === "local" && modeData === undefined);

		/**
		 * Returns end.
		 * @param {string} input input
		 * @param {number} start start
		 * @param {number} end end
		 * @returns {number} end
		 */
		const comment = (input, start, end) => {
			if (!this.comments) this.comments = [];
			const { line: sl, column: sc } = locConverter.get(start);
			const { line: el, column: ec } = locConverter.get(end);

			const value = input.slice(start + 2, end - 2);

			/** @type {Comment} */
			const comment = {
				value,
				range: [start, end],
				loc: {
					start: { line: sl, column: sc },
					end: { line: el, column: ec }
				}
			};
			this.comments.push(comment);

			if (pureMode) {
				if (PURE_IGNORE_RE.test(value)) {
					pureIgnorePending = true;
				} else if (
					PURE_NO_CHECK_RE.test(value) &&
					scope === CSS_MODE_TOP_LEVEL &&
					!seenTopLevelRule
				) {
					pureNoCheck = true;
				}
			}

			return end;
		};

		// Vanilla CSS stuff

		/**
		 * Processes the provided input.
		 * @param {string} input input
		 * @param {number} start name start position
		 * @param {number} end name end position
		 * @returns {number} position after handling
		 */
		const processAtImport = (input, start, end) => {
			const tokens = walkCssTokens.eatImportTokens(input, end, {
				comment
			});
			if (!tokens[3]) return end;
			const semi = tokens[3][1];
			if (!tokens[0] || (tokens[0][4] && !isModules)) {
				this._emitWarning(
					state,
					`Expected URL in '${input.slice(start, semi)}'`,
					locConverter,
					start,
					semi
				);
				return end;
			}

			const urlToken = tokens[0];
			/** @type {string} */
			let url;
			if (urlToken[4]) {
				// URL given as identifier β€” resolve via CSS Modules @value.
				const name = input.slice(urlToken[2], urlToken[3]);
				const def = icssDefinitions.get(name);
				if (!def) {
					this._emitWarning(
						state,
						`Unknown '@value' identifier '${name}' in '${input.slice(start, semi)}'`,
						locConverter,
						start,
						semi
					);
					// Consume the whole at-rule so the unresolved identifier
					// doesn't get re-tokenized and accidentally substituted
					// into a malformed `@import` in the output.
					const dep = new ConstDependency("", [start, semi]);
					module.addPresentationalDependency(dep);
					return semi;
				}
				if (def.value === undefined) {
					this._emitWarning(
						state,
						`'@value' identifier '${name}' was imported from another module and cannot be used as the URL of '@import' β€” only locally defined values are supported here`,
						locConverter,
						start,
						semi
					);
					const dep = new ConstDependency("", [start, semi]);
					module.addPresentationalDependency(dep);
					return semi;
				}
				const raw = def.value.trim();
				url =
					(raw.startsWith('"') && raw.endsWith('"')) ||
					(raw.startsWith("'") && raw.endsWith("'"))
						? normalizeUrl(raw.slice(1, -1), true)
						: normalizeUrl(raw, false);
			} else {
				url = normalizeUrl(input.slice(urlToken[2], urlToken[3]), true);
			}
			const newline = walkCssTokens.eatWhiteLine(input, semi);
			const { options, errors: commentErrors } = this.parseCommentOptions([
				end,
				urlToken[1]
			]);
			if (commentErrors) {
				for (const e of commentErrors) {
					const { comment } = e;
					state.module.addWarning(
						new CommentCompilationWarning(
							`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
							comment.loc
						)
					);
				}
			}
			if (options && options.webpackIgnore !== undefined) {
				if (typeof options.webpackIgnore !== "boolean") {
					const { line: sl, column: sc } = locConverter.get(start);
					const { line: el, column: ec } = locConverter.get(newline);

					state.module.addWarning(
						new UnsupportedFeatureWarning(
							`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
							{
								start: { line: sl, column: sc },
								end: { line: el, column: ec }
							}
						)
					);
				} else if (options.webpackIgnore) {
					return newline;
				}
			}
			if (url.length === 0) {
				const { line: sl, column: sc } = locConverter.get(start);
				const { line: el, column: ec } = locConverter.get(newline);
				const dep = new ConstDependency("", [start, newline]);
				module.addPresentationalDependency(dep);
				dep.setLoc(sl, sc, el, ec);

				return newline;
			}

			/** @type {undefined | string} */
			let layer;

			if (tokens[1]) {
				layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim();
			}

			/** @type {undefined | string} */
			let supports;

			if (tokens[2]) {
				supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim();
			}

			const last = tokens[2] || tokens[1] || tokens[0];
			const mediaStart = walkCssTokens.eatWhitespaceAndComments(
				input,
				last[1]
			)[0];

			/** @type {undefined | string} */
			let media;

			if (mediaStart !== semi - 1) {
				media = input.slice(mediaStart, semi - 1).trim();
			}

			const { line: sl, column: sc } = locConverter.get(start);
			const { line: el, column: ec } = locConverter.get(newline);
			const dep = new CssImportDependency(
				url,
				[start, newline],
				mode === "local" || mode === "global" ? mode : undefined,
				layer,
				supports && supports.length > 0 ? supports : undefined,
				media && media.length > 0 ? media : undefined
			);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			// `text` and `css-style-sheet` parents inline the imported
			// module's rendered CSS at build time, which means we read the
			// imported module's `codeGenerationResults` (and through it the
			// results of any assets the import references). Registering this
			// as a code-generation dependency tells the compilation scheduler
			// to generate the imported subtree before us.
			const exportType = /** @type {import("./CssModule")} */ (module)
				.exportType;
			if (exportType === "text" || exportType === "css-style-sheet") {
				module.addCodeGenerationDependency(dep);
			}

			return newline;
		};

		/**
		 * Process url function.
		 * @param {string} input input
		 * @param {number} end end position
		 * @param {string} name the name of function
		 * @returns {number} position after handling
		 */
		const processURLFunction = (input, end, name) => {
			const string = walkCssTokens.eatString(input, end);
			if (!string) return end;
			const { options, errors: commentErrors } = this.parseCommentOptions([
				lastTokenEndForComments,
				end
			]);
			if (commentErrors) {
				for (const e of commentErrors) {
					const { comment } = e;
					state.module.addWarning(
						new CommentCompilationWarning(
							`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
							comment.loc
						)
					);
				}
			}
			if (options && options.webpackIgnore !== undefined) {
				if (typeof options.webpackIgnore !== "boolean") {
					const { line: sl, column: sc } = locConverter.get(string[0]);
					const { line: el, column: ec } = locConverter.get(string[1]);

					state.module.addWarning(
						new UnsupportedFeatureWarning(
							`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
							{
								start: { line: sl, column: sc },
								end: { line: el, column: ec }
							}
						)
					);
				} else if (options.webpackIgnore) {
					return end;
				}
			}
			const value = normalizeUrl(
				input.slice(string[0] + 1, string[1] - 1),
				true
			);
			// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
			if (value.length === 0) return end;
			const isUrl = name === "url" || name === "src";
			const dep = new CssUrlDependency(
				value,
				[string[0], string[1]],
				isUrl ? "string" : "url"
			);
			const { line: sl, column: sc } = locConverter.get(string[0]);
			const { line: el, column: ec } = locConverter.get(string[1]);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			module.addCodeGenerationDependency(dep);
			return string[1];
		};

		/**
		 * Process old url function.
		 * @param {string} input input
		 * @param {number} start start position
		 * @param {number} end end position
		 * @param {number} contentStart start position
		 * @param {number} contentEnd end position
		 * @returns {number} position after handling
		 */
		const processOldURLFunction = (
			input,
			start,
			end,
			contentStart,
			contentEnd
		) => {
			const { options, errors: commentErrors } = this.parseCommentOptions([
				lastTokenEndForComments,
				end
			]);
			if (commentErrors) {
				for (const e of commentErrors) {
					const { comment } = e;
					state.module.addWarning(
						new CommentCompilationWarning(
							`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
							comment.loc
						)
					);
				}
			}
			if (options && options.webpackIgnore !== undefined) {
				if (typeof options.webpackIgnore !== "boolean") {
					const { line: sl, column: sc } = locConverter.get(
						lastTokenEndForComments
					);
					const { line: el, column: ec } = locConverter.get(end);

					state.module.addWarning(
						new UnsupportedFeatureWarning(
							`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
							{
								start: { line: sl, column: sc },
								end: { line: el, column: ec }
							}
						)
					);
				} else if (options.webpackIgnore) {
					return end;
				}
			}
			let value = normalizeUrl(input.slice(contentStart, contentEnd), false);
			// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
			if (value.length === 0) return end;
			if (isModules) {
				const def = icssDefinitions.get(value);
				if (def) {
					if (def.value !== undefined) {
						const raw = def.value.trim();
						value =
							(raw.startsWith('"') && raw.endsWith('"')) ||
							(raw.startsWith("'") && raw.endsWith("'"))
								? normalizeUrl(raw.slice(1, -1), true)
								: normalizeUrl(raw, false);
						if (value.length === 0) return end;
					} else {
						this._emitWarning(
							state,
							`'@value' identifier '${value}' was imported from another module and cannot be used inside 'url()' β€” only locally defined values are supported here`,
							locConverter,
							start,
							end
						);
						return end;
					}
				}
			}
			const dep = new CssUrlDependency(value, [start, end], "url");
			const { line: sl, column: sc } = locConverter.get(start);
			const { line: el, column: ec } = locConverter.get(end);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			module.addCodeGenerationDependency(dep);
			return end;
		};

		/**
		 * Process image set function.
		 * @param {string} input input
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processImageSetFunction = (input, start, end) => {
			lastTokenEndForComments = end;
			const values = walkCssTokens.eatImageSetStrings(input, end, {
				comment
			});
			if (values.length === 0) return end;
			for (const [index, string] of values.entries()) {
				const value = normalizeUrl(
					input.slice(string[0] + 1, string[1] - 1),
					true
				);
				if (value.length === 0) return end;
				const { options, errors: commentErrors } = this.parseCommentOptions([
					index === 0 ? start : values[index - 1][1],
					string[1]
				]);
				if (commentErrors) {
					for (const e of commentErrors) {
						const { comment } = e;
						state.module.addWarning(
							new CommentCompilationWarning(
								`Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
								comment.loc
							)
						);
					}
				}
				if (options && options.webpackIgnore !== undefined) {
					if (typeof options.webpackIgnore !== "boolean") {
						const { line: sl, column: sc } = locConverter.get(string[0]);
						const { line: el, column: ec } = locConverter.get(string[1]);

						state.module.addWarning(
							new UnsupportedFeatureWarning(
								`\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
								{
									start: { line: sl, column: sc },
									end: { line: el, column: ec }
								}
							)
						);
					} else if (options.webpackIgnore) {
						continue;
					}
				}
				const dep = new CssUrlDependency(value, [string[0], string[1]], "url");
				const { line: sl, column: sc } = locConverter.get(string[0]);
				const { line: el, column: ec } = locConverter.get(string[1]);
				dep.setLoc(sl, sc, el, ec);
				module.addDependency(dep);
				module.addCodeGenerationDependency(dep);
			}
			// Can contain `url()` inside, so let's return end to allow parse them
			return end;
		};

		// CSS modules stuff

		/**
		 * Returns resolved reexport (localName and importName).
		 * @param {string} value value to resolve
		 * @param {string=} localName override local name
		 * @param {boolean=} isCustomProperty true when it is custom property, otherwise false
		 * @returns {string | [string, string] | [string, string, string]} resolved reexport (`localName`, `importName` and optional `request` of the active `@value` import)
		 */
		const getReexport = (value, localName, isCustomProperty) => {
			const reexport = icssDefinitions.get(
				isCustomProperty ? `--${value}` : value
			);

			if (reexport) {
				if (reexport.importName) {
					const resolvedLocalName =
						reexport.localName || (isCustomProperty ? `--${value}` : value);
					return reexport.request
						? [resolvedLocalName, reexport.importName, reexport.request]
						: [resolvedLocalName, reexport.importName];
				}

				if (isCustomProperty) {
					return /** @type {string} */ (reexport.value).slice(2);
				}

				return /** @type {string} */ (reexport.value);
			}

			if (localName) {
				return [localName, value];
			}

			return value;
		};

		/**
		 * Process import or export.
		 * @param {0 | 1} type import or export
		 * @param {string} input input
		 * @param {number} pos start position
		 * @returns {number} position after parse
		 */
		const processImportOrExport = (type, input, pos) => {
			pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
			/** @type {string | undefined} */
			let request;
			if (type === 0) {
				let cc = input.charCodeAt(pos);
				if (cc !== CC_LEFT_PARENTHESIS) {
					this._emitWarning(
						state,
						`Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`,
						locConverter,
						pos,
						pos
					);
					return pos;
				}
				pos++;
				const stringStart = pos;
				const str = walkCssTokens.eatString(input, pos);
				if (!str) {
					this._emitWarning(
						state,
						`Unexpected '${input[pos]}' at ${pos} during parsing of '${type === 0 ? ":import" : ":export"}' (expected string)`,
						locConverter,
						stringStart,
						pos
					);
					return pos;
				}
				request = input.slice(str[0] + 1, str[1] - 1);
				pos = str[1];
				pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
				cc = input.charCodeAt(pos);
				if (cc !== CC_RIGHT_PARENTHESIS) {
					this._emitWarning(
						state,
						`Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`,
						locConverter,
						pos,
						pos
					);
					return pos;
				}
				pos++;
				pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
			}

			/**
			 * Creates a dep from the provided name.
			 * @param {string} name name
			 * @param {string} value value
			 * @param {number} start start of position
			 * @param {number} end end of position
			 */
			const createDep = (name, value, start, end) => {
				if (type === 0) {
					const dep = new CssIcssImportDependency(
						/** @type {string} */
						(request),
						[0, 0],
						/** @type {"local" | "global"} */
						(mode),
						value,
						name
					);
					const { line: sl, column: sc } = locConverter.get(start);
					const { line: el, column: ec } = locConverter.get(end);
					dep.setLoc(sl, sc, el, ec);
					module.addDependency(dep);

					icssDefinitions.set(name, {
						importName: value,
						request: /** @type {string} */ (request)
					});
				} else if (type === 1) {
					const dep = new CssIcssExportDependency(name, getReexport(value));
					const { line: sl, column: sc } = locConverter.get(start);
					const { line: el, column: ec } = locConverter.get(end);
					dep.setLoc(sl, sc, el, ec);
					module.addDependency(dep);
				}
			};

			let needTerminate = false;
			let balanced = 0;
			/** @type {undefined | 0 | 1 | 2} */
			let scope;

			/** @typedef {[number, number]} Name */

			/** @type {Name | undefined} */
			let name;
			/** @type {number | undefined} */
			let value;

			/** @type {CssTokenCallbacks} */
			const callbacks = {
				leftCurlyBracket: (_input, _start, end) => {
					balanced++;

					if (scope === undefined) {
						scope = 0;
					}

					return end;
				},
				rightCurlyBracket: (_input, _start, end) => {
					balanced--;

					if (scope === 2) {
						const [nameStart, nameEnd] = /** @type {Name} */ (name);
						createDep(
							input.slice(nameStart, nameEnd),
							input.slice(value, end - 1).trim(),
							nameEnd,
							end - 1
						);
						scope = 0;
					}

					if (balanced === 0 && scope === 0) {
						needTerminate = true;
					}

					return end;
				},
				identifier: (_input, start, end) => {
					if (scope === 0) {
						name = [start, end];
						scope = 1;
					}

					return end;
				},
				colon: (_input, _start, end) => {
					if (scope === 1) {
						scope = 2;
						value = walkCssTokens.eatWhitespace(input, end);
						return value;
					}

					return end;
				},
				semicolon: (input, _start, end) => {
					if (scope === 2) {
						const [nameStart, nameEnd] = /** @type {Name} */ (name);
						createDep(
							input.slice(nameStart, nameEnd),
							input.slice(value, end - 1),
							nameEnd,
							end - 1
						);
						scope = 0;
					}

					return end;
				},
				needTerminate: () => needTerminate
			};

			pos = walkCssTokens(input, pos, callbacks);
			pos = walkCssTokens.eatWhiteLine(input, pos);

			return pos;
		};

		/** @typedef {{ from: string, items: ({ localName: string, importName: string })[] }} ValueAtRuleImport */
		/** @typedef {{ localName: string, value: string }} ValueAtRuleValue */
		/**
		 * Parses value at rule params.
		 * @param {string} str value at-rule params
		 * @returns {ValueAtRuleImport | ValueAtRuleValue} parsed result
		 */
		const parseValueAtRuleParams = (str) => {
			if (/from(\/\*|\s)(?:[\s\S]+)$/i.test(str)) {
				str = str.replace(CSS_COMMENT, " ").trim().replace(/;$/, "");
				const fromIdx = str.lastIndexOf("from");
				const path = str
					.slice(fromIdx + 5)
					.trim()
					.replace(/['"]/g, "");
				let content = str.slice(0, fromIdx).trim();

				if (content.startsWith("(") && content.endsWith(")")) {
					content = content.slice(1, -1);
				}

				return {
					from: path,
					items: content.split(",").map((item) => {
						item = item.trim();

						if (item.includes(":")) {
							const [local, remote] = item.split(":");

							return { localName: local.trim(), importName: remote.trim() };
						}

						const asParts = item.split(/\s+as\s+/);

						if (asParts.length === 2) {
							return {
								localName: asParts[1].trim(),
								importName: asParts[0].trim()
							};
						}

						return { localName: item, importName: item };
					})
				};
			}

			/** @type {string} */
			let localName;
			/** @type {string} */
			let value;

			const idx = str.indexOf(":");

			if (idx !== -1) {
				localName = str.slice(0, idx).replace(CSS_COMMENT, "").trim();
				value = str.slice(idx + 1);
			} else {
				const mask = str.replace(CSS_COMMENT, (m) => " ".repeat(m.length));
				const idx = mask.search(/\S\s/) + 1;

				localName = str.slice(0, idx).replace(CSS_COMMENT, "").trim();
				value = str.slice(idx + (str[idx] === " " ? 1 : 0));
			}

			if (value.length > 0 && !/^\s+$/.test(value.replace(CSS_COMMENT, ""))) {
				value = value.trim();
			}

			return { localName, value };
		};

		/**
		 * Processes the provided input.
		 * @param {string} input input
		 * @param {number} start name start position
		 * @param {number} end name end position
		 * @returns {number} position after handling
		 */
		const processAtValue = (input, start, end) => {
			const semi = eatUntilSemi(input, end);
			const atRuleEnd = semi + 1;
			const params = input.slice(end, semi);
			const parsed = parseValueAtRuleParams(params);

			if (
				typeof (/** @type {ValueAtRuleImport} */ (parsed).from) !== "undefined"
			) {
				if (/** @type {ValueAtRuleImport} */ (parsed).from.length === 0) {
					this._emitWarning(
						state,
						`Broken '@value' at-rule: ${input.slice(start, atRuleEnd)}'`,
						locConverter,
						start,
						atRuleEnd
					);

					const dep = new ConstDependency("", [start, atRuleEnd]);
					module.addPresentationalDependency(dep);
					return atRuleEnd;
				}

				let { from, items } = /** @type {ValueAtRuleImport} */ (parsed);

				for (const { importName, localName } of items) {
					{
						const reexport = icssDefinitions.get(from);

						if (reexport && reexport.value) {
							from = reexport.value.slice(1, -1);
						}

						const dep = new CssIcssImportDependency(
							from,
							[0, 0],
							/** @type {"local" | "global"} */
							(mode),
							importName,
							localName
						);
						const { line: sl, column: sc } = locConverter.get(start);
						const { line: el, column: ec } = locConverter.get(end);
						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);

						icssDefinitions.set(localName, { importName, request: from });
					}

					{
						const dep = new CssIcssExportDependency(
							localName,
							getReexport(localName),
							undefined,
							false,
							CssIcssExportDependency.EXPORT_MODE.REPLACE
						);
						const { line: sl, column: sc } = locConverter.get(start);
						const { line: el, column: ec } = locConverter.get(end);
						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
					}
				}
			} else {
				if (/** @type {ValueAtRuleValue} */ (parsed).localName.length === 0) {
					this._emitWarning(
						state,
						`Broken '@value' at-rule: ${input.slice(start, atRuleEnd)}'`,
						locConverter,
						start,
						atRuleEnd
					);

					const dep = new ConstDependency("", [start, atRuleEnd]);
					module.addPresentationalDependency(dep);
					return atRuleEnd;
				}

				const { localName, value } = /** @type {ValueAtRuleValue} */ (parsed);
				const { line: sl, column: sc } = locConverter.get(start);
				const { line: el, column: ec } = locConverter.get(end);

				if (icssDefinitions.has(value)) {
					const def =
						/** @type {IcssDefinition} */
						(icssDefinitions.get(value));

					def.localName = value;

					icssDefinitions.set(localName, def);

					const dep = new CssIcssExportDependency(
						localName,
						getReexport(value)
					);
					dep.setLoc(sl, sc, el, ec);
					module.addDependency(dep);
				} else {
					icssDefinitions.set(localName, { value });

					const dep = new CssIcssExportDependency(localName, value);
					dep.setLoc(sl, sc, el, ec);
					module.addDependency(dep);
				}
			}

			const dep = new ConstDependency("", [start, atRuleEnd]);
			module.addPresentationalDependency(dep);
			return atRuleEnd;
		};

		/**
		 * Process icss symbol.
		 * @param {string} name ICSS symbol name
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processICSSSymbol = (name, start, end) => {
			const def =
				/** @type {IcssDefinition} */
				(icssDefinitions.get(name));
			const { line: sl, column: sc } = locConverter.get(start);
			const { line: el, column: ec } = locConverter.get(end);
			const dep = new CssIcssSymbolDependency(
				def.localName || name,
				[start, end],
				def.value,
				def.importName,
				def.request
			);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			return end;
		};

		/**
		 * Process local or global function.
		 * @param {string} input input
		 * @param {1 | 2} type type of function
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processLocalOrGlobalFunction = (input, type, start, end) => {
			// Replace `local(`/` or `global(` (handle legacy `:local(` or `:global(` too)
			{
				const isColon = input.charCodeAt(start - 1) === CC_COLON;
				const dep = new ConstDependency("", [isColon ? start - 1 : start, end]);
				module.addPresentationalDependency(dep);
			}

			end = walkCssTokens.consumeUntil(
				input,
				start,
				{
					identifier(input, start, end) {
						if (type === 1) {
							let identifier = unescapeIdentifierCached(
								input.slice(start, end)
							);
							const { line: sl, column: sc } = locConverter.get(start);
							const { line: el, column: ec } = locConverter.get(end);
							const isDashedIdent = isDashedIdentifier(identifier);

							if (isDashedIdent) {
								identifier = identifier.slice(2);
							}

							const dep = new CssIcssExportDependency(
								identifier,
								getReexport(identifier),
								[start, end],
								true,
								CssIcssExportDependency.EXPORT_MODE.ONCE,
								isDashedIdent
									? CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
									: CssIcssExportDependency.EXPORT_TYPE.NORMAL
							);

							dep.setLoc(sl, sc, el, ec);
							module.addDependency(dep);
						}

						return end;
					}
				},
				{},
				{ onlyTopLevel: true, functionValue: true }
			);

			{
				// Replace the last `)`
				const dep = new ConstDependency("", [end, end + 1]);
				module.addPresentationalDependency(dep);
			}

			return end;
		};

		/**
		 * Process local at rule.
		 * @param {string} input input
		 * @param {number} end name end position
		 * @param {{ string?: boolean, identifier?: boolean | RegExp }} options types which allowed to handle
		 * @returns {number} position after handling
		 */
		const processLocalAtRule = (input, end, options) => {
			let found = false;

			return walkCssTokens.consumeUntil(
				input,
				end,
				{
					string(_input, start, end) {
						if (!found && options.string) {
							const value = unescapeIdentifierCached(
								input.slice(start + 1, end - 1)
							);
							const { line: sl, column: sc } = locConverter.get(start);
							const { line: el, column: ec } = locConverter.get(end);
							const dep = new CssIcssExportDependency(
								value,
								value,
								[start, end],
								true,
								CssIcssExportDependency.EXPORT_MODE.ONCE
							);
							dep.setLoc(sl, sc, el, ec);
							module.addDependency(dep);
							found = true;
							if (pureMode) currentSelectorHasLocal = true;
						}
						return end;
					},
					identifier(input, start, end) {
						if (!found) {
							const value = input.slice(start, end);

							if (options.identifier) {
								const identifier = unescapeIdentifierCached(value);

								if (
									options.identifier instanceof RegExp &&
									options.identifier.test(identifier)
								) {
									return end;
								}

								const { line: sl, column: sc } = locConverter.get(start);
								const { line: el, column: ec } = locConverter.get(end);

								const dep = new CssIcssExportDependency(
									identifier,
									getReexport(identifier),
									[start, end],
									true,
									CssIcssExportDependency.EXPORT_MODE.ONCE,
									CssIcssExportDependency.EXPORT_TYPE.NORMAL
								);
								dep.setLoc(sl, sc, el, ec);
								module.addDependency(dep);
								found = true;
								if (pureMode) currentSelectorHasLocal = true;
							}
						}
						return end;
					}
				},
				{
					function: (input, start, end) => {
						// No need to handle `:` (COLON), because it's always a function
						const name = input
							.slice(start, end - 1)
							.replace(/\\/g, "")
							.toLowerCase();

						const type =
							name === "local" ? 1 : name === "global" ? 2 : undefined;

						if (!found && type) {
							found = true;
							if (type === 1 && pureMode) currentSelectorHasLocal = true;
							return processLocalOrGlobalFunction(input, type, start, end);
						}

						if (
							this.options.dashedIdents &&
							isLocalMode() &&
							(name === "var" || name === "style")
						) {
							return processDashedIdent(input, end, end);
						}

						return end;
					}
				},
				{ onlyTopLevel: true, atRulePrelude: true }
			);
		};
		/**
		 * Process dashed ident.
		 * @param {string} input input
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processDashedIdent = (input, start, end) => {
			const customIdent = walkCssTokens.eatIdentSequence(input, start);
			if (!customIdent) return end;
			const identifier = unescapeIdentifierCached(
				input.slice(customIdent[0] + 2, customIdent[1])
			);
			const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments(
				input,
				customIdent[1]
			)[0];
			if (
				input.charCodeAt(afterCustomIdent) === CC_LOWER_F ||
				input.charCodeAt(afterCustomIdent) === CC_UPPER_F
			) {
				const fromWord = walkCssTokens.eatIdentSequence(
					input,
					afterCustomIdent
				);
				if (
					!fromWord ||
					input.slice(fromWord[0], fromWord[1]).toLowerCase() !== "from"
				) {
					return end;
				}
				const from = walkCssTokens.eatIdentSequenceOrString(
					input,
					walkCssTokens.eatWhitespaceAndComments(input, fromWord[1])[0]
				);
				if (!from) {
					return end;
				}
				const path = input.slice(from[0], from[1]);
				if (from[2] === true && path === "global") {
					const dep = new ConstDependency("", [customIdent[1], from[1]]);
					module.addPresentationalDependency(dep);
					return end;
				} else if (from[2] === false) {
					const { line: sl, column: sc } = locConverter.get(customIdent[0]);
					const { line: el, column: ec } = locConverter.get(from[1] - 1);
					const localName = `__ICSS_IMPORT_${counter++}__`;

					{
						const dep = new CssIcssImportDependency(
							path.slice(1, -1),
							[customIdent[0], from[1] - 1],
							/** @type {"local" | "global"} */
							(mode),
							identifier,
							localName
						);

						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
					}

					{
						const dep = new CssIcssExportDependency(
							identifier,
							getReexport(identifier, localName, true),
							[customIdent[0], from[1] - 1],
							true,
							CssIcssExportDependency.EXPORT_MODE.ONCE,
							CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
						);

						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
					}

					{
						const dep = new ConstDependency("", [fromWord[0], from[1]]);
						module.addPresentationalDependency(dep);
						return end;
					}
				}
			} else {
				const { line: sl, column: sc } = locConverter.get(customIdent[0]);
				const { line: el, column: ec } = locConverter.get(customIdent[1]);
				const dep = new CssIcssExportDependency(
					identifier,
					getReexport(identifier, undefined, true),
					[customIdent[0], customIdent[1]],
					true,
					CssIcssExportDependency.EXPORT_MODE.ONCE,
					CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
				);
				dep.setLoc(sl, sc, el, ec);
				module.addDependency(dep);
				return end;
			}

			return end;
		};
		/**
		 * Process local declaration.
		 * @param {string} input input
		 * @param {number} pos name start position
		 * @param {number} end name end position
		 * @returns {number} position after handling
		 */
		const processLocalDeclaration = (input, pos, end) => {
			pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
			const identifier = walkCssTokens.eatIdentSequence(input, pos);

			if (!identifier) {
				return end;
			}

			const propertyNameStart = identifier[0];

			pos = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];

			if (input.charCodeAt(pos) !== CC_COLON) {
				return end;
			}

			pos += 1;

			// Remove prefix and lowercase
			const propertyName = input
				.slice(identifier[0], identifier[1])
				.replace(/^(-\w+-)/, "")
				.toLowerCase();

			if (isLocalMode() && knownProperties.has(propertyName)) {
				/** @type {[number, number, boolean?][]} */
				const values = [];
				/** @type {Record<string, number>} */
				let parsedKeywords = Object.create(null);

				const isGridProperty = Boolean(propertyName.startsWith("grid"));
				const isGridTemplate = isGridProperty
					? Boolean(
							propertyName === "grid" ||
							propertyName === "grid-template" ||
							propertyName === "grid-template-columns" ||
							propertyName === "grid-template-rows"
						)
					: false;

				let afterExclamation = false;

				const end = walkCssTokens.consumeUntil(
					input,
					pos,
					{
						delim(input, start, end) {
							afterExclamation = input.charCodeAt(start) === CC_EXCLAMATION;
							return end;
						},
						leftSquareBracket(input, start, end) {
							let i = end;

							while (true) {
								i = walkCssTokens.eatWhitespaceAndComments(input, i)[0];
								const name = walkCssTokens.eatIdentSequence(input, i);

								if (!name) {
									break;
								}

								values.push(name);
								i = name[1];
							}

							return end;
						},
						string(_input, start, end) {
							if (
								propertyName === "animation" ||
								propertyName === "animation-name"
							) {
								values.push([start, end, true]);
							}

							if (
								propertyName === "grid" ||
								propertyName === "grid-template" ||
								propertyName === "grid-template-areas"
							) {
								const areas = unescapeIdentifierCached(
									input.slice(start + 1, end - 1)
								);
								const matches = matchAll(/\b\w+\b/g, areas);

								for (const match of matches) {
									const areaStart = start + 1 + match.index;
									values.push([areaStart, areaStart + match[0].length, false]);
								}
							}

							return end;
						},
						identifier(input, start, end) {
							if (isGridTemplate) {
								return end;
							}

							if (afterExclamation) {
								afterExclamation = false;
								return end;
							}

							const identifier = input.slice(start, end);
							const keyword = identifier.toLowerCase();

							parsedKeywords[keyword] =
								typeof parsedKeywords[keyword] !== "undefined"
									? parsedKeywords[keyword] + 1
									: 0;
							const keywords =
								/** @type {Record<string, number>} */
								(knownProperties.get(propertyName));

							if (
								keywords[keyword] &&
								parsedKeywords[keyword] < keywords[keyword]
							) {
								return end;
							}

							values.push([start, end]);
							return end;
						},
						comma(_input, _start, end) {
							parsedKeywords = {};

							return end;
						}
					},
					{
						function: (input, start, end) => {
							const name = input
								.slice(start, end - 1)
								.replace(/\\/g, "")
								.toLowerCase();

							const type =
								name === "local" ? 1 : name === "global" ? 2 : undefined;

							if (type) {
								return processLocalOrGlobalFunction(input, type, start, end);
							}

							if (
								this.options.dashedIdents &&
								isLocalMode() &&
								name === "var"
							) {
								return processDashedIdent(input, end, end);
							}

							if (this.options.url) {
								if (name === "src" || name === "url") {
									return processURLFunction(input, end, name);
								} else if (IMAGE_SET_FUNCTION.test(name)) {
									return processImageSetFunction(input, start, end);
								}
							}

							return end;
						}
					},
					{
						onlyTopLevel: !isGridTemplate,
						declarationValue: true
					}
				);

				if (values.length > 0) {
					for (const value of values) {
						const { line: sl, column: sc } = locConverter.get(value[0]);
						const { line: el, column: ec } = locConverter.get(value[1]);
						const [start, end, isString] = value;
						const name = unescapeIdentifierCached(
							isString
								? input.slice(start + 1, end - 1)
								: input.slice(start, end)
						);
						const dep = new CssIcssExportDependency(
							name,
							getReexport(name),
							[start, end],
							true,
							CssIcssExportDependency.EXPORT_MODE.ONCE,
							isGridProperty
								? CssIcssExportDependency.EXPORT_TYPE.GRID_CUSTOM_IDENTIFIER
								: CssIcssExportDependency.EXPORT_TYPE.NORMAL
						);
						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
					}
				}

				return end;
			} else if (COMPOSES_PROPERTY.test(propertyName)) {
				if (lastLocalIdentifiers.length > 1) {
					const end = eatUntilSemi(input, pos);
					this._emitWarning(
						state,
						`Composition is only allowed when selector is single local class name not in "${lastLocalIdentifiers.join('", "')}"`,
						locConverter,
						pos,
						end
					);

					return end;
				}

				if (lastLocalIdentifiers.length !== 1) return pos;

				const lastLocalIdentifier = lastLocalIdentifiers[0];
				let end = pos;

				/** @type {Set<[number, number, boolean]>} */
				const classNames = new Set();

				while (true) {
					pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];

					let className = walkCssTokens.eatIdentSequence(input, pos);

					const ifFunction =
						className && input.charCodeAt(className[1]) === CC_LEFT_PARENTHESIS;
					let isGlobalFunction = false;

					if (className && ifFunction) {
						const name = input
							.slice(className[0], className[1])
							.replace(/\\/g, "")
							.toLowerCase();

						isGlobalFunction = name === "global";
						pos = walkCssTokens.eatWhitespaceAndComments(
							input,
							className[1] + 1
						)[0];
						className = walkCssTokens.eatIdentSequence(input, pos);
						if (className) {
							pos = walkCssTokens.eatWhitespaceAndComments(
								input,
								className[1]
							)[0];
							pos += 1;
						}
					} else if (className) {
						pos = walkCssTokens.eatWhitespaceAndComments(
							input,
							className[1]
						)[0];
						pos = className[1];
					}

					// True when we have multiple values
					const isComma = input.charCodeAt(pos) === CC_COMMA;
					const isSemicolon = input.charCodeAt(pos) === CC_SEMICOLON;
					const isRightCurly = input.charCodeAt(pos) === CC_RIGHT_CURLY;

					if (isComma || isSemicolon || isRightCurly) {
						if (className) {
							classNames.add([className[0], className[1], isGlobalFunction]);
						}

						for (const entry of classNames) {
							const [start, end, isGlobal] = entry;
							const identifier = unescapeIdentifierCached(
								input.slice(start, end)
							);
							const dep = new CssIcssExportDependency(
								lastLocalIdentifier,
								getReexport(identifier),
								[start, end],
								!isGlobal,
								isGlobal
									? CssIcssExportDependency.EXPORT_MODE.APPEND
									: CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE,
								CssIcssExportDependency.EXPORT_TYPE.COMPOSES
							);
							const { line: sl, column: sc } = locConverter.get(start);
							const { line: el, column: ec } = locConverter.get(end);
							dep.setLoc(sl, sc, el, ec);
							module.addDependency(dep);
						}

						classNames.clear();

						if (isSemicolon || isRightCurly) {
							end = isSemicolon
								? walkCssTokens.eatWhitespace(input, pos + 1)
								: pos;
							break;
						}

						pos += 1;
					} else if (
						classNames.size > 0 &&
						className &&
						input.slice(className[0], className[1]).toLowerCase() === "from"
					) {
						let from = walkCssTokens.eatString(input, pos);

						if (from) {
							const request = input.slice(from[0] + 1, from[1] - 1);
							const selfReference = isSelfReferenceRequest(request);

							if (!selfReference && !currentRuleComposesFiles.has(request)) {
								currentRuleComposesFiles.add(request);
								if (
									currentRulePrevComposesFile !== undefined &&
									currentRulePrevComposesFile !== request
								) {
									let successors = composesGraph.get(
										currentRulePrevComposesFile
									);
									if (!successors) {
										successors = new Set();
										composesGraph.set(currentRulePrevComposesFile, successors);
									}
									successors.add(request);
								}
								currentRulePrevComposesFile = request;
							}

							for (const entry of classNames) {
								const [start, end] = entry;
								const identifier = unescapeIdentifierCached(
									input.slice(start, end)
								);
								const { line: sl, column: sc } = locConverter.get(start);
								const { line: el, column: ec } = locConverter.get(end);

								if (selfReference) {
									// `composes: foo from "./self.module.css"` from inside
									// `self.module.css` β€” collapse to a self-reference, like
									// `composes: foo` without `from`. When the composed name
									// equals the local class name, it's a true no-op.
									if (identifier === lastLocalIdentifier) continue;
									const dep = new CssIcssExportDependency(
										lastLocalIdentifier,
										getReexport(identifier),
										[start, end],
										true,
										CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE,
										CssIcssExportDependency.EXPORT_TYPE.COMPOSES
									);
									dep.setLoc(sl, sc, el, ec);
									module.addDependency(dep);
									continue;
								}

								const localName = `__ICSS_IMPORT_${counter++}__`;

								{
									const dep = new CssIcssImportDependency(
										request,
										[start, end],
										/** @type {"local" | "global"} */
										(mode),
										identifier,
										localName
									);
									dep.setLoc(sl, sc, el, ec);
									module.addDependency(dep);
									if (!composesFirstFileImport.has(request)) {
										composesFirstFileImport.set(request, dep);
									}
								}

								{
									const dep = new CssIcssExportDependency(
										lastLocalIdentifier,
										getReexport(identifier, localName),
										[start, end],
										true,
										CssIcssExportDependency.EXPORT_MODE.APPEND,
										CssIcssExportDependency.EXPORT_TYPE.COMPOSES
									);
									dep.setLoc(sl, sc, el, ec);
									module.addDependency(dep);
								}
							}

							classNames.clear();
							pos = from[1];
						} else {
							from = walkCssTokens.eatIdentSequence(input, pos);

							if (from && input.slice(from[0], from[1]) === "global") {
								for (const entry of classNames) {
									const [start, end] = entry;
									const identifier = unescapeIdentifierCached(
										input.slice(start, end)
									);
									const dep = new CssIcssExportDependency(
										/** @type {string} */
										(lastLocalIdentifier),
										getReexport(identifier),
										[start, end],
										false,
										CssIcssExportDependency.EXPORT_MODE.APPEND,
										CssIcssExportDependency.EXPORT_TYPE.COMPOSES
									);
									const { line: sl, column: sc } = locConverter.get(start);
									const { line: el, column: ec } = locConverter.get(end);
									dep.setLoc(sl, sc, el, ec);
									module.addDependency(dep);
								}

								classNames.clear();
								pos = from[1];
							} else {
								const end = eatUntilSemi(input, pos);
								this._emitWarning(
									state,
									"Incorrect composition, expected global keyword or string value",
									locConverter,
									pos,
									end
								);
								return end;
							}
						}
					} else if (className) {
						classNames.add([className[0], className[1], isGlobalFunction]);
					} else {
						const end = eatUntilSemi(input, pos);
						this._emitWarning(
							state,
							"Incorrect composition, expected class named",
							locConverter,
							pos,
							end
						);
						return end;
					}
				}

				// Remove `composes` from source code
				const dep = new ConstDependency("", [propertyNameStart, end]);
				module.addPresentationalDependency(dep);
			}

			return pos;
		};

		/**
		 * Process id selector.
		 * @param {string} input input
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processIdSelector = (input, start, end) => {
			const valueStart = start + 1;
			const name = unescapeIdentifierCached(input.slice(valueStart, end));
			const dep = new CssIcssExportDependency(
				name,
				getReexport(name),
				[valueStart, end],
				true,
				CssIcssExportDependency.EXPORT_MODE.ONCE
			);
			const { line: sl, column: sc } = locConverter.get(start);
			const { line: el, column: ec } = locConverter.get(end);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			if (pureMode) currentSelectorHasLocal = true;
			return end;
		};

		/**
		 * Process class selector.
		 * @param {string} input input
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processClassSelector = (input, start, end) => {
			const ident = walkCssTokens.skipCommentsAndEatIdentSequence(input, end);
			if (!ident) return end;
			const name = unescapeIdentifierCached(input.slice(ident[0], ident[1]));
			lastLocalIdentifiers.push(name);
			const dep = new CssIcssExportDependency(
				name,
				getReexport(name),
				[ident[0], ident[1]],
				true,
				CssIcssExportDependency.EXPORT_MODE.ONCE
			);
			const { line: sl, column: sc } = locConverter.get(ident[0]);
			const { line: el, column: ec } = locConverter.get(ident[1]);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			if (pureMode) currentSelectorHasLocal = true;
			return ident[1];
		};

		/**
		 * Process attribute selector.
		 * @param {string} input input
		 * @param {number} start start position
		 * @param {number} end end position
		 * @returns {number} position after handling
		 */
		const processAttributeSelector = (input, start, end) => {
			end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
			const identifier = walkCssTokens.eatIdentSequence(input, end);
			if (!identifier) return end;
			const name = unescapeIdentifierCached(
				input.slice(identifier[0], identifier[1])
			);
			if (name.toLowerCase() !== "class") {
				return end;
			}
			end = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];

			const isTilde = input.charCodeAt(end) === CC_TILDE;

			if (
				input.charCodeAt(end) !== CC_EQUAL &&
				input.charCodeAt(end) !== CC_TILDE
			) {
				return end;
			}

			end += 1;

			if (isTilde) {
				if (input.charCodeAt(end) !== CC_EQUAL) {
					return end;
				}

				end += 1;
			}

			end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
			const value = walkCssTokens.eatIdentSequenceOrString(input, end);

			if (!value) {
				return end;
			}

			const classNameStart = value[2] ? value[0] : value[0] + 1;
			const classNameEnd = value[2] ? value[1] : value[1] - 1;
			const className = unescapeIdentifierCached(
				input.slice(classNameStart, classNameEnd)
			);
			const dep = new CssIcssExportDependency(
				className,
				getReexport(className),
				[classNameStart, classNameEnd],
				true,
				CssIcssExportDependency.EXPORT_MODE.NONE
			);
			const { line: sl, column: sc } = locConverter.get(classNameStart);
			const { line: el, column: ec } = locConverter.get(classNameEnd);
			dep.setLoc(sl, sc, el, ec);
			module.addDependency(dep);
			return value[2] ? classNameEnd : classNameEnd + 1;
		};

		walkCssTokens(source, 0, {
			comment,
			leftCurlyBracket: (input, start, end) => {
				const wasTopLevel = scope === CSS_MODE_TOP_LEVEL;
				if (wasTopLevel) {
					allowImportAtRule = false;
					scope = CSS_MODE_IN_BLOCK;
				} else if (scope !== CSS_MODE_IN_BLOCK) {
					return end;
				}
				if (!isModules) return end;
				if (pureMode) {
					inAtRulePrelude = false;
					if (wasTopLevel) seenTopLevelRule = true;
					const isRulePrelude = isNextRulePrelude;
					if (isRulePrelude) finalizeSelector();
					const top = pureTop();
					if (top) top.hasNestedBlock = true;
					const inheritedSkip = top ? top.skipChildren : false;
					pureBlockStack.push({
						ignored: pureIgnorePending,
						skipOwn: inheritedSkip,
						skipChildren: nextBlockChildrenSkip || inheritedSkip,
						treatAsLeaf: nextBlockTreatAsLeaf,
						// "this rule is fully pure" (no impure comma-segment) OR any
						// ancestor pure. Matches PCSL's `[isPureSelectorSymbol]`.
						ancestorHadLocal:
							parentEffectivePure() ||
							(isRulePrelude && !currentRuleHasImpureSelector),
						impure: isRulePrelude && currentRuleHasImpureSelector,
						hasDirectDecl: false,
						hasNestedBlock: false,
						isRulePrelude,
						preludeStart: currentRulePreludeStart,
						preludeEnd: start
					});
					pureIgnorePending = false;
					nextBlockChildrenSkip = false;
					nextBlockTreatAsLeaf = false;
					currentRuleHasImpureSelector = false;
					currentSelectorHasLocal = false;
					currentRulePreludeStart = end;
				}
				blockNestingLevel = wasTopLevel ? 1 : blockNestingLevel + 1;
				isNextRulePrelude = isNextNestedSyntax(input, end);
				return end;
			},
			rightCurlyBracket: (input, start, end) => {
				if (scope !== CSS_MODE_IN_BLOCK) return end;
				const closing = blockNestingLevel === 1;
				if (closing) {
					scope = CSS_MODE_TOP_LEVEL;
					blockNestingLevel = 0;
					if (!isModules) return end;
					isNextRulePrelude = true;
					modeData = undefined;
					lastLocalIdentifiers = [];
					currentRulePrevComposesFile = undefined;
					currentRuleComposesFiles.clear();
				} else {
					blockNestingLevel--;
					if (!isModules) return end;
					isNextRulePrelude = isNextNestedSyntax(input, end);
				}
				if (pureMode) {
					const frame = pureBlockStack.pop();
					if (frame) {
						// PCSL throws on impure rules whose body has any non-rule
						// content (declaration, empty body). Rules whose body is
						// only nested rules are skipped β€” child rules carry the
						// check themselves.
						if (
							!pureNoCheck &&
							!frame.ignored &&
							!frame.skipOwn &&
							frame.isRulePrelude &&
							frame.impure &&
							(frame.hasDirectDecl ||
								!frame.hasNestedBlock ||
								frame.treatAsLeaf)
						) {
							reportPureRule(frame.preludeStart, frame.preludeEnd);
						}
						// Propagate "has direct declaration" through at-rule frames
						// so a parent rule containing only e.g. `@media { decl }` is
						// still treated as "rule with declarations".
						if (!frame.isRulePrelude && frame.hasDirectDecl) {
							const parent = pureTop();
							if (parent) parent.hasDirectDecl = true;
						}
					}
					currentRuleHasImpureSelector = false;
					currentSelectorHasLocal = false;
					currentRulePreludeStart = end;
				}
				return end;
			},
			url: (input, start, end, contentStart, contentEnd) => {
				if (!this.options.url) {
					return end;
				}

				return processOldURLFunction(
					input,
					start,
					end,
					contentStart,
					contentEnd
				);
			},
			atKeyword: (input, start, end) => {
				const name = input.slice(start, end).toLowerCase();
				const wasTopLevel = scope === CSS_MODE_TOP_LEVEL;
				if (pureMode) {
					inAtRulePrelude = true;
					// Match PCSL's `isPureCheckDisabled`: any non-comment top-level
					// node (including `;`-terminated at-rules like `@import`) seals
					// the leading-comments window.
					if (wasTopLevel) seenTopLevelRule = true;
				}

				let pos = end;
				switch (name) {
					case "@namespace": {
						this._emitWarning(
							state,
							"'@namespace' is not supported in bundled CSS",
							locConverter,
							start,
							end
						);

						pos = eatUntilSemi(input, start);
						break;
					}
					case "@charset": {
						const atRuleEnd = eatUntilSemi(input, start);

						if (/** @type {CssModule} */ (module).exportType === "style") {
							pos = atRuleEnd;
							break;
						}

						const dep = new ConstDependency("", [start, atRuleEnd + 1]);
						module.addPresentationalDependency(dep);

						const value = walkCssTokens.eatString(input, end);

						if (!value) {
							pos = atRuleEnd;
							break;
						}

						/** @type {BuildInfo} */
						(module.buildInfo).charset = input
							.slice(value[0] + 1, value[1] - 1)
							.toUpperCase();

						pos = atRuleEnd;
						break;
					}
					case "@import": {
						if (!this.options.import) {
							pos = eatUntilSemi(input, end);
							break;
						}

						if (!allowImportAtRule) {
							this._emitWarning(
								state,
								"Any '@import' rules must precede all other rules",
								locConverter,
								start,
								end
							);
							pos = eatUntilSemi(input, end);
							break;
						}

						pos = processAtImport(input, start, end);
						break;
					}
					default: {
						if (isModules) {
							if (name === "@value") {
								pos = processAtValue(input, start, end);
								break;
							} else if (
								this.options.animation &&
								OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) &&
								isLocalMode()
							) {
								if (pureMode) {
									nextBlockChildrenSkip = true;
									nextBlockTreatAsLeaf = true;
								}
								pos = processLocalAtRule(input, end, {
									string: true,
									identifier: true
								});
								break;
							} else if (
								this.options.customIdents &&
								name === "@counter-style" &&
								isLocalMode()
							) {
								if (pureMode) {
									nextBlockChildrenSkip = true;
									nextBlockTreatAsLeaf = true;
								}
								pos = processLocalAtRule(input, end, {
									identifier: true
								});
								break;
							} else if (
								this.options.container &&
								name === "@container" &&
								isLocalMode()
							) {
								pos = processLocalAtRule(input, end, {
									identifier: /^(none|and|or|not)$/
								});
								break;
							} else if (name === "@scope") {
								isNextRulePrelude = true;
								break;
							}

							isNextRulePrelude = false;
						}
					}
				}

				// If the at-rule consumed its own `;` (for `@import`/`@value`/
				// `@charset`/`@namespace`), advance the prelude pointer so a
				// later impure rule's reported selector doesn't include this
				// at-rule's text. Body-bearing at-rules return at `{` β€” let
				// `leftCurlyBracket` handle those.
				if (pureMode && wasTopLevel && pos > end) {
					let probe = pos - 1;
					while (
						probe > end &&
						walkCssTokens.isWhiteSpace(input.charCodeAt(probe))
					) {
						probe--;
					}
					if (input.charCodeAt(probe) === CC_SEMICOLON) {
						currentRulePreludeStart = pos;
					}
				}

				return pos;
			},
			semicolon: (input, start, end) => {
				if (isModules && scope === CSS_MODE_IN_BLOCK) {
					isNextRulePrelude = isNextNestedSyntax(input, end);
				}
				if (pureMode) {
					if (scope === CSS_MODE_IN_BLOCK) {
						if (
							balanced.length === 0 &&
							!isNextRulePrelude &&
							!inAtRulePrelude
						) {
							const top = pureTop();
							if (top) top.hasDirectDecl = true;
						}
					} else if (scope === CSS_MODE_TOP_LEVEL && balanced.length === 0) {
						// Top-level `;` ends a statement (e.g. `@import "x";`).
						// Advance the prelude pointer so a later impure rule's
						// reported selector doesn't include the preceding text.
						currentRulePreludeStart = end;
					}
					inAtRulePrelude = false;
				}
				return end;
			},
			identifier: (input, start, end) => {
				if (isModules) {
					const identifier = input.slice(start, end);

					if (
						this.options.dashedIdents &&
						isLocalMode() &&
						isDashedIdentifier(identifier)
					) {
						return processDashedIdent(input, start, end);
					}

					if (icssDefinitions.has(identifier)) {
						return processICSSSymbol(identifier, start, end);
					}

					switch (scope) {
						case CSS_MODE_IN_BLOCK: {
							if (isModules && !isNextRulePrelude) {
								if (balanced.length === 0 && !inAtRulePrelude) {
									const top = pureTop();
									if (top) top.hasDirectDecl = true;
								}
								// Handle only top level values and not inside functions
								return processLocalDeclaration(input, start, end);
							}
							break;
						}
					}
				}

				return end;
			},
			delim: (input, start, end) => {
				const ch = input.charCodeAt(start);
				if (ch === CC_FULL_STOP && isNextRulePrelude && isLocalMode()) {
					return processClassSelector(input, start, end);
				}
				if (
					ch === CC_AMPERSAND &&
					isNextRulePrelude &&
					parentEffectivePure() &&
					pureMode
				) {
					currentSelectorHasLocal = true;
				}

				return end;
			},
			hash: (input, start, end, isID) => {
				if (isNextRulePrelude && isLocalMode() && isID) {
					return processIdSelector(input, start, end);
				}

				return end;
			},
			colon: (input, start, end) => {
				if (isModules) {
					const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
						input,
						end
					);
					if (!ident) return end;
					const name = input.slice(ident[0], ident[1]).toLowerCase();

					switch (scope) {
						case CSS_MODE_TOP_LEVEL: {
							if (name === "import") {
								const pos = processImportOrExport(0, input, ident[1]);
								const dep = new ConstDependency("", [start, pos]);
								module.addPresentationalDependency(dep);
								return pos;
							} else if (name === "export") {
								const pos = processImportOrExport(1, input, ident[1]);
								const dep = new ConstDependency("", [start, pos]);
								module.addPresentationalDependency(dep);
								return pos;
							}
						}
						// falls through
						default: {
							if (isNextRulePrelude) {
								const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS;

								if (isFn && name === "local") {
									// Eat extra whitespace
									const end = walkCssTokens.eatWhitespaceAndComments(
										input,
										ident[1] + 1
									)[0];
									modeData = LOCAL_MODE;
									const dep = new ConstDependency("", [start, end]);
									module.addPresentationalDependency(dep);
									balanced.push([":local", start, end, true]);
									return end;
								} else if (name === "local") {
									modeData = LOCAL_MODE;
									const found = walkCssTokens.eatWhitespaceAndComments(
										input,
										ident[1]
									);

									if (!found[1]) {
										this._emitWarning(
											state,
											`Missing whitespace after ':local' in '${input.slice(
												start,
												eatUntilLeftCurly(input, end) + 1
											)}'`,
											locConverter,
											start,
											end
										);
									}

									end = walkCssTokens.eatWhitespace(input, ident[1]);
									const dep = new ConstDependency("", [start, end]);
									module.addPresentationalDependency(dep);
									return end;
								} else if (isFn && name === "global") {
									// Eat extra whitespace
									const end = walkCssTokens.eatWhitespaceAndComments(
										input,
										ident[1] + 1
									)[0];
									modeData = GLOBAL_MODE;
									const dep = new ConstDependency("", [start, end]);
									module.addPresentationalDependency(dep);
									balanced.push([":global", start, end, true]);
									return end;
								} else if (name === "global") {
									modeData = GLOBAL_MODE;
									// Eat extra whitespace
									const found = walkCssTokens.eatWhitespaceAndComments(
										input,
										ident[1]
									);

									if (!found[1]) {
										this._emitWarning(
											state,
											`Missing whitespace after ':global' in '${input.slice(
												start,
												eatUntilLeftCurly(input, end) + 1
											)}'`,
											locConverter,
											start,
											end
										);
									}

									end = walkCssTokens.eatWhitespace(input, ident[1]);
									const dep = new ConstDependency("", [start, end]);
									module.addPresentationalDependency(dep);
									return end;
								}
							}
						}
					}
				}

				lastTokenEndForComments = end;

				return end;
			},
			function: (input, start, end) => {
				const name = input
					.slice(start, end - 1)
					.replace(/\\/g, "")
					.toLowerCase();

				balanced.push([name, start, end]);

				switch (name) {
					case "src":
					case "url": {
						if (!this.options.url) {
							return end;
						}

						return processURLFunction(input, end, name);
					}
					default: {
						if (this.options.url && IMAGE_SET_FUNCTION.test(name)) {
							return processImageSetFunction(input, start, end);
						}

						if (isModules) {
							if (
								this.options.function &&
								isLocalMode() &&
								isDashedIdentifier(name)
							) {
								return processDashedIdent(input, start, end);
							}

							const type =
								name === "local" ? 1 : name === "global" ? 2 : undefined;

							if (type && !isNextRulePrelude) {
								return processLocalOrGlobalFunction(input, type, start, end);
							}
						}
					}
				}

				return end;
			},
			leftSquareBracket: (input, start, end) => {
				if (isNextRulePrelude && isLocalMode()) {
					return processAttributeSelector(input, start, end);
				}
				return end;
			},
			leftParenthesis: (input, start, end) => {
				balanced.push(["(", start, end]);

				return end;
			},
			rightParenthesis: (input, start, end) => {
				const popped = balanced.pop();

				if (isModules && popped) {
					const isLocal = popped[0] === ":local";
					const isGlobal = popped[0] === ":global";
					if (isLocal || isGlobal) {
						modeData = balanced[balanced.length - 1]
							? balanced[balanced.length - 1][0] === ":local"
								? LOCAL_MODE
								: balanced[balanced.length - 1][0] === ":global"
									? GLOBAL_MODE
									: undefined
							: undefined;
						if (popped[3] && isLocal) {
							while (walkCssTokens.isWhiteSpace(input.charCodeAt(start - 1))) {
								start -= 1;
							}
						}
						const dep = new ConstDependency("", [start, end]);
						module.addPresentationalDependency(dep);
					} else if (isNextRulePrelude) {
						modeData = undefined;
					}
				}

				return end;
			},
			comma: (input, start, end) => {
				if (isModules && balanced.length === 0) {
					// Reset stack for `:global .class :local .class-other` selector after
					modeData = undefined;
					if (pureMode && isNextRulePrelude) finalizeSelector();
				}

				lastTokenEndForComments = start;

				return end;
			}
		});

		/** @type {BuildInfo} */
		(module.buildInfo).strict = true;

		// Topologically sort the files referenced by `composes ... from`
		// declarations and tag each file's first import dep with the
		// resulting `sourceOrder`. `NormalModule#build` then reorders the
		// deps via `sortWithSourceOrder` so the bundle loads them in
		// cascade-correct order. Files stuck in a cycle are not visited
		// and keep their natural loc-based position.
		if (composesFirstFileImport.size > 1) {
			topologicalSort(
				composesGraph,
				[...composesFirstFileImport.keys()],
				(file, i) => {
					/** @type {CssIcssImportDependency} */
					(composesFirstFileImport.get(file)).sourceOrder = i;
				}
			);
		}

		const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);

		buildMeta.exportsType = this.options.namedExports ? "namespace" : "default";
		buildMeta.defaultObject = this.options.namedExports
			? false
			: "redirect-warn";

		if (
			/** @type {CssModule} */ (module).exportType === "text" ||
			/** @type {CssModule} */ (module).exportType === "css-style-sheet"
		) {
			module.addDependency(new StaticExportsDependency(["default"], true));
		} else {
			module.addDependency(new StaticExportsDependency([], true));
		}

		return state;
	}

	/**
	 * Returns comments in the range.
	 * @param {Range} range range
	 * @returns {Comment[]} comments in the range
	 */
	getComments(range) {
		if (!this.comments) return [];
		const [rangeStart, rangeEnd] = range;
		/**
		 * Returns compared.
		 * @param {Comment} comment comment
		 * @param {number} needle needle
		 * @returns {number} compared
		 */
		const compare = (comment, needle) =>
			/** @type {Range} */ (comment.range)[0] - needle;
		const comments = /** @type {Comment[]} */ (this.comments);
		let idx = binarySearchBounds.ge(comments, rangeStart, compare);
		/** @type {Comment[]} */
		const commentsInRange = [];
		while (
			comments[idx] &&
			/** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
		) {
			commentsInRange.push(comments[idx]);
			idx++;
		}

		return commentsInRange;
	}

	/**
	 * Parses comment options.
	 * @param {Range} range range of the comment
	 * @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
	 */
	parseCommentOptions(range) {
		const comments = this.getComments(range);
		if (comments.length === 0) {
			return EMPTY_COMMENT_OPTIONS;
		}
		/** @type {Record<string, EXPECTED_ANY>} */
		const options = {};
		/** @type {(Error & { comment: Comment })[]} */
		const errors = [];
		for (const comment of comments) {
			const { value } = comment;
			if (value && webpackCommentRegExp.test(value)) {
				// try compile only if webpack options comment is present
				try {
					for (let [key, val] of Object.entries(
						vm.runInContext(
							`(function(){return {${value}};})()`,
							this.magicCommentContext
						)
					)) {
						if (typeof val === "object" && val !== null) {
							val =
								val.constructor.name === "RegExp"
									? new RegExp(val)
									: JSON.parse(JSON.stringify(val));
						}
						options[key] = val;
					}
				} catch (err) {
					const newErr = new Error(String(/** @type {Error} */ (err).message));
					newErr.stack = String(/** @type {Error} */ (err).stack);
					Object.assign(newErr, { comment });
					errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
				}
			}
		}
		return { options, errors };
	}
}

module.exports = CssParser;
module.exports.escapeIdentifier = escapeIdentifier;
module.exports.unescapeIdentifier = unescapeIdentifier;