code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const { SyncWaterfallHook } = require("tapable");
const Compilation = require("../Compilation");
const RuntimeGlobals = require("../RuntimeGlobals");
const RuntimeModule = require("../RuntimeModule");
const Template = require("../Template");
const compileBooleanMatcher = require("../util/compileBooleanMatcher");
const { chunkHasCss } = require("./CssModulesPlugin");

/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../Compilation").RuntimeRequirementsContext} RuntimeRequirementsContext */
/** @typedef {import("../Module").ReadOnlyRuntimeRequirements} ReadOnlyRuntimeRequirements */

/**
 * @typedef {object} CssLoadingRuntimeModulePluginHooks
 * @property {SyncWaterfallHook<[string, Chunk]>} createStylesheet
 * @property {SyncWaterfallHook<[string, Chunk]>} linkPreload
 * @property {SyncWaterfallHook<[string, Chunk]>} linkPrefetch
 */

/** @type {WeakMap<Compilation, CssLoadingRuntimeModulePluginHooks>} */
const compilationHooksMap = new WeakMap();

class CssLoadingRuntimeModule extends RuntimeModule {
	/**
	 * @param {Compilation} compilation the compilation
	 * @returns {CssLoadingRuntimeModulePluginHooks} hooks
	 */
	static getCompilationHooks(compilation) {
		if (!(compilation instanceof Compilation)) {
			throw new TypeError(
				"The 'compilation' argument must be an instance of Compilation"
			);
		}
		let hooks = compilationHooksMap.get(compilation);
		if (hooks === undefined) {
			hooks = {
				createStylesheet: new SyncWaterfallHook(["source", "chunk"]),
				linkPreload: new SyncWaterfallHook(["source", "chunk"]),
				linkPrefetch: new SyncWaterfallHook(["source", "chunk"])
			};
			compilationHooksMap.set(compilation, hooks);
		}
		return hooks;
	}

	/**
	 * @param {ReadOnlyRuntimeRequirements} runtimeRequirements runtime requirements
	 */
	constructor(runtimeRequirements) {
		super("css loading", 10);

		this._runtimeRequirements = runtimeRequirements;
	}

	/**
	 * @returns {string | null} runtime code
	 */
	generate() {
		const { _runtimeRequirements } = this;
		const compilation = /** @type {Compilation} */ (this.compilation);
		const chunk = /** @type {Chunk} */ (this.chunk);
		const {
			chunkGraph,
			runtimeTemplate,
			outputOptions: {
				crossOriginLoading,
				uniqueName,
				chunkLoadTimeout: loadTimeout,
				cssHeadDataCompression: withCompression
			}
		} = compilation;
		const fn = RuntimeGlobals.ensureChunkHandlers;
		const conditionMap = chunkGraph.getChunkConditionMap(
			/** @type {Chunk} */ (chunk),
			/**
			 * @param {Chunk} chunk the chunk
			 * @param {ChunkGraph} chunkGraph the chunk graph
			 * @returns {boolean} true, if the chunk has css
			 */
			(chunk, chunkGraph) =>
				!!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css")
		);
		const hasCssMatcher = compileBooleanMatcher(conditionMap);

		const withLoading =
			_runtimeRequirements.has(RuntimeGlobals.ensureChunkHandlers) &&
			hasCssMatcher !== false;
		const withPrefetch = this._runtimeRequirements.has(
			RuntimeGlobals.prefetchChunkHandlers
		);
		const withPreload = this._runtimeRequirements.has(
			RuntimeGlobals.preloadChunkHandlers
		);
		/** @type {boolean} */
		const withHmr = _runtimeRequirements.has(
			RuntimeGlobals.hmrDownloadUpdateHandlers
		);
		/** @type {Set<number | string | null>} */
		const initialChunkIdsWithCss = new Set();
		/** @type {Set<number | string | null>} */
		const initialChunkIdsWithoutCss = new Set();
		for (const c of /** @type {Chunk} */ (chunk).getAllInitialChunks()) {
			(chunkHasCss(c, chunkGraph)
				? initialChunkIdsWithCss
				: initialChunkIdsWithoutCss
			).add(c.id);
		}

		if (!withLoading && !withHmr && initialChunkIdsWithCss.size === 0) {
			return null;
		}

		const { linkPreload, linkPrefetch } =
			CssLoadingRuntimeModule.getCompilationHooks(compilation);

		const withFetchPriority = _runtimeRequirements.has(
			RuntimeGlobals.hasFetchPriority
		);

		const { createStylesheet } =
			CssLoadingRuntimeModule.getCompilationHooks(compilation);

		const stateExpression = withHmr
			? `${RuntimeGlobals.hmrRuntimeStatePrefix}_css`
			: undefined;

		const code = Template.asString([
			"link = document.createElement('link');",
			`if (${RuntimeGlobals.scriptNonce}) {`,
			Template.indent(
				`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
			),
			"}",
			uniqueName
				? 'link.setAttribute("data-webpack", uniqueName + ":" + key);'
				: "",
			withFetchPriority
				? Template.asString([
						"if(fetchPriority) {",
						Template.indent(
							'link.setAttribute("fetchpriority", fetchPriority);'
						),
						"}"
					])
				: "",
			"link.setAttribute(loadingAttribute, 1);",
			'link.rel = "stylesheet";',
			"link.href = url;",
			crossOriginLoading
				? crossOriginLoading === "use-credentials"
					? 'link.crossOrigin = "use-credentials";'
					: Template.asString([
							"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
							Template.indent(
								`link.crossOrigin = ${JSON.stringify(crossOriginLoading)};`
							),
							"}"
						])
				: ""
		]);

		/** @type {(str: string) => number} */
		const cc = str => str.charCodeAt(0);
		const name = uniqueName
			? runtimeTemplate.concatenation(
					"--webpack-",
					{ expr: "uniqueName" },
					"-",
					{ expr: "chunkId" }
				)
			: runtimeTemplate.concatenation("--webpack-", { expr: "chunkId" });

		return Template.asString([
			"// object to store loaded and loading chunks",
			"// undefined = chunk not loaded, null = chunk preloaded/prefetched",
			"// [resolve, reject, Promise] = chunk loading, 0 = chunk loaded",
			`var installedChunks = ${
				stateExpression ? `${stateExpression} = ${stateExpression} || ` : ""
			}{${Array.from(
				initialChunkIdsWithoutCss,
				id => `${JSON.stringify(id)}:0`
			).join(",")}};`,
			"",
			uniqueName
				? `var uniqueName = ${JSON.stringify(
						runtimeTemplate.outputOptions.uniqueName
					)};`
				: "// data-webpack is not used as build has no uniqueName",
			`var loadCssChunkData = ${runtimeTemplate.basicFunction(
				"target, link, chunkId",
				[
					`var data, token = "", token2 = "", exports = {}, ${
						withHmr ? "moduleIds = [], " : ""
					}name = ${name}, i, cc = 1;`,
					"try {",
					Template.indent([
						"if(!link) link = loadStylesheet(chunkId);",
						// `link.sheet.rules` for legacy browsers
						"var cssRules = link.sheet.cssRules || link.sheet.rules;",
						"var j = cssRules.length - 1;",
						"while(j > -1 && !data) {",
						Template.indent([
							"var style = cssRules[j--].style;",
							"if(!style) continue;",
							`data = style.getPropertyValue(name);`
						]),
						"}"
					]),
					"}catch(e){}",
					"if(!data) {",
					Template.indent([
						"data = getComputedStyle(document.head).getPropertyValue(name);"
					]),
					"}",
					"if(!data) return [];",
					withCompression
						? Template.asString([
								// LZW decode
								`var map = {}, char = data[0], oldPhrase = char, decoded = char, code = 256, maxCode = ${"\uffff".charCodeAt(
									0
								)}, phrase;`,
								"for (i = 1; i < data.length; i++) {",
								Template.indent([
									"cc = data[i].charCodeAt(0);",
									"if (cc < 256) phrase = data[i]; else phrase = map[cc] ? map[cc] : (oldPhrase + char);",
									"decoded += phrase;",
									"char = phrase.charAt(0);",
									"map[code] = oldPhrase + char;",
									"if (++code > maxCode) { code = 256; map = {}; }",
									"oldPhrase = phrase;"
								]),
								"}",
								"data = decoded;"
							])
						: "// css head data compression is disabled",
					"for(i = 0; cc; i++) {",
					Template.indent([
						"cc = data.charCodeAt(i);",
						`if(cc == ${cc(":")}) { token2 = token; token = ""; }`,
						`else if(cc == ${cc(
							"/"
						)}) { token = token.replace(/^_/, ""); token2 = token2.replace(/^_/, ""); exports[token2] = token; token = ""; token2 = ""; }`,
						`else if(cc == ${cc("&")}) { ${
							RuntimeGlobals.makeNamespaceObject
						}(exports); }`,
						`else if(!cc || cc == ${cc(
							","
						)}) { token = token.replace(/^_/, ""); target[token] = (${runtimeTemplate.basicFunction(
							"exports, module",
							`module.exports = exports;`
						)}).bind(null, exports); ${
							withHmr ? "moduleIds.push(token); " : ""
						}token = ""; token2 = ""; exports = {};  }`,
						`else if(cc == ${cc("\\")}) { token += data[++i] }`,
						`else { token += data[i]; }`
					]),
					"}",
					`${
						withHmr ? `if(target == ${RuntimeGlobals.moduleFactories}) ` : ""
					}installedChunks[chunkId] = 0;`,
					withHmr ? "return moduleIds;" : ""
				]
			)}`,
			'var loadingAttribute = "data-webpack-loading";',
			`var loadStylesheet = ${runtimeTemplate.basicFunction(
				"chunkId, url, done" +
					(withHmr ? ", hmr" : "") +
					(withFetchPriority ? ", fetchPriority" : ""),
				[
					'var link, needAttach, key = "chunk-" + chunkId;',
					withHmr ? "if(!hmr) {" : "",
					'var links = document.getElementsByTagName("link");',
					"for(var i = 0; i < links.length; i++) {",
					Template.indent([
						"var l = links[i];",
						`if(l.rel == "stylesheet" && (${
							withHmr
								? 'l.href.startsWith(url) || l.getAttribute("href").startsWith(url)'
								: 'l.href == url || l.getAttribute("href") == url'
						}${
							uniqueName
								? ' || l.getAttribute("data-webpack") == uniqueName + ":" + key'
								: ""
						})) { link = l; break; }`
					]),
					"}",
					"if(!done) return link;",
					withHmr ? "}" : "",
					"if(!link) {",
					Template.indent([
						"needAttach = true;",
						createStylesheet.call(code, /** @type {Chunk} */ (this.chunk))
					]),
					"}",
					`var onLinkComplete = ${runtimeTemplate.basicFunction(
						"prev, event",
						Template.asString([
							"link.onerror = link.onload = null;",
							"link.removeAttribute(loadingAttribute);",
							"clearTimeout(timeout);",
							'if(event && event.type != "load") link.parentNode.removeChild(link)',
							"done(event);",
							"if(prev) return prev(event);"
						])
					)};`,
					"if(link.getAttribute(loadingAttribute)) {",
					Template.indent([
						`var timeout = setTimeout(onLinkComplete.bind(null, undefined, { type: 'timeout', target: link }), ${loadTimeout});`,
						"link.onerror = onLinkComplete.bind(null, link.onerror);",
						"link.onload = onLinkComplete.bind(null, link.onload);"
					]),
					"} else onLinkComplete(undefined, { type: 'load', target: link });", // We assume any existing stylesheet is render blocking
					withHmr ? "hmr ? document.head.insertBefore(link, hmr) :" : "",
					"needAttach && document.head.appendChild(link);",
					"return link;"
				]
			)};`,
			initialChunkIdsWithCss.size > 2
				? `${JSON.stringify(
						Array.from(initialChunkIdsWithCss)
					)}.forEach(loadCssChunkData.bind(null, ${
						RuntimeGlobals.moduleFactories
					}, 0));`
				: initialChunkIdsWithCss.size > 0
					? `${Array.from(
							initialChunkIdsWithCss,
							id =>
								`loadCssChunkData(${
									RuntimeGlobals.moduleFactories
								}, 0, ${JSON.stringify(id)});`
						).join("")}`
					: "// no initial css",
			"",
			withLoading
				? Template.asString([
						`${fn}.css = ${runtimeTemplate.basicFunction(
							`chunkId, promises${withFetchPriority ? " , fetchPriority" : ""}`,
							[
								"// css chunk loading",
								`var installedChunkData = ${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId) ? installedChunks[chunkId] : undefined;`,
								'if(installedChunkData !== 0) { // 0 means "already installed".',
								Template.indent([
									"",
									'// a Promise means "currently loading".',
									"if(installedChunkData) {",
									Template.indent(["promises.push(installedChunkData[2]);"]),
									"} else {",
									Template.indent([
										hasCssMatcher === true
											? "if(true) { // all chunks have CSS"
											: `if(${hasCssMatcher("chunkId")}) {`,
										Template.indent([
											"// setup Promise in chunk cache",
											`var promise = new Promise(${runtimeTemplate.expressionFunction(
												`installedChunkData = installedChunks[chunkId] = [resolve, reject]`,
												"resolve, reject"
											)});`,
											"promises.push(installedChunkData[2] = promise);",
											"",
											"// start chunk loading",
											`var url = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
											"// create error before stack unwound to get useful stacktrace later",
											"var error = new Error();",
											`var loadingEnded = ${runtimeTemplate.basicFunction(
												"event",
												[
													`if(${RuntimeGlobals.hasOwnProperty}(installedChunks, chunkId)) {`,
													Template.indent([
														"installedChunkData = installedChunks[chunkId];",
														"if(installedChunkData !== 0) installedChunks[chunkId] = undefined;",
														"if(installedChunkData) {",
														Template.indent([
															'if(event.type !== "load") {',
															Template.indent([
																"var errorType = event && event.type;",
																"var realHref = event && event.target && event.target.href;",
																"error.message = 'Loading css chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';",
																"error.name = 'ChunkLoadError';",
																"error.type = errorType;",
																"error.request = realHref;",
																"installedChunkData[1](error);"
															]),
															"} else {",
															Template.indent([
																`loadCssChunkData(${RuntimeGlobals.moduleFactories}, link, chunkId);`,
																"installedChunkData[0]();"
															]),
															"}"
														]),
														"}"
													]),
													"}"
												]
											)};`,
											`var link = loadStylesheet(chunkId, url, loadingEnded${
												withFetchPriority ? ", fetchPriority" : ""
											});`
										]),
										"} else installedChunks[chunkId] = 0;"
									]),
									"}"
								]),
								"}"
							]
						)};`
					])
				: "// no chunk loading",
			"",
			withPrefetch && hasCssMatcher !== false
				? `${
						RuntimeGlobals.prefetchChunkHandlers
					}.s = ${runtimeTemplate.basicFunction("chunkId", [
						`if((!${
							RuntimeGlobals.hasOwnProperty
						}(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
							hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")
						}) {`,
						Template.indent([
							"installedChunks[chunkId] = null;",
							linkPrefetch.call(
								Template.asString([
									"var link = document.createElement('link');",
									crossOriginLoading
										? `link.crossOrigin = ${JSON.stringify(
												crossOriginLoading
											)};`
										: "",
									`if (${RuntimeGlobals.scriptNonce}) {`,
									Template.indent(
										`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
									),
									"}",
									'link.rel = "prefetch";',
									'link.as = "style";',
									`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`
								]),
								chunk
							),
							"document.head.appendChild(link);"
						]),
						"}"
					])};`
				: "// no prefetching",
			"",
			withPreload && hasCssMatcher !== false
				? `${
						RuntimeGlobals.preloadChunkHandlers
					}.s = ${runtimeTemplate.basicFunction("chunkId", [
						`if((!${
							RuntimeGlobals.hasOwnProperty
						}(installedChunks, chunkId) || installedChunks[chunkId] === undefined) && ${
							hasCssMatcher === true ? "true" : hasCssMatcher("chunkId")
						}) {`,
						Template.indent([
							"installedChunks[chunkId] = null;",
							linkPreload.call(
								Template.asString([
									"var link = document.createElement('link');",
									"link.charset = 'utf-8';",
									`if (${RuntimeGlobals.scriptNonce}) {`,
									Template.indent(
										`link.setAttribute("nonce", ${RuntimeGlobals.scriptNonce});`
									),
									"}",
									'link.rel = "preload";',
									'link.as = "style";',
									`link.href = ${RuntimeGlobals.publicPath} + ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
									crossOriginLoading
										? crossOriginLoading === "use-credentials"
											? 'link.crossOrigin = "use-credentials";'
											: Template.asString([
													"if (link.href.indexOf(window.location.origin + '/') !== 0) {",
													Template.indent(
														`link.crossOrigin = ${JSON.stringify(
															crossOriginLoading
														)};`
													),
													"}"
												])
										: ""
								]),
								chunk
							),
							"document.head.appendChild(link);"
						]),
						"}"
					])};`
				: "// no preloaded",
			withHmr
				? Template.asString([
						"var oldTags = [];",
						"var newTags = [];",
						`var applyHandler = ${runtimeTemplate.basicFunction("options", [
							`return { dispose: ${runtimeTemplate.basicFunction(
								"",
								[]
							)}, apply: ${runtimeTemplate.basicFunction("", [
								"var moduleIds = [];",
								`newTags.forEach(${runtimeTemplate.expressionFunction(
									"info[1].sheet.disabled = false",
									"info"
								)});`,
								"while(oldTags.length) {",
								Template.indent([
									"var oldTag = oldTags.pop();",
									"if(oldTag.parentNode) oldTag.parentNode.removeChild(oldTag);"
								]),
								"}",
								"while(newTags.length) {",
								Template.indent([
									`var info = newTags.pop();`,
									`var chunkModuleIds = loadCssChunkData(${RuntimeGlobals.moduleFactories}, info[1], info[0]);`,
									`chunkModuleIds.forEach(${runtimeTemplate.expressionFunction(
										"moduleIds.push(id)",
										"id"
									)});`
								]),
								"}",
								"return moduleIds;"
							])} };`
						])}`,
						`var cssTextKey = ${runtimeTemplate.returningFunction(
							`Array.from(link.sheet.cssRules, ${runtimeTemplate.returningFunction(
								"r.cssText",
								"r"
							)}).join()`,
							"link"
						)}`,
						`${
							RuntimeGlobals.hmrDownloadUpdateHandlers
						}.css = ${runtimeTemplate.basicFunction(
							"chunkIds, removedChunks, removedModules, promises, applyHandlers, updatedModulesList",
							[
								"applyHandlers.push(applyHandler);",
								`chunkIds.forEach(${runtimeTemplate.basicFunction("chunkId", [
									`var filename = ${RuntimeGlobals.getChunkCssFilename}(chunkId);`,
									`var url = ${RuntimeGlobals.publicPath} + filename;`,
									"var oldTag = loadStylesheet(chunkId, url);",
									"if(!oldTag) return;",
									`promises.push(new Promise(${runtimeTemplate.basicFunction(
										"resolve, reject",
										[
											`var link = loadStylesheet(chunkId, url + (url.indexOf("?") < 0 ? "?" : "&") + "hmr=" + Date.now(), ${runtimeTemplate.basicFunction(
												"event",
												[
													'if(event.type !== "load") {',
													Template.indent([
														"var errorType = event && event.type;",
														"var realHref = event && event.target && event.target.href;",
														"error.message = 'Loading css hot update chunk ' + chunkId + ' failed.\\n(' + errorType + ': ' + realHref + ')';",
														"error.name = 'ChunkLoadError';",
														"error.type = errorType;",
														"error.request = realHref;",
														"reject(error);"
													]),
													"} else {",
													Template.indent([
														"try { if(cssTextKey(oldTag) == cssTextKey(link)) { if(link.parentNode) link.parentNode.removeChild(link); return resolve(); } } catch(e) {}",
														"var factories = {};",
														"loadCssChunkData(factories, link, chunkId);",
														`Object.keys(factories).forEach(${runtimeTemplate.expressionFunction(
															"updatedModulesList.push(id)",
															"id"
														)})`,
														"link.sheet.disabled = true;",
														"oldTags.push(oldTag);",
														"newTags.push([chunkId, link]);",
														"resolve();"
													]),
													"}"
												]
											)}, oldTag);`
										]
									)}));`
								])});`
							]
						)}`
					])
				: "// no hmr"
		]);
	}
}

module.exports = CssLoadingRuntimeModule;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const {
	ConcatSource,
	PrefixSource,
	ReplaceSource,
	CachedSource
} = require("webpack-sources");
const CssModule = require("../CssModule");
const HotUpdateChunk = require("../HotUpdateChunk");
const {
	CSS_MODULE_TYPE,
	CSS_MODULE_TYPE_GLOBAL,
	CSS_MODULE_TYPE_MODULE,
	CSS_MODULE_TYPE_AUTO
} = require("../ModuleTypeConstants");
const RuntimeGlobals = require("../RuntimeGlobals");
const SelfModuleFactory = require("../SelfModuleFactory");
const WebpackError = require("../WebpackError");
const CssExportDependency = require("../dependencies/CssExportDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const { compareModulesByIdentifier } = require("../util/comparators");
const createSchemaValidation = require("../util/create-schema-validation");
const createHash = require("../util/createHash");
const { getUndoPath } = require("../util/identifier");
const memoize = require("../util/memoize");
const nonNumericOnlyHash = require("../util/nonNumericOnlyHash");
const CssExportsGenerator = require("./CssExportsGenerator");
const CssGenerator = require("./CssGenerator");
const CssParser = require("./CssParser");

/** @typedef {import("webpack-sources").Source} Source */
/** @typedef {import("../../declarations/WebpackOptions").Output} OutputOptions */
/** @typedef {import("../Chunk")} Chunk */
/** @typedef {import("../ChunkGraph")} ChunkGraph */
/** @typedef {import("../CodeGenerationResults")} CodeGenerationResults */
/** @typedef {import("../Compilation")} Compilation */
/** @typedef {import("../Compiler")} Compiler */
/** @typedef {import("../CssModule").Inheritance} Inheritance */
/** @typedef {import("../DependencyTemplate").CssExportsData} CssExportsData */
/** @typedef {import("../Module")} Module */
/** @typedef {import("../util/memoize")} Memoize */

const getCssLoadingRuntimeModule = memoize(() =>
	require("./CssLoadingRuntimeModule")
);

/**
 * @param {string} name name
 * @returns {{oneOf: [{$ref: string}], definitions: *}} schema
 */
const getSchema = name => {
	const { definitions } = require("../../schemas/WebpackOptions.json");
	return {
		definitions,
		oneOf: [{ $ref: `#/definitions/${name}` }]
	};
};

const generatorValidationOptions = {
	name: "Css Modules Plugin",
	baseDataPath: "generator"
};
const validateGeneratorOptions = {
	css: createSchemaValidation(
		require("../../schemas/plugins/css/CssGeneratorOptions.check.js"),
		() => getSchema("CssGeneratorOptions"),
		generatorValidationOptions
	),
	"css/auto": createSchemaValidation(
		require("../../schemas/plugins/css/CssAutoGeneratorOptions.check.js"),
		() => getSchema("CssAutoGeneratorOptions"),
		generatorValidationOptions
	),
	"css/module": createSchemaValidation(
		require("../../schemas/plugins/css/CssModuleGeneratorOptions.check.js"),
		() => getSchema("CssModuleGeneratorOptions"),
		generatorValidationOptions
	),
	"css/global": createSchemaValidation(
		require("../../schemas/plugins/css/CssGlobalGeneratorOptions.check.js"),
		() => getSchema("CssGlobalGeneratorOptions"),
		generatorValidationOptions
	)
};

const parserValidationOptions = {
	name: "Css Modules Plugin",
	baseDataPath: "parser"
};
const validateParserOptions = {
	css: createSchemaValidation(
		require("../../schemas/plugins/css/CssParserOptions.check.js"),
		() => getSchema("CssParserOptions"),
		parserValidationOptions
	),
	"css/auto": createSchemaValidation(
		require("../../schemas/plugins/css/CssAutoParserOptions.check.js"),
		() => getSchema("CssAutoParserOptions"),
		parserValidationOptions
	),
	"css/module": createSchemaValidation(
		require("../../schemas/plugins/css/CssModuleParserOptions.check.js"),
		() => getSchema("CssModuleParserOptions"),
		parserValidationOptions
	),
	"css/global": createSchemaValidation(
		require("../../schemas/plugins/css/CssGlobalParserOptions.check.js"),
		() => getSchema("CssGlobalParserOptions"),
		parserValidationOptions
	)
};

/**
 * @param {string} str string
 * @param {boolean=} omitOptionalUnderscore if true, optional underscore is not added
 * @returns {string} escaped string
 */
const escapeCss = (str, omitOptionalUnderscore) => {
	const escaped = `${str}`.replace(
		// cspell:word uffff
		/[^a-zA-Z0-9_\u0081-\uffff-]/g,
		s => `\\${s}`
	);
	return !omitOptionalUnderscore && /^(?!--)[0-9_-]/.test(escaped)
		? `_${escaped}`
		: escaped;
};

/**
 * @param {string} str string
 * @returns {string} encoded string
 */
const LZWEncode = str => {
	/** @type {Map<string, string>} */
	const map = new Map();
	let encoded = "";
	let phrase = str[0];
	let code = 256;
	let maxCode = "\uffff".charCodeAt(0);
	for (let i = 1; i < str.length; i++) {
		const c = str[i];
		if (map.has(phrase + c)) {
			phrase += c;
		} else {
			encoded += phrase.length > 1 ? map.get(phrase) : phrase;
			map.set(phrase + c, String.fromCharCode(code));
			phrase = c;
			if (++code > maxCode) {
				code = 256;
				map.clear();
			}
		}
	}
	encoded += phrase.length > 1 ? map.get(phrase) : phrase;
	return encoded;
};

const plugin = "CssModulesPlugin";

class CssModulesPlugin {
	constructor() {
		/** @type {WeakMap<Source, { undoPath: string, inheritance: Inheritance, source: CachedSource }>} */
		this._moduleCache = new WeakMap();
	}

	/**
	 * Apply the plugin
	 * @param {Compiler} compiler the compiler instance
	 * @returns {void}
	 */
	apply(compiler) {
		compiler.hooks.compilation.tap(
			plugin,
			(compilation, { normalModuleFactory }) => {
				const selfFactory = new SelfModuleFactory(compilation.moduleGraph);
				compilation.dependencyFactories.set(
					CssUrlDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					CssUrlDependency,
					new CssUrlDependency.Template()
				);
				compilation.dependencyTemplates.set(
					CssLocalIdentifierDependency,
					new CssLocalIdentifierDependency.Template()
				);
				compilation.dependencyFactories.set(
					CssSelfLocalIdentifierDependency,
					selfFactory
				);
				compilation.dependencyTemplates.set(
					CssSelfLocalIdentifierDependency,
					new CssSelfLocalIdentifierDependency.Template()
				);
				compilation.dependencyTemplates.set(
					CssExportDependency,
					new CssExportDependency.Template()
				);
				compilation.dependencyFactories.set(
					CssImportDependency,
					normalModuleFactory
				);
				compilation.dependencyTemplates.set(
					CssImportDependency,
					new CssImportDependency.Template()
				);
				compilation.dependencyTemplates.set(
					StaticExportsDependency,
					new StaticExportsDependency.Template()
				);
				for (const type of [
					CSS_MODULE_TYPE,
					CSS_MODULE_TYPE_GLOBAL,
					CSS_MODULE_TYPE_MODULE,
					CSS_MODULE_TYPE_AUTO
				]) {
					normalModuleFactory.hooks.createParser
						.for(type)
						.tap(plugin, parserOptions => {
							validateParserOptions[type](parserOptions);
							const { namedExports } = parserOptions;

							switch (type) {
								case CSS_MODULE_TYPE_GLOBAL:
								case CSS_MODULE_TYPE_AUTO:
									return new CssParser({
										namedExports
									});
								case CSS_MODULE_TYPE:
									return new CssParser({
										allowModeSwitch: false,
										namedExports
									});
								case CSS_MODULE_TYPE_MODULE:
									return new CssParser({
										defaultMode: "local",
										namedExports
									});
							}
						});
					normalModuleFactory.hooks.createGenerator
						.for(type)
						.tap(plugin, generatorOptions => {
							validateGeneratorOptions[type](generatorOptions);

							return generatorOptions.exportsOnly
								? new CssExportsGenerator(
										generatorOptions.exportsConvention,
										generatorOptions.localIdentName,
										generatorOptions.esModule
									)
								: new CssGenerator(
										generatorOptions.exportsConvention,
										generatorOptions.localIdentName,
										generatorOptions.esModule
									);
						});
					normalModuleFactory.hooks.createModuleClass
						.for(type)
						.tap(plugin, (createData, resolveData) => {
							if (resolveData.dependencies.length > 0) {
								// When CSS is imported from CSS there is only one dependency
								const dependency = resolveData.dependencies[0];

								if (dependency instanceof CssImportDependency) {
									const parent =
										/** @type {CssModule} */
										(compilation.moduleGraph.getParentModule(dependency));

									if (parent instanceof CssModule) {
										/** @type {import("../CssModule").Inheritance | undefined} */
										let inheritance;

										if (
											(parent.cssLayer !== null &&
												parent.cssLayer !== undefined) ||
											parent.supports ||
											parent.media
										) {
											if (!inheritance) {
												inheritance = [];
											}

											inheritance.push([
												parent.cssLayer,
												parent.supports,
												parent.media
											]);
										}

										if (parent.inheritance) {
											if (!inheritance) {
												inheritance = [];
											}

											inheritance.push(...parent.inheritance);
										}

										return new CssModule({
											...createData,
											cssLayer: dependency.layer,
											supports: dependency.supports,
											media: dependency.media,
											inheritance
										});
									}

									return new CssModule({
										...createData,
										cssLayer: dependency.layer,
										supports: dependency.supports,
										media: dependency.media
									});
								}
							}

							return new CssModule(createData);
						});
				}
				const orderedCssModulesPerChunk = new WeakMap();
				compilation.hooks.afterCodeGeneration.tap("CssModulesPlugin", () => {
					const { chunkGraph } = compilation;
					for (const chunk of compilation.chunks) {
						if (CssModulesPlugin.chunkHasCss(chunk, chunkGraph)) {
							orderedCssModulesPerChunk.set(
								chunk,
								this.getOrderedChunkCssModules(chunk, chunkGraph, compilation)
							);
						}
					}
				});
				compilation.hooks.contentHash.tap("CssModulesPlugin", chunk => {
					const {
						chunkGraph,
						outputOptions: {
							hashSalt,
							hashDigest,
							hashDigestLength,
							hashFunction
						}
					} = compilation;
					const modules = orderedCssModulesPerChunk.get(chunk);
					if (modules === undefined) return;
					const hash = createHash(hashFunction);
					if (hashSalt) hash.update(hashSalt);
					for (const module of modules) {
						hash.update(chunkGraph.getModuleHash(module, chunk.runtime));
					}
					const digest = /** @type {string} */ (hash.digest(hashDigest));
					chunk.contentHash.css = nonNumericOnlyHash(digest, hashDigestLength);
				});
				compilation.hooks.renderManifest.tap(plugin, (result, options) => {
					const { chunkGraph } = compilation;
					const { hash, chunk, codeGenerationResults } = options;

					if (chunk instanceof HotUpdateChunk) return result;

					/** @type {CssModule[] | undefined} */
					const modules = orderedCssModulesPerChunk.get(chunk);
					if (modules !== undefined) {
						const { path: filename, info } = compilation.getPathWithInfo(
							CssModulesPlugin.getChunkFilenameTemplate(
								chunk,
								compilation.outputOptions
							),
							{
								hash,
								runtime: chunk.runtime,
								chunk,
								contentHashType: "css"
							}
						);
						const undoPath = getUndoPath(
							filename,
							compilation.outputOptions.path,
							false
						);
						result.push({
							render: () =>
								this.renderChunk({
									chunk,
									chunkGraph,
									codeGenerationResults,
									uniqueName: compilation.outputOptions.uniqueName,
									cssHeadDataCompression:
										compilation.outputOptions.cssHeadDataCompression,
									undoPath,
									modules
								}),
							filename,
							info,
							identifier: `css${chunk.id}`,
							hash: chunk.contentHash.css
						});
					}
					return result;
				});
				const globalChunkLoading = compilation.outputOptions.chunkLoading;
				/**
				 * @param {Chunk} chunk the chunk
				 * @returns {boolean} true, when enabled
				 */
				const isEnabledForChunk = chunk => {
					const options = chunk.getEntryOptions();
					const chunkLoading =
						options && options.chunkLoading !== undefined
							? options.chunkLoading
							: globalChunkLoading;
					return chunkLoading === "jsonp" || chunkLoading === "import";
				};
				const onceForChunkSet = new WeakSet();
				/**
				 * @param {Chunk} chunk chunk to check
				 * @param {Set<string>} set runtime requirements
				 */
				const handler = (chunk, set) => {
					if (onceForChunkSet.has(chunk)) return;
					onceForChunkSet.add(chunk);
					if (!isEnabledForChunk(chunk)) return;

					set.add(RuntimeGlobals.publicPath);
					set.add(RuntimeGlobals.getChunkCssFilename);
					set.add(RuntimeGlobals.hasOwnProperty);
					set.add(RuntimeGlobals.moduleFactoriesAddOnly);
					set.add(RuntimeGlobals.makeNamespaceObject);

					const CssLoadingRuntimeModule = getCssLoadingRuntimeModule();
					compilation.addRuntimeModule(chunk, new CssLoadingRuntimeModule(set));
				};
				compilation.hooks.runtimeRequirementInTree
					.for(RuntimeGlobals.hasCssModules)
					.tap(plugin, handler);
				compilation.hooks.runtimeRequirementInTree
					.for(RuntimeGlobals.ensureChunkHandlers)
					.tap(plugin, handler);
				compilation.hooks.runtimeRequirementInTree
					.for(RuntimeGlobals.hmrDownloadUpdateHandlers)
					.tap(plugin, handler);
			}
		);
	}

	/**
	 * @param {Chunk} chunk chunk
	 * @param {Iterable<Module>} modules unordered modules
	 * @param {Compilation} compilation compilation
	 * @returns {Module[]} ordered modules
	 */
	getModulesInOrder(chunk, modules, compilation) {
		if (!modules) return [];

		/** @type {Module[]} */
		const modulesList = [...modules];

		// Get ordered list of modules per chunk group
		// Lists are in reverse order to allow to use Array.pop()
		const modulesByChunkGroup = Array.from(chunk.groupsIterable, chunkGroup => {
			const sortedModules = modulesList
				.map(module => {
					return {
						module,
						index: chunkGroup.getModulePostOrderIndex(module)
					};
				})
				.filter(item => item.index !== undefined)
				.sort(
					(a, b) =>
						/** @type {number} */ (b.index) - /** @type {number} */ (a.index)
				)
				.map(item => item.module);

			return { list: sortedModules, set: new Set(sortedModules) };
		});

		if (modulesByChunkGroup.length === 1)
			return modulesByChunkGroup[0].list.reverse();

		const compareModuleLists = ({ list: a }, { list: b }) => {
			if (a.length === 0) {
				return b.length === 0 ? 0 : 1;
			} else {
				if (b.length === 0) return -1;
				return compareModulesByIdentifier(a[a.length - 1], b[b.length - 1]);
			}
		};

		modulesByChunkGroup.sort(compareModuleLists);

		/** @type {Module[]} */
		const finalModules = [];

		for (;;) {
			const failedModules = new Set();
			const list = modulesByChunkGroup[0].list;
			if (list.length === 0) {
				// done, everything empty
				break;
			}
			/** @type {Module} */
			let selectedModule = list[list.length - 1];
			let hasFailed = undefined;
			outer: for (;;) {
				for (const { list, set } of modulesByChunkGroup) {
					if (list.length === 0) continue;
					const lastModule = list[list.length - 1];
					if (lastModule === selectedModule) continue;
					if (!set.has(selectedModule)) continue;
					failedModules.add(selectedModule);
					if (failedModules.has(lastModule)) {
						// There is a conflict, try other alternatives
						hasFailed = lastModule;
						continue;
					}
					selectedModule = lastModule;
					hasFailed = false;
					continue outer; // restart
				}
				break;
			}
			if (hasFailed) {
				// There is a not resolve-able conflict with the selectedModule
				// TODO print better warning
				compilation.warnings.push(
					new WebpackError(
						`chunk ${chunk.name || chunk.id}\nConflicting order between ${
							/** @type {Module} */
							(hasFailed).readableIdentifier(compilation.requestShortener)
						} and ${selectedModule.readableIdentifier(
							compilation.requestShortener
						)}`
					)
				);
				selectedModule = /** @type {Module} */ (hasFailed);
			}
			// Insert the selected module into the final modules list
			finalModules.push(selectedModule);
			// Remove the selected module from all lists
			for (const { list, set } of modulesByChunkGroup) {
				const lastModule = list[list.length - 1];
				if (lastModule === selectedModule) list.pop();
				else if (hasFailed && set.has(selectedModule)) {
					const idx = list.indexOf(selectedModule);
					if (idx >= 0) list.splice(idx, 1);
				}
			}
			modulesByChunkGroup.sort(compareModuleLists);
		}
		return finalModules;
	}

	/**
	 * @param {Chunk} chunk chunk
	 * @param {ChunkGraph} chunkGraph chunk graph
	 * @param {Compilation} compilation compilation
	 * @returns {Module[]} ordered css modules
	 */
	getOrderedChunkCssModules(chunk, chunkGraph, compilation) {
		return [
			...this.getModulesInOrder(
				chunk,
				/** @type {Iterable<Module>} */
				(
					chunkGraph.getOrderedChunkModulesIterableBySourceType(
						chunk,
						"css-import",
						compareModulesByIdentifier
					)
				),
				compilation
			),
			...this.getModulesInOrder(
				chunk,
				/** @type {Iterable<Module>} */
				(
					chunkGraph.getOrderedChunkModulesIterableBySourceType(
						chunk,
						"css",
						compareModulesByIdentifier
					)
				),
				compilation
			)
		];
	}

	/**
	 * @param {object} options options
	 * @param {string[]} options.metaData meta data
	 * @param {string} options.undoPath undo path for public path auto
	 * @param {Chunk} options.chunk chunk
	 * @param {ChunkGraph} options.chunkGraph chunk graph
	 * @param {CodeGenerationResults} options.codeGenerationResults code generation results
	 * @param {CssModule} options.module css module
	 * @returns {Source} css module source
	 */
	renderModule({
		metaData,
		undoPath,
		chunk,
		chunkGraph,
		codeGenerationResults,
		module
	}) {
		const codeGenResult = codeGenerationResults.get(module, chunk.runtime);
		const moduleSourceContent =
			/** @type {Source} */
			(
				codeGenResult.sources.get("css") ||
					codeGenResult.sources.get("css-import")
			);

		const cacheEntry = this._moduleCache.get(moduleSourceContent);

		/** @type {Inheritance} */
		let inheritance = [[module.cssLayer, module.supports, module.media]];
		if (module.inheritance) {
			inheritance.push(...module.inheritance);
		}

		let source;
		if (
			cacheEntry &&
			cacheEntry.undoPath === undoPath &&
			cacheEntry.inheritance.every(([layer, supports, media], i) => {
				const item = inheritance[i];
				if (Array.isArray(item)) {
					return layer === item[0] && supports === item[1] && media === item[2];
				}
				return false;
			})
		) {
			source = cacheEntry.source;
		} else {
			const moduleSourceCode = /** @type {string} */ (
				moduleSourceContent.source()
			);
			const publicPathAutoRegex = new RegExp(
				CssUrlDependency.PUBLIC_PATH_AUTO,
				"g"
			);
			/** @type {Source} */
			let moduleSource = new ReplaceSource(moduleSourceContent);
			let match;
			while ((match = publicPathAutoRegex.exec(moduleSourceCode))) {
				/** @type {ReplaceSource} */ (moduleSource).replace(
					match.index,
					(match.index += match[0].length - 1),
					undoPath
				);
			}

			for (let i = 0; i < inheritance.length; i++) {
				const layer = inheritance[i][0];
				const supports = inheritance[i][1];
				const media = inheritance[i][2];

				if (media) {
					moduleSource = new ConcatSource(
						`@media ${media} {\n`,
						new PrefixSource("\t", moduleSource),
						"}\n"
					);
				}

				if (supports) {
					moduleSource = new ConcatSource(
						`@supports (${supports}) {\n`,
						new PrefixSource("\t", moduleSource),
						"}\n"
					);
				}

				// Layer can be anonymous
				if (layer !== undefined && layer !== null) {
					moduleSource = new ConcatSource(
						`@layer${layer ? ` ${layer}` : ""} {\n`,
						new PrefixSource("\t", moduleSource),
						"}\n"
					);
				}
			}

			if (moduleSource) {
				moduleSource = new ConcatSource(moduleSource, "\n");
			}

			source = new CachedSource(moduleSource);
			this._moduleCache.set(moduleSourceContent, {
				inheritance,
				undoPath,
				source
			});
		}
		/** @type {CssExportsData | undefined} */
		const cssExportsData =
			codeGenResult.data && codeGenResult.data.get("css-exports");
		const exports = cssExportsData && cssExportsData.exports;
		const esModule = cssExportsData && cssExportsData.esModule;
		let moduleId = chunkGraph.getModuleId(module) + "";

		// When `optimization.moduleIds` is `named` the module id is a path, so we need to normalize it between platforms
		if (typeof moduleId === "string") {
			moduleId = moduleId.replace(/\\/g, "/");
		}

		metaData.push(
			`${
				exports
					? Array.from(
							exports,
							([n, v]) => `${escapeCss(n)}:${escapeCss(v)}/`
						).join("")
					: ""
			}${esModule ? "&" : ""}${escapeCss(moduleId)}`
		);
		return source;
	}

	/**
	 * @param {object} options options
	 * @param {string | undefined} options.uniqueName unique name
	 * @param {boolean | undefined} options.cssHeadDataCompression compress css head data
	 * @param {string} options.undoPath undo path for public path auto
	 * @param {Chunk} options.chunk chunk
	 * @param {ChunkGraph} options.chunkGraph chunk graph
	 * @param {CodeGenerationResults} options.codeGenerationResults code generation results
	 * @param {CssModule[]} options.modules ordered css modules
	 * @returns {Source} generated source
	 */
	renderChunk({
		uniqueName,
		cssHeadDataCompression,
		undoPath,
		chunk,
		chunkGraph,
		codeGenerationResults,
		modules
	}) {
		const source = new ConcatSource();
		/** @type {string[]} */
		const metaData = [];
		for (const module of modules) {
			try {
				const moduleSource = this.renderModule({
					metaData,
					undoPath,
					chunk,
					chunkGraph,
					codeGenerationResults,
					module
				});
				source.add(moduleSource);
			} catch (e) {
				/** @type {Error} */
				(e).message += `\nduring rendering of css ${module.identifier()}`;
				throw e;
			}
		}
		const metaDataStr = metaData.join(",");
		source.add(
			`head{--webpack-${escapeCss(
				(uniqueName ? uniqueName + "-" : "") + chunk.id,
				true
			)}:${cssHeadDataCompression ? LZWEncode(metaDataStr) : metaDataStr};}`
		);
		return source;
	}

	/**
	 * @param {Chunk} chunk chunk
	 * @param {OutputOptions} outputOptions output options
	 * @returns {Chunk["cssFilenameTemplate"] | OutputOptions["cssFilename"] | OutputOptions["cssChunkFilename"]} used filename template
	 */
	static getChunkFilenameTemplate(chunk, outputOptions) {
		if (chunk.cssFilenameTemplate) {
			return chunk.cssFilenameTemplate;
		} else if (chunk.canBeInitial()) {
			return outputOptions.cssFilename;
		} else {
			return outputOptions.cssChunkFilename;
		}
	}

	/**
	 * @param {Chunk} chunk chunk
	 * @param {ChunkGraph} chunkGraph chunk graph
	 * @returns {boolean} true, when the chunk has css
	 */
	static chunkHasCss(chunk, chunkGraph) {
		return (
			!!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css") ||
			!!chunkGraph.getChunkModulesIterableBySourceType(chunk, "css-import")
		);
	}
}

module.exports = CssModulesPlugin;

license: mit


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

const ModuleDependencyWarning = require("../ModuleDependencyWarning");
const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
const Parser = require("../Parser");
const WebpackError = require("../WebpackError");
const ConstDependency = require("../dependencies/ConstDependency");
const CssExportDependency = require("../dependencies/CssExportDependency");
const CssImportDependency = require("../dependencies/CssImportDependency");
const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
const CssUrlDependency = require("../dependencies/CssUrlDependency");
const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
const { parseResource } = require("../util/identifier");
const walkCssTokens = require("./walkCssTokens");

/** @typedef {import("../Parser").ParserState} ParserState */
/** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
/** @typedef {[number, number]} Range */

const CC_LEFT_CURLY = "{".charCodeAt(0);
const CC_RIGHT_CURLY = "}".charCodeAt(0);
const CC_COLON = ":".charCodeAt(0);
const CC_SLASH = "/".charCodeAt(0);
const CC_SEMICOLON = ";".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-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g;
const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i;
const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/;
const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY =
	/^(-\w+-)?animation(-name)?$/i;
const IS_MODULES = /\.module(s)?\.[^.]+$/i;

/**
 * @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(parseInt(match.slice(1).trim(), 16));
			} else {
				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 (error) {
			// Ignore
		}
	}

	return str;
};

class LocConverter {
	/**
	 * @param {string} input input
	 */
	constructor(input) {
		this._input = input;
		this.line = 1;
		this.column = 0;
		this.pos = 0;
	}

	/**
	 * @param {number} pos position
	 * @returns {LocConverter} location converter
	 */
	get(pos) {
		if (this.pos !== pos) {
			if (this.pos < pos) {
				const str = this._input.slice(this.pos, pos);
				let i = str.lastIndexOf("\n");
				if (i === -1) {
					this.column += str.length;
				} else {
					this.column = str.length - i - 1;
					this.line++;
					while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1)
						this.line++;
				}
			} else {
				let i = this._input.lastIndexOf("\n", this.pos);
				while (i >= pos) {
					this.line--;
					i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
				}
				this.column = pos - i;
			}
			this.pos = pos;
		}
		return this;
	}
}

const CSS_MODE_TOP_LEVEL = 0;
const CSS_MODE_IN_BLOCK = 1;
const CSS_MODE_IN_AT_IMPORT = 2;
const CSS_MODE_AT_IMPORT_INVALID = 3;
const CSS_MODE_AT_NAMESPACE_INVALID = 4;

class CssParser extends Parser {
	constructor({
		allowModeSwitch = true,
		defaultMode = "global",
		namedExports = true
	} = {}) {
		super();
		this.allowModeSwitch = allowModeSwitch;
		this.defaultMode = defaultMode;
		this.namedExports = namedExports;
	}

	/**
	 * @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 }
			})
		);
	}

	/**
	 * @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("utf-8");
		} else if (typeof source === "object") {
			throw new Error("webpackAst is unexpected for the CssParser");
		}
		if (source[0] === "\ufeff") {
			source = source.slice(1);
		}

		const module = state.module;

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

		if (
			module.type === CSS_MODULE_TYPE_AUTO &&
			IS_MODULES.test(
				parseResource(module.matchResource || module.resource).path
			)
		) {
			oldDefaultMode = this.defaultMode;

			this.defaultMode = "local";
		}

		const locConverter = new LocConverter(source);
		/** @type {Set<string>}*/
		const declaredCssVariables = new Set();
		/** @type {number} */
		let scope = CSS_MODE_TOP_LEVEL;
		/** @type {number} */
		let blockNestingLevel = 0;
		/** @type {boolean} */
		let allowImportAtRule = true;
		/** @type {"local" | "global" | undefined} */
		let modeData = undefined;
		/** @type {[number, number] | undefined} */
		let lastIdentifier = undefined;
		/** @type [string, number, number][] */
		let balanced = [];
		/** @type {undefined | { start: number, url?: string, urlStart?: number, urlEnd?: number, layer?: string, layerStart?: number, layerEnd?: number, supports?: string, supportsStart?: number, supportsEnd?: number, inSupports?:boolean, media?: string  }} */
		let importData = undefined;
		/** @type {boolean} */
		let inAnimationProperty = false;
		/** @type {boolean} */
		let isNextRulePrelude = true;

		/**
		 * @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);

			if (input[pos] === "}") {
				return false;
			}

			// According spec only identifier can be used as a property name
			const isIdentifier = walkCssTokens.isIdentStartCodePoint(
				input.charCodeAt(pos)
			);

			return !isIdentifier;
		};
		/**
		 * @returns {boolean} true, when in local scope
		 */
		const isLocalMode = () =>
			modeData === "local" ||
			(this.defaultMode === "local" && modeData === undefined);
		/**
		 * @param {string} chars characters
		 * @returns {(input: string, pos: number) => number} function to eat characters
		 */
		const eatUntil = chars => {
			const charCodes = Array.from({ length: chars.length }, (_, i) =>
				chars.charCodeAt(i)
			);
			const arr = Array.from(
				{ length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 },
				() => false
			);
			charCodes.forEach(cc => (arr[cc] = true));
			return (input, pos) => {
				for (;;) {
					const cc = input.charCodeAt(pos);
					if (cc < arr.length && arr[cc]) {
						return pos;
					}
					pos++;
					if (pos === input.length) return pos;
				}
			};
		};
		/**
		 * @param {string} input input
		 * @param {number} pos start position
		 * @param {(input: string, pos: number) => number} eater eater
		 * @returns {[number,string]} new position and text
		 */
		const eatText = (input, pos, eater) => {
			let text = "";
			for (;;) {
				if (input.charCodeAt(pos) === CC_SLASH) {
					const newPos = walkCssTokens.eatComments(input, pos);
					if (pos !== newPos) {
						pos = newPos;
						if (pos === input.length) break;
					} else {
						text += "/";
						pos++;
						if (pos === input.length) break;
					}
				}
				const newPos = eater(input, pos);
				if (pos !== newPos) {
					text += input.slice(pos, newPos);
					pos = newPos;
				} else {
					break;
				}
				if (pos === input.length) break;
			}
			return [pos, text.trimEnd()];
		};
		const eatExportName = eatUntil(":};/");
		const eatExportValue = eatUntil("};/");
		/**
		 * @param {string} input input
		 * @param {number} pos start position
		 * @returns {number} position after parse
		 */
		const parseExports = (input, pos) => {
			pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
			const cc = input.charCodeAt(pos);
			if (cc !== CC_LEFT_CURLY) {
				this._emitWarning(
					state,
					`Unexpected '${input[pos]}' at ${pos} during parsing of ':export' (expected '{')`,
					locConverter,
					pos,
					pos
				);
				return pos;
			}
			pos++;
			pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
			for (;;) {
				if (input.charCodeAt(pos) === CC_RIGHT_CURLY) break;
				pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
				if (pos === input.length) return pos;
				let start = pos;
				let name;
				[pos, name] = eatText(input, pos, eatExportName);
				if (pos === input.length) return pos;
				if (input.charCodeAt(pos) !== CC_COLON) {
					this._emitWarning(
						state,
						`Unexpected '${input[pos]}' at ${pos} during parsing of export name in ':export' (expected ':')`,
						locConverter,
						start,
						pos
					);
					return pos;
				}
				pos++;
				if (pos === input.length) return pos;
				pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
				if (pos === input.length) return pos;
				let value;
				[pos, value] = eatText(input, pos, eatExportValue);
				if (pos === input.length) return pos;
				const cc = input.charCodeAt(pos);
				if (cc === CC_SEMICOLON) {
					pos++;
					if (pos === input.length) return pos;
					pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
					if (pos === input.length) return pos;
				} else if (cc !== CC_RIGHT_CURLY) {
					this._emitWarning(
						state,
						`Unexpected '${input[pos]}' at ${pos} during parsing of export value in ':export' (expected ';' or '}')`,
						locConverter,
						start,
						pos
					);
					return pos;
				}
				const dep = new CssExportDependency(name, value);
				const { line: sl, column: sc } = locConverter.get(start);
				const { line: el, column: ec } = locConverter.get(pos);
				dep.setLoc(sl, sc, el, ec);
				module.addDependency(dep);
			}
			pos++;
			if (pos === input.length) return pos;
			pos = walkCssTokens.eatWhiteLine(input, pos);
			return pos;
		};
		const eatPropertyName = eatUntil(":{};");
		/**
		 * @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) => {
			modeData = undefined;
			pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
			const propertyNameStart = pos;
			const [propertyNameEnd, propertyName] = eatText(
				input,
				pos,
				eatPropertyName
			);
			if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return end;
			pos = propertyNameEnd + 1;
			if (propertyName.startsWith("--")) {
				// CSS Variable
				const { line: sl, column: sc } = locConverter.get(propertyNameStart);
				const { line: el, column: ec } = locConverter.get(propertyNameEnd);
				const name = propertyName.slice(2);
				const dep = new CssLocalIdentifierDependency(
					name,
					[propertyNameStart, propertyNameEnd],
					"--"
				);
				dep.setLoc(sl, sc, el, ec);
				module.addDependency(dep);
				declaredCssVariables.add(name);
			} else if (
				!propertyName.startsWith("--") &&
				OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName)
			) {
				inAnimationProperty = true;
			}
			return pos;
		};
		/**
		 * @param {string} input input
		 */
		const processDeclarationValueDone = input => {
			if (inAnimationProperty && lastIdentifier) {
				const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]);
				const { line: el, column: ec } = locConverter.get(lastIdentifier[1]);
				const name = input.slice(lastIdentifier[0], lastIdentifier[1]);
				const dep = new CssSelfLocalIdentifierDependency(name, lastIdentifier);
				dep.setLoc(sl, sc, el, ec);
				module.addDependency(dep);
				lastIdentifier = undefined;
			}
		};
		const eatKeyframes = eatUntil("{};/");
		const eatNameInVar = eatUntil(",)};/");
		walkCssTokens(source, {
			isSelector: () => {
				return isNextRulePrelude;
			},
			url: (input, start, end, contentStart, contentEnd) => {
				let value = normalizeUrl(input.slice(contentStart, contentEnd), false);

				switch (scope) {
					case CSS_MODE_IN_AT_IMPORT: {
						// Do not parse URLs in `supports(...)`
						if (importData.inSupports) {
							break;
						}

						if (importData.url) {
							this._emitWarning(
								state,
								`Duplicate of 'url(...)' in '${input.slice(
									importData.start,
									end
								)}'`,
								locConverter,
								start,
								end
							);

							break;
						}

						importData.url = value;
						importData.urlStart = start;
						importData.urlEnd = end;
						break;
					}
					// Do not parse URLs in import between rules
					case CSS_MODE_AT_NAMESPACE_INVALID:
					case CSS_MODE_AT_IMPORT_INVALID: {
						break;
					}
					case CSS_MODE_IN_BLOCK: {
						// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
						if (value.length === 0) {
							break;
						}

						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);
						break;
					}
				}
				return end;
			},
			string: (input, start, end) => {
				switch (scope) {
					case CSS_MODE_IN_AT_IMPORT: {
						const insideURLFunction =
							balanced[balanced.length - 1] &&
							balanced[balanced.length - 1][0] === "url";

						// Do not parse URLs in `supports(...)` and other strings if we already have a URL
						if (
							importData.inSupports ||
							(!insideURLFunction && importData.url)
						) {
							break;
						}

						if (insideURLFunction && importData.url) {
							this._emitWarning(
								state,
								`Duplicate of 'url(...)' in '${input.slice(
									importData.start,
									end
								)}'`,
								locConverter,
								start,
								end
							);

							break;
						}

						importData.url = normalizeUrl(
							input.slice(start + 1, end - 1),
							true
						);

						if (!insideURLFunction) {
							importData.urlStart = start;
							importData.urlEnd = end;
						}

						break;
					}
					case CSS_MODE_IN_BLOCK: {
						// TODO move escaped parsing to tokenizer
						const last = balanced[balanced.length - 1];

						if (
							last &&
							(last[0].replace(/\\/g, "").toLowerCase() === "url" ||
								IMAGE_SET_FUNCTION.test(last[0].replace(/\\/g, "")))
						) {
							let value = normalizeUrl(input.slice(start + 1, end - 1), true);

							// Ignore `url()`, `url('')` and `url("")`, they are valid by spec
							if (value.length === 0) {
								break;
							}

							const isUrl = last[0].replace(/\\/g, "").toLowerCase() === "url";
							const dep = new CssUrlDependency(
								value,
								[start, end],
								isUrl ? "string" : "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;
			},
			atKeyword: (input, start, end) => {
				const name = input.slice(start, end).toLowerCase();
				if (name === "@namespace") {
					scope = CSS_MODE_AT_NAMESPACE_INVALID;
					this._emitWarning(
						state,
						"'@namespace' is not supported in bundled CSS",
						locConverter,
						start,
						end
					);
					return end;
				} else if (name === "@import") {
					if (!allowImportAtRule) {
						scope = CSS_MODE_AT_IMPORT_INVALID;
						this._emitWarning(
							state,
							"Any '@import' rules must precede all other rules",
							locConverter,
							start,
							end
						);
						return end;
					}

					scope = CSS_MODE_IN_AT_IMPORT;
					importData = { start };
				} else if (
					this.allowModeSwitch &&
					OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name)
				) {
					let pos = end;
					pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
					if (pos === input.length) return pos;
					const [newPos, name] = eatText(input, pos, eatKeyframes);
					if (newPos === input.length) return newPos;
					if (input.charCodeAt(newPos) !== CC_LEFT_CURLY) {
						this._emitWarning(
							state,
							`Unexpected '${input[newPos]}' at ${newPos} during parsing of @keyframes (expected '{')`,
							locConverter,
							start,
							end
						);

						return newPos;
					}
					if (isLocalMode()) {
						const { line: sl, column: sc } = locConverter.get(pos);
						const { line: el, column: ec } = locConverter.get(newPos);
						const dep = new CssLocalIdentifierDependency(name, [pos, newPos]);
						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
					}
					pos = newPos;
					return pos + 1;
				} else if (this.allowModeSwitch && name === "@property") {
					let pos = end;
					pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
					if (pos === input.length) return pos;
					const propertyNameStart = pos;
					const [propertyNameEnd, propertyName] = eatText(
						input,
						pos,
						eatKeyframes
					);
					if (propertyNameEnd === input.length) return propertyNameEnd;
					if (!propertyName.startsWith("--")) return propertyNameEnd;
					if (input.charCodeAt(propertyNameEnd) !== CC_LEFT_CURLY) {
						this._emitWarning(
							state,
							`Unexpected '${input[propertyNameEnd]}' at ${propertyNameEnd} during parsing of @property (expected '{')`,
							locConverter,
							start,
							end
						);

						return propertyNameEnd;
					}
					const name = propertyName.slice(2);
					declaredCssVariables.add(name);
					if (isLocalMode()) {
						const { line: sl, column: sc } = locConverter.get(pos);
						const { line: el, column: ec } = locConverter.get(propertyNameEnd);
						const dep = new CssLocalIdentifierDependency(
							name,
							[propertyNameStart, propertyNameEnd],
							"--"
						);
						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
					}
					pos = propertyNameEnd;
					return pos + 1;
				} else if (
					name === "@media" ||
					name === "@supports" ||
					name === "@layer" ||
					name === "@container"
				) {
					modeData = isLocalMode() ? "local" : "global";
					isNextRulePrelude = true;
					return end;
				} else if (this.allowModeSwitch) {
					modeData = "global";
					isNextRulePrelude = false;
				}
				return end;
			},
			semicolon: (input, start, end) => {
				switch (scope) {
					case CSS_MODE_IN_AT_IMPORT: {
						const { start } = importData;

						if (importData.url === undefined) {
							this._emitWarning(
								state,
								`Expected URL in '${input.slice(start, end)}'`,
								locConverter,
								start,
								end
							);
							importData = undefined;
							scope = CSS_MODE_TOP_LEVEL;
							return end;
						}
						if (
							importData.urlStart > importData.layerStart ||
							importData.urlStart > importData.supportsStart
						) {
							this._emitWarning(
								state,
								`An URL in '${input.slice(
									start,
									end
								)}' should be before 'layer(...)' or 'supports(...)'`,
								locConverter,
								start,
								end
							);
							importData = undefined;
							scope = CSS_MODE_TOP_LEVEL;
							return end;
						}
						if (importData.layerStart > importData.supportsStart) {
							this._emitWarning(
								state,
								`The 'layer(...)' in '${input.slice(
									start,
									end
								)}' should be before 'supports(...)'`,
								locConverter,
								start,
								end
							);
							importData = undefined;
							scope = CSS_MODE_TOP_LEVEL;
							return end;
						}

						const semicolonPos = end;
						end = walkCssTokens.eatWhiteLine(input, end);
						const { line: sl, column: sc } = locConverter.get(start);
						const { line: el, column: ec } = locConverter.get(end);
						const lastEnd =
							importData.supportsEnd ||
							importData.layerEnd ||
							importData.urlEnd ||
							start;
						const pos = walkCssTokens.eatWhitespaceAndComments(input, lastEnd);
						// Prevent to consider comments as a part of media query
						if (pos !== semicolonPos - 1) {
							importData.media = input.slice(lastEnd, semicolonPos - 1).trim();
						}

						const url = importData.url.trim();

						if (url.length === 0) {
							const dep = new ConstDependency("", [start, end]);
							module.addPresentationalDependency(dep);
							dep.setLoc(sl, sc, el, ec);
						} else {
							const dep = new CssImportDependency(
								url,
								[start, end],
								importData.layer,
								importData.supports,
								importData.media && importData.media.length > 0
									? importData.media
									: undefined
							);
							dep.setLoc(sl, sc, el, ec);
							module.addDependency(dep);
						}

						importData = undefined;
						scope = CSS_MODE_TOP_LEVEL;

						break;
					}
					case CSS_MODE_AT_IMPORT_INVALID:
					case CSS_MODE_AT_NAMESPACE_INVALID: {
						scope = CSS_MODE_TOP_LEVEL;

						break;
					}
					case CSS_MODE_IN_BLOCK: {
						if (this.allowModeSwitch) {
							processDeclarationValueDone(input);
							inAnimationProperty = false;
							isNextRulePrelude = isNextNestedSyntax(input, end);
						}
						break;
					}
				}
				return end;
			},
			leftCurlyBracket: (input, start, end) => {
				switch (scope) {
					case CSS_MODE_TOP_LEVEL: {
						allowImportAtRule = false;
						scope = CSS_MODE_IN_BLOCK;
						blockNestingLevel = 1;

						if (this.allowModeSwitch) {
							isNextRulePrelude = isNextNestedSyntax(input, end);
						}

						break;
					}
					case CSS_MODE_IN_BLOCK: {
						blockNestingLevel++;

						if (this.allowModeSwitch) {
							isNextRulePrelude = isNextNestedSyntax(input, end);
						}
						break;
					}
				}
				return end;
			},
			rightCurlyBracket: (input, start, end) => {
				switch (scope) {
					case CSS_MODE_IN_BLOCK: {
						if (isLocalMode()) {
							processDeclarationValueDone(input);
							inAnimationProperty = false;
						}
						if (--blockNestingLevel === 0) {
							scope = CSS_MODE_TOP_LEVEL;

							if (this.allowModeSwitch) {
								isNextRulePrelude = true;
								modeData = undefined;
							}
						} else if (this.allowModeSwitch) {
							isNextRulePrelude = isNextNestedSyntax(input, end);
						}
						break;
					}
				}
				return end;
			},
			identifier: (input, start, end) => {
				switch (scope) {
					case CSS_MODE_IN_BLOCK: {
						if (isLocalMode()) {
							// Handle only top level values and not inside functions
							if (inAnimationProperty && balanced.length === 0) {
								lastIdentifier = [start, end];
							} else {
								return processLocalDeclaration(input, start, end);
							}
						}
						break;
					}
					case CSS_MODE_IN_AT_IMPORT: {
						if (input.slice(start, end).toLowerCase() === "layer") {
							importData.layer = "";
							importData.layerStart = start;
							importData.layerEnd = end;
						}
						break;
					}
				}
				return end;
			},
			class: (input, start, end) => {
				if (isLocalMode()) {
					const name = input.slice(start + 1, end);
					const dep = new CssLocalIdentifierDependency(name, [start + 1, end]);
					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);
				}

				return end;
			},
			id: (input, start, end) => {
				if (isLocalMode()) {
					const name = input.slice(start + 1, end);
					const dep = new CssLocalIdentifierDependency(name, [start + 1, end]);
					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);
				}
				return end;
			},
			function: (input, start, end) => {
				let name = input.slice(start, end - 1);

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

				if (
					scope === CSS_MODE_IN_AT_IMPORT &&
					name.toLowerCase() === "supports"
				) {
					importData.inSupports = true;
				}

				if (isLocalMode()) {
					name = name.toLowerCase();

					// Don't rename animation name when we have `var()` function
					if (inAnimationProperty && balanced.length === 1) {
						lastIdentifier = undefined;
					}

					if (name === "var") {
						let pos = walkCssTokens.eatWhitespaceAndComments(input, end);
						if (pos === input.length) return pos;
						const [newPos, name] = eatText(input, pos, eatNameInVar);
						if (!name.startsWith("--")) return end;
						const { line: sl, column: sc } = locConverter.get(pos);
						const { line: el, column: ec } = locConverter.get(newPos);
						const dep = new CssSelfLocalIdentifierDependency(
							name.slice(2),
							[pos, newPos],
							"--",
							declaredCssVariables
						);
						dep.setLoc(sl, sc, el, ec);
						module.addDependency(dep);
						return newPos;
					}
				}

				return end;
			},
			leftParenthesis: (input, start, end) => {
				balanced.push(["(", start, end]);

				return end;
			},
			rightParenthesis: (input, start, end) => {
				const last = balanced[balanced.length - 1];
				const popped = balanced.pop();

				if (
					this.allowModeSwitch &&
					popped &&
					(popped[0] === ":local" || popped[0] === ":global")
				) {
					modeData = balanced[balanced.length - 1]
						? /** @type {"local" | "global"} */
							(balanced[balanced.length - 1][0])
						: undefined;
					const dep = new ConstDependency("", [start, end]);
					module.addPresentationalDependency(dep);

					return end;
				}

				switch (scope) {
					case CSS_MODE_IN_AT_IMPORT: {
						if (last && last[0] === "url" && !importData.inSupports) {
							importData.urlStart = last[1];
							importData.urlEnd = end;
						} else if (
							last &&
							last[0].toLowerCase() === "layer" &&
							!importData.inSupports
						) {
							importData.layer = input.slice(last[2], end - 1).trim();
							importData.layerStart = last[1];
							importData.layerEnd = end;
						} else if (last && last[0].toLowerCase() === "supports") {
							importData.supports = input.slice(last[2], end - 1).trim();
							importData.supportsStart = last[1];
							importData.supportsEnd = end;
							importData.inSupports = false;
						}
						break;
					}
				}

				return end;
			},
			pseudoClass: (input, start, end) => {
				if (this.allowModeSwitch) {
					const name = input.slice(start, end).toLowerCase();

					if (name === ":global") {
						modeData = "global";
						// Eat extra whitespace and comments
						end = walkCssTokens.eatWhitespace(input, end);
						const dep = new ConstDependency("", [start, end]);
						module.addPresentationalDependency(dep);
						return end;
					} else if (name === ":local") {
						modeData = "local";
						// Eat extra whitespace and comments
						end = walkCssTokens.eatWhitespace(input, end);
						const dep = new ConstDependency("", [start, end]);
						module.addPresentationalDependency(dep);
						return end;
					}

					switch (scope) {
						case CSS_MODE_TOP_LEVEL: {
							if (name === ":export") {
								const pos = parseExports(input, end);
								const dep = new ConstDependency("", [start, pos]);
								module.addPresentationalDependency(dep);
								return pos;
							}
							break;
						}
					}
				}

				return end;
			},
			pseudoFunction: (input, start, end) => {
				let name = input.slice(start, end - 1);

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

				if (this.allowModeSwitch) {
					name = name.toLowerCase();

					if (name === ":global") {
						modeData = "global";
						const dep = new ConstDependency("", [start, end]);
						module.addPresentationalDependency(dep);
					} else if (name === ":local") {
						modeData = "local";
						const dep = new ConstDependency("", [start, end]);
						module.addPresentationalDependency(dep);
					}
				}

				return end;
			},
			comma: (input, start, end) => {
				if (this.allowModeSwitch) {
					// Reset stack for `:global .class :local .class-other` selector after
					modeData = undefined;

					switch (scope) {
						case CSS_MODE_IN_BLOCK: {
							if (isLocalMode()) {
								processDeclarationValueDone(input);
							}

							break;
						}
					}
				}
				return end;
			}
		});

		if (oldDefaultMode) {
			this.defaultMode = oldDefaultMode;
		}

		module.buildInfo.strict = true;
		module.buildMeta.exportsType = this.namedExports ? "namespace" : "default";

		if (!this.namedExports) {
			module.buildMeta.defaultObject = "redirect";
		}

		module.addDependency(new StaticExportsDependency([], true));
		return state;
	}
}

module.exports = CssParser;


code:
/*
	MIT License http://www.opensource.org/licenses/mit-license.php
	Author Tobias Koppers @sokra
*/

"use strict";

/**
 * @typedef {object} CssTokenCallbacks
 * @property {function(string, number): boolean=} isSelector
 * @property {function(string, number, number, number, number): number=} url
 * @property {function(string, number, number): number=} string
 * @property {function(string, number, number): number=} leftParenthesis
 * @property {function(string, number, number): number=} rightParenthesis
 * @property {function(string, number, number): number=} pseudoFunction
 * @property {function(string, number, number): number=} function
 * @property {function(string, number, number): number=} pseudoClass
 * @property {function(string, number, number): number=} atKeyword
 * @property {function(string, number, number): number=} class
 * @property {function(string, number, number): number=} identifier
 * @property {function(string, number, number): number=} id
 * @property {function(string, number, number): number=} leftCurlyBracket
 * @property {function(string, number, number): number=} rightCurlyBracket
 * @property {function(string, number, number): number=} semicolon
 * @property {function(string, number, number): number=} comma
 */

/** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */

// spec: https://drafts.csswg.org/css-syntax/

const CC_LINE_FEED = "\n".charCodeAt(0);
const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
const CC_FORM_FEED = "\f".charCodeAt(0);

const CC_TAB = "\t".charCodeAt(0);
const CC_SPACE = " ".charCodeAt(0);

const CC_SOLIDUS = "/".charCodeAt(0);
const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0);
const CC_ASTERISK = "*".charCodeAt(0);

const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
const CC_LEFT_CURLY = "{".charCodeAt(0);
const CC_RIGHT_CURLY = "}".charCodeAt(0);
const CC_LEFT_SQUARE = "[".charCodeAt(0);
const CC_RIGHT_SQUARE = "]".charCodeAt(0);

const CC_QUOTATION_MARK = '"'.charCodeAt(0);
const CC_APOSTROPHE = "'".charCodeAt(0);

const CC_FULL_STOP = ".".charCodeAt(0);
const CC_COLON = ":".charCodeAt(0);
const CC_SEMICOLON = ";".charCodeAt(0);
const CC_COMMA = ",".charCodeAt(0);
const CC_PERCENTAGE = "%".charCodeAt(0);
const CC_AT_SIGN = "@".charCodeAt(0);

const CC_LOW_LINE = "_".charCodeAt(0);
const CC_LOWER_A = "a".charCodeAt(0);
const CC_LOWER_U = "u".charCodeAt(0);
const CC_LOWER_E = "e".charCodeAt(0);
const CC_LOWER_Z = "z".charCodeAt(0);
const CC_UPPER_A = "A".charCodeAt(0);
const CC_UPPER_E = "E".charCodeAt(0);
const CC_UPPER_U = "U".charCodeAt(0);
const CC_UPPER_Z = "Z".charCodeAt(0);
const CC_0 = "0".charCodeAt(0);
const CC_9 = "9".charCodeAt(0);

const CC_NUMBER_SIGN = "#".charCodeAt(0);
const CC_PLUS_SIGN = "+".charCodeAt(0);
const CC_HYPHEN_MINUS = "-".charCodeAt(0);

const CC_LESS_THAN_SIGN = "<".charCodeAt(0);
const CC_GREATER_THAN_SIGN = ">".charCodeAt(0);

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a newline
 */
const _isNewLine = cc => {
	return (
		cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED
	);
};

/** @type {CharHandler} */
const consumeSpace = (input, pos, callbacks) => {
	/** @type {number} */
	let cc;
	do {
		pos++;
		cc = input.charCodeAt(pos);
	} while (_isWhiteSpace(cc));
	return pos;
};

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a newline
 */
const _isNewline = cc => {
	return (
		cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED
	);
};

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE)
 */
const _isSpace = cc => {
	return cc === CC_TAB || cc === CC_SPACE;
};

/**
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a whitespace
 */
const _isWhiteSpace = cc => {
	return _isNewline(cc) || _isSpace(cc);
};

/**
 * ident-start code point
 *
 * A letter, a non-ASCII code point, or U+005F LOW LINE (_).
 *
 * @param {number} cc char code
 * @returns {boolean} true, if cc is a start code point of an identifier
 */
const isIdentStartCodePoint = cc => {
	return (
		(cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
		(cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
		cc === CC_LOW_LINE ||
		cc >= 0x80
	);
};

/** @type {CharHandler} */
const consumeDelimToken = (input, pos, callbacks) => {
	return pos + 1;
};

/** @type {CharHandler} */
const consumeComments = (input, pos, callbacks) => {
	// If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A
	// ASTERISK (*), consume them and all following code points up to and including
	// the first U+002A ASTERISK (*) followed by a U+002F SOLIDUS (/), or up to an
	// EOF code point. Return to the start of this step.
	//
	// If the preceding paragraph ended by consuming an EOF code point, this is a parse error.
	// But we are silent on errors.
	if (
		input.charCodeAt(pos) === CC_SOLIDUS &&
		input.charCodeAt(pos + 1) === CC_ASTERISK
	) {
		pos += 1;
		while (pos < input.length) {
			if (
				input.charCodeAt(pos) === CC_ASTERISK &&
				input.charCodeAt(pos + 1) === CC_SOLIDUS
			) {
				pos += 2;
				break;
			}
			pos++;
		}
	}
	return pos;
};

/** @type {function(number): CharHandler} */
const consumeString = quote_cc => (input, pos, callbacks) => {
	const start = pos;
	pos = _consumeString(input, pos, quote_cc);
	if (callbacks.string !== undefined) {
		pos = callbacks.string(input, start, pos);
	}
	return pos;
};

/**
 * @param {string} input input
 * @param {number} pos position
 * @param {number} quote_cc quote char code
 * @returns {number} new position
 */
const _consumeString = (input, pos, quote_cc) => {
	pos++;
	for (;;) {
		if (pos === input.length) return pos;
		const cc = input.charCodeAt(pos);
		if (cc === quote_cc) return pos + 1;
		if (_isNewLine(cc)) {
			// bad string
			return pos;
		}
		if (cc === CC_REVERSE_SOLIDUS) {
			// we don't need to fully parse the escaped code point
			// just skip over a potential new line
			pos++;
			if (pos === input.length) return pos;
			pos++;
		} else {
			pos++;
		}
	}
};

/**
 * @param {number} cc char code
 * @returns {boolean} is identifier start code
 */
const _isIdentifierStartCode = cc => {
	return (
		cc === CC_LOW_LINE ||
		(cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
		(cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
		cc > 0x80
	);
};

/**
 * @param {number} first first code point
 * @param {number} second second code point
 * @returns {boolean} true if two code points are a valid escape
 */
const _isTwoCodePointsAreValidEscape = (first, second) => {
	if (first !== CC_REVERSE_SOLIDUS) return false;
	if (_isNewLine(second)) return false;
	return true;
};

/**
 * @param {number} cc char code
 * @returns {boolean} is digit
 */
const _isDigit = cc => {
	return cc >= CC_0 && cc <= CC_9;
};

/**
 * @param {string} input input
 * @param {number} pos position
 * @returns {boolean} true, if input at pos starts an identifier
 */
const _startsIdentifier = (input, pos) => {
	const cc = input.charCodeAt(pos);
	if (cc === CC_HYPHEN_MINUS) {
		if (pos === input.length) return false;
		const cc = input.charCodeAt(pos + 1);
		if (cc === CC_HYPHEN_MINUS) return true;
		if (cc === CC_REVERSE_SOLIDUS) {
			const cc = input.charCodeAt(pos + 2);
			return !_isNewLine(cc);
		}
		return _isIdentifierStartCode(cc);
	}
	if (cc === CC_REVERSE_SOLIDUS) {
		const cc = input.charCodeAt(pos + 1);
		return !_isNewLine(cc);
	}
	return _isIdentifierStartCode(cc);
};

/** @type {CharHandler} */
const consumeNumberSign = (input, pos, callbacks) => {
	const start = pos;
	pos++;
	if (pos === input.length) return pos;
	if (callbacks.isSelector(input, pos) && _startsIdentifier(input, pos)) {
		pos = _consumeIdentifier(input, pos, callbacks);
		if (callbacks.id !== undefined) {
			return callbacks.id(input, start, pos);
		}
	}
	return pos;
};

/** @type {CharHandler} */
const consumeMinus = (input, pos, callbacks) => {
	const start = pos;
	pos++;
	if (pos === input.length) return pos;
	const cc = input.charCodeAt(pos);
	// If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
	if (cc === CC_FULL_STOP || _isDigit(cc)) {
		return consumeNumericToken(input, pos, callbacks);
	} else if (cc === CC_HYPHEN_MINUS) {
		pos++;
		if (pos === input.length) return pos;
		const cc = input.charCodeAt(pos);
		if (cc === CC_GREATER_THAN_SIGN) {
			return pos + 1;
		} else {
			pos = _consumeIdentifier(input, pos, callbacks);
			if (callbacks.identifier !== undefined) {
				return callbacks.identifier(input, start, pos);
			}
		}
	} else if (cc === CC_REVERSE_SOLIDUS) {
		if (pos + 1 === input.length) return pos;
		const cc = input.charCodeAt(pos + 1);
		if (_isNewLine(cc)) return pos;
		pos = _consumeIdentifier(input, pos, callbacks);
		if (callbacks.identifier !== undefined) {
			return callbacks.identifier(input, start, pos);
		}
	} else if (_isIdentifierStartCode(cc)) {
		pos = consumeOtherIdentifier(input, pos - 1, callbacks);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeDot = (input, pos, callbacks) => {
	const start = pos;
	pos++;
	if (pos === input.length) return pos;
	const cc = input.charCodeAt(pos);
	if (_isDigit(cc)) return consumeNumericToken(input, pos - 2, callbacks);
	if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
		return pos;
	pos = _consumeIdentifier(input, pos, callbacks);
	if (callbacks.class !== undefined) return callbacks.class(input, start, pos);
	return pos;
};

/** @type {CharHandler} */
const consumeNumericToken = (input, pos, callbacks) => {
	pos = _consumeNumber(input, pos, callbacks);
	if (pos === input.length) return pos;
	if (_startsIdentifier(input, pos))
		return _consumeIdentifier(input, pos, callbacks);
	const cc = input.charCodeAt(pos);
	if (cc === CC_PERCENTAGE) return pos + 1;
	return pos;
};

/** @type {CharHandler} */
const consumeOtherIdentifier = (input, pos, callbacks) => {
	const start = pos;
	pos = _consumeIdentifier(input, pos, callbacks);
	if (pos !== input.length && input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) {
		pos++;
		if (callbacks.function !== undefined) {
			return callbacks.function(input, start, pos);
		}
	} else {
		if (callbacks.identifier !== undefined) {
			return callbacks.identifier(input, start, pos);
		}
	}
	return pos;
};

/** @type {CharHandler} */
const consumePotentialUrl = (input, pos, callbacks) => {
	const start = pos;
	pos = _consumeIdentifier(input, pos, callbacks);
	const nextPos = pos + 1;
	if (
		pos === start + 3 &&
		input.slice(start, nextPos).toLowerCase() === "url("
	) {
		pos++;
		let cc = input.charCodeAt(pos);
		while (_isWhiteSpace(cc)) {
			pos++;
			if (pos === input.length) return pos;
			cc = input.charCodeAt(pos);
		}
		if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
			if (callbacks.function !== undefined) {
				return callbacks.function(input, start, nextPos);
			}
			return nextPos;
		} else {
			const contentStart = pos;
			/** @type {number} */
			let contentEnd;
			for (;;) {
				if (cc === CC_REVERSE_SOLIDUS) {
					pos++;
					if (pos === input.length) return pos;
					pos++;
				} else if (_isWhiteSpace(cc)) {
					contentEnd = pos;
					do {
						pos++;
						if (pos === input.length) return pos;
						cc = input.charCodeAt(pos);
					} while (_isWhiteSpace(cc));
					if (cc !== CC_RIGHT_PARENTHESIS) return pos;
					pos++;
					if (callbacks.url !== undefined) {
						return callbacks.url(input, start, pos, contentStart, contentEnd);
					}
					return pos;
				} else if (cc === CC_RIGHT_PARENTHESIS) {
					contentEnd = pos;
					pos++;
					if (callbacks.url !== undefined) {
						return callbacks.url(input, start, pos, contentStart, contentEnd);
					}
					return pos;
				} else if (cc === CC_LEFT_PARENTHESIS) {
					return pos;
				} else {
					pos++;
				}
				if (pos === input.length) return pos;
				cc = input.charCodeAt(pos);
			}
		}
	} else {
		if (callbacks.identifier !== undefined) {
			return callbacks.identifier(input, start, pos);
		}
		return pos;
	}
};

/** @type {CharHandler} */
const consumePotentialPseudo = (input, pos, callbacks) => {
	const start = pos;
	pos++;
	if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
		return pos;
	pos = _consumeIdentifier(input, pos, callbacks);
	let cc = input.charCodeAt(pos);
	if (cc === CC_LEFT_PARENTHESIS) {
		pos++;
		if (callbacks.pseudoFunction !== undefined) {
			return callbacks.pseudoFunction(input, start, pos);
		}
		return pos;
	}
	if (callbacks.pseudoClass !== undefined) {
		return callbacks.pseudoClass(input, start, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeLeftParenthesis = (input, pos, callbacks) => {
	pos++;
	if (callbacks.leftParenthesis !== undefined) {
		return callbacks.leftParenthesis(input, pos - 1, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeRightParenthesis = (input, pos, callbacks) => {
	pos++;
	if (callbacks.rightParenthesis !== undefined) {
		return callbacks.rightParenthesis(input, pos - 1, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeLeftCurlyBracket = (input, pos, callbacks) => {
	pos++;
	if (callbacks.leftCurlyBracket !== undefined) {
		return callbacks.leftCurlyBracket(input, pos - 1, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeRightCurlyBracket = (input, pos, callbacks) => {
	pos++;
	if (callbacks.rightCurlyBracket !== undefined) {
		return callbacks.rightCurlyBracket(input, pos - 1, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeSemicolon = (input, pos, callbacks) => {
	pos++;
	if (callbacks.semicolon !== undefined) {
		return callbacks.semicolon(input, pos - 1, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeComma = (input, pos, callbacks) => {
	pos++;
	if (callbacks.comma !== undefined) {
		return callbacks.comma(input, pos - 1, pos);
	}
	return pos;
};

/** @type {CharHandler} */
const _consumeIdentifier = (input, pos) => {
	for (;;) {
		const cc = input.charCodeAt(pos);
		if (cc === CC_REVERSE_SOLIDUS) {
			pos++;
			if (pos === input.length) return pos;
			pos++;
		} else if (
			_isIdentifierStartCode(cc) ||
			_isDigit(cc) ||
			cc === CC_HYPHEN_MINUS
		) {
			pos++;
		} else {
			return pos;
		}
	}
};

/** @type {CharHandler} */
const _consumeNumber = (input, pos) => {
	pos++;
	if (pos === input.length) return pos;
	let cc = input.charCodeAt(pos);
	while (_isDigit(cc)) {
		pos++;
		if (pos === input.length) return pos;
		cc = input.charCodeAt(pos);
	}
	if (cc === CC_FULL_STOP && pos + 1 !== input.length) {
		const next = input.charCodeAt(pos + 1);
		if (_isDigit(next)) {
			pos += 2;
			cc = input.charCodeAt(pos);
			while (_isDigit(cc)) {
				pos++;
				if (pos === input.length) return pos;
				cc = input.charCodeAt(pos);
			}
		}
	}
	if (cc === CC_LOWER_E || cc === CC_UPPER_E) {
		if (pos + 1 !== input.length) {
			const next = input.charCodeAt(pos + 2);
			if (_isDigit(next)) {
				pos += 2;
			} else if (
				(next === CC_HYPHEN_MINUS || next === CC_PLUS_SIGN) &&
				pos + 2 !== input.length
			) {
				const next = input.charCodeAt(pos + 2);
				if (_isDigit(next)) {
					pos += 3;
				} else {
					return pos;
				}
			} else {
				return pos;
			}
		}
	} else {
		return pos;
	}
	cc = input.charCodeAt(pos);
	while (_isDigit(cc)) {
		pos++;
		if (pos === input.length) return pos;
		cc = input.charCodeAt(pos);
	}
	return pos;
};

/** @type {CharHandler} */
const consumeLessThan = (input, pos, callbacks) => {
	if (input.slice(pos + 1, pos + 4) === "!--") return pos + 4;
	return pos + 1;
};

/** @type {CharHandler} */
const consumeAt = (input, pos, callbacks) => {
	const start = pos;
	pos++;
	if (pos === input.length) return pos;
	if (_startsIdentifier(input, pos)) {
		pos = _consumeIdentifier(input, pos, callbacks);
		if (callbacks.atKeyword !== undefined) {
			pos = callbacks.atKeyword(input, start, pos);
		}
	}
	return pos;
};

/** @type {CharHandler} */
const consumeReverseSolidus = (input, pos, callbacks) => {
	const start = pos;
	pos++;
	if (pos === input.length) return pos;
	// If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it.
	if (
		_isTwoCodePointsAreValidEscape(
			input.charCodeAt(start),
			input.charCodeAt(pos)
		)
	) {
		return consumeOtherIdentifier(input, pos - 1, callbacks);
	}
	// Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point.
	return pos;
};

const CHAR_MAP = Array.from({ length: 0x80 }, (_, cc) => {
	// https://drafts.csswg.org/css-syntax/#consume-token
	switch (cc) {
		// whitespace
		case CC_LINE_FEED:
		case CC_CARRIAGE_RETURN:
		case CC_FORM_FEED:
		case CC_TAB:
		case CC_SPACE:
			return consumeSpace;
		// U+0022 QUOTATION MARK (")
		case CC_QUOTATION_MARK:
			return consumeString(cc);
		// U+0023 NUMBER SIGN (#)
		case CC_NUMBER_SIGN:
			return consumeNumberSign;
		// U+0027 APOSTROPHE (')
		case CC_APOSTROPHE:
			return consumeString(cc);
		// U+0028 LEFT PARENTHESIS (()
		case CC_LEFT_PARENTHESIS:
			return consumeLeftParenthesis;
		// U+0029 RIGHT PARENTHESIS ())
		case CC_RIGHT_PARENTHESIS:
			return consumeRightParenthesis;
		// U+002B PLUS SIGN (+)
		case CC_PLUS_SIGN:
			return consumeNumericToken;
		// U+002C COMMA (,)
		case CC_COMMA:
			return consumeComma;
		// U+002D HYPHEN-MINUS (-)
		case CC_HYPHEN_MINUS:
			return consumeMinus;
		// U+002E FULL STOP (.)
		case CC_FULL_STOP:
			return consumeDot;
		// U+003A COLON (:)
		case CC_COLON:
			return consumePotentialPseudo;
		// U+003B SEMICOLON (;)
		case CC_SEMICOLON:
			return consumeSemicolon;
		// U+003C LESS-THAN SIGN (<)
		case CC_LESS_THAN_SIGN:
			return consumeLessThan;
		// U+0040 COMMERCIAL AT (@)
		case CC_AT_SIGN:
			return consumeAt;
		// U+005B LEFT SQUARE BRACKET ([)
		case CC_LEFT_SQUARE:
			return consumeDelimToken;
		// U+005C REVERSE SOLIDUS (\)
		case CC_REVERSE_SOLIDUS:
			return consumeReverseSolidus;
		// U+005D RIGHT SQUARE BRACKET (])
		case CC_RIGHT_SQUARE:
			return consumeDelimToken;
		// U+007B LEFT CURLY BRACKET ({)
		case CC_LEFT_CURLY:
			return consumeLeftCurlyBracket;
		// U+007D RIGHT CURLY BRACKET (})
		case CC_RIGHT_CURLY:
			return consumeRightCurlyBracket;
		// Optimization
		case CC_LOWER_U:
		case CC_UPPER_U:
			return consumePotentialUrl;
		default:
			// digit
			if (_isDigit(cc)) return consumeNumericToken;
			// ident-start code point
			if (isIdentStartCodePoint(cc)) {
				return consumeOtherIdentifier;
			}
			// EOF, but we don't have it
			// anything else
			return consumeDelimToken;
	}
});

/**
 * @param {string} input input css
 * @param {CssTokenCallbacks} callbacks callbacks
 * @returns {void}
 */
module.exports = (input, callbacks) => {
	// This section describes how to consume a token from a stream of code points. It will return a single token of any type.
	let pos = 0;
	while (pos < input.length) {
		// Consume comments.
		pos = consumeComments(input, pos, callbacks);

		const cc = input.charCodeAt(pos);

		// Consume the next input code point.
		if (cc < 0x80) {
			pos = CHAR_MAP[cc](input, pos, callbacks);
		} else {
			pos++;
		}
	}
};

module.exports.isIdentStartCodePoint = isIdentStartCodePoint;

/**
 * @param {string} input input
 * @param {number} pos position
 * @returns {number} position after comments
 */
module.exports.eatComments = (input, pos) => {
	for (;;) {
		let originalPos = pos;
		pos = consumeComments(input, pos, {});
		if (originalPos === pos) {
			break;
		}
	}

	return pos;
};

/**
 * @param {string} input input
 * @param {number} pos position
 * @returns {number} position after whitespace
 */
module.exports.eatWhitespace = (input, pos) => {
	while (_isWhiteSpace(input.charCodeAt(pos))) {
		pos++;
	}

	return pos;
};

/**
 * @param {string} input input
 * @param {number} pos position
 * @returns {number} position after whitespace and comments
 */
module.exports.eatWhitespaceAndComments = (input, pos) => {
	for (;;) {
		let originalPos = pos;
		pos = consumeComments(input, pos, {});
		while (_isWhiteSpace(input.charCodeAt(pos))) {
			pos++;
		}
		if (originalPos === pos) {
			break;
		}
	}

	return pos;
};

/**
 * @param {string} input input
 * @param {number} pos position
 * @returns {number} position after whitespace
 */
module.exports.eatWhiteLine = (input, pos) => {
	for (;;) {
		const cc = input.charCodeAt(pos);
		if (_isSpace(cc)) {
			pos++;
			continue;
		}
		if (_isNewLine(cc)) pos++;
		// For `\r\n`
		if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos + 1) === CC_LINE_FEED)
			pos++;
		break;
	}

	return pos;
};