CssParser.js 69 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const vm = require("vm");
  7. const CommentCompilationWarning = require("../CommentCompilationWarning");
  8. const CssModule = require("../CssModule");
  9. const ModuleDependencyWarning = require("../ModuleDependencyWarning");
  10. const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
  11. const Parser = require("../Parser");
  12. const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
  13. const WebpackError = require("../WebpackError");
  14. const ConstDependency = require("../dependencies/ConstDependency");
  15. const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
  16. const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
  17. const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
  18. const CssImportDependency = require("../dependencies/CssImportDependency");
  19. const CssUrlDependency = require("../dependencies/CssUrlDependency");
  20. const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
  21. const binarySearchBounds = require("../util/binarySearchBounds");
  22. const { parseResource } = require("../util/identifier");
  23. const {
  24. createMagicCommentContext,
  25. webpackCommentRegExp
  26. } = require("../util/magicComment");
  27. const walkCssTokens = require("./walkCssTokens");
  28. /** @typedef {import("../Module").BuildInfo} BuildInfo */
  29. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  30. /** @typedef {import("../Parser").ParserState} ParserState */
  31. /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
  32. /** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */
  33. /** @typedef {import("../../declarations/WebpackOptions").CssModuleParserOptions} CssModuleParserOptions */
  34. /** @typedef {[number, number]} Range */
  35. /** @typedef {{ line: number, column: number }} Position */
  36. /** @typedef {{ value: string, range: Range, loc: { start: Position, end: Position } }} Comment */
  37. const CC_COLON = ":".charCodeAt(0);
  38. const CC_SEMICOLON = ";".charCodeAt(0);
  39. const CC_COMMA = ",".charCodeAt(0);
  40. const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
  41. const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
  42. const CC_LOWER_F = "f".charCodeAt(0);
  43. const CC_UPPER_F = "F".charCodeAt(0);
  44. const CC_RIGHT_CURLY = "}".charCodeAt(0);
  45. const CC_HYPHEN_MINUS = "-".charCodeAt(0);
  46. const CC_TILDE = "~".charCodeAt(0);
  47. const CC_EQUAL = "=".charCodeAt(0);
  48. // https://www.w3.org/TR/css-syntax-3/#newline
  49. // We don't have `preprocessing` stage, so we need specify all of them
  50. const STRING_MULTILINE = /\\[\n\r\f]/g;
  51. // https://www.w3.org/TR/css-syntax-3/#whitespace
  52. const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
  53. const UNESCAPE = /\\([0-9a-f]{1,6}[ \t\n\r\f]?|[\s\S])/gi;
  54. const IMAGE_SET_FUNCTION = /^(?:-\w+-)?image-set$/i;
  55. const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(?:-\w+-)?keyframes$/;
  56. const COMPOSES_PROPERTY = /^(?:composes|compose-with)$/i;
  57. const IS_MODULES = /\.modules?\.[^.]+$/i;
  58. const CSS_COMMENT = /\/\*((?!\*\/).*?)\*\//g;
  59. /**
  60. * @param {RegExp} regexp a regexp
  61. * @param {string} str a string
  62. * @returns {RegExpExecArray[]} matches
  63. */
  64. const matchAll = (regexp, str) => {
  65. /** @type {RegExpExecArray[]} */
  66. const result = [];
  67. /** @type {null | RegExpExecArray} */
  68. let match;
  69. // Use a while loop with exec() to find all matches
  70. while ((match = regexp.exec(str)) !== null) {
  71. result.push(match);
  72. }
  73. // Return an array to be easily iterable (note: a true spec-compliant polyfill
  74. // returns an iterator object, but an array spread often suffices for basic use)
  75. return result;
  76. };
  77. /**
  78. * @param {string} str url string
  79. * @param {boolean} isString is url wrapped in quotes
  80. * @returns {string} normalized url
  81. */
  82. const normalizeUrl = (str, isString) => {
  83. // Remove extra spaces and newlines:
  84. // `url("im\
  85. // g.png")`
  86. if (isString) {
  87. str = str.replace(STRING_MULTILINE, "");
  88. }
  89. str = str
  90. // Remove unnecessary spaces from `url(" img.png ")`
  91. .replace(TRIM_WHITE_SPACES, "")
  92. // Unescape
  93. .replace(UNESCAPE, (match) => {
  94. if (match.length > 2) {
  95. return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
  96. }
  97. return match[1];
  98. });
  99. if (/^data:/i.test(str)) {
  100. return str;
  101. }
  102. if (str.includes("%")) {
  103. // Convert `url('%2E/img.png')` -> `url('./img.png')`
  104. try {
  105. str = decodeURIComponent(str);
  106. } catch (_err) {
  107. // Ignore
  108. }
  109. }
  110. return str;
  111. };
  112. const regexSingleEscape = /[ -,./:-@[\]^`{-~]/;
  113. const regexExcessiveSpaces =
  114. /(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g;
  115. /**
  116. * @param {string} str string
  117. * @returns {string} escaped identifier
  118. */
  119. const escapeIdentifier = (str) => {
  120. let output = "";
  121. let counter = 0;
  122. while (counter < str.length) {
  123. const character = str.charAt(counter++);
  124. /** @type {string} */
  125. let value;
  126. if (/[\t\n\f\r\v]/.test(character)) {
  127. const codePoint = character.charCodeAt(0);
  128. value = `\\${codePoint.toString(16).toUpperCase()} `;
  129. } else if (character === "\\" || regexSingleEscape.test(character)) {
  130. value = `\\${character}`;
  131. } else {
  132. value = character;
  133. }
  134. output += value;
  135. }
  136. const firstChar = str.charAt(0);
  137. if (/^-[-\d]/.test(output)) {
  138. output = `\\-${output.slice(1)}`;
  139. } else if (/\d/.test(firstChar)) {
  140. output = `\\3${firstChar} ${output.slice(1)}`;
  141. }
  142. // Remove spaces after `\HEX` escapes that are not followed by a hex digit,
  143. // since they’re redundant. Note that this is only possible if the escape
  144. // sequence isn’t preceded by an odd number of backslashes.
  145. output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => {
  146. if ($1 && $1.length % 2) {
  147. // It’s not safe to remove the space, so don’t.
  148. return $0;
  149. }
  150. // Strip the space.
  151. return ($1 || "") + $2;
  152. });
  153. return output;
  154. };
  155. const CONTAINS_ESCAPE = /\\/;
  156. /**
  157. * @param {string} str string
  158. * @returns {[string, number] | undefined} hex
  159. */
  160. const gobbleHex = (str) => {
  161. const lower = str.toLowerCase();
  162. let hex = "";
  163. let spaceTerminated = false;
  164. for (let i = 0; i < 6 && lower[i] !== undefined; i++) {
  165. const code = lower.charCodeAt(i);
  166. // check to see if we are dealing with a valid hex char [a-f|0-9]
  167. const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57);
  168. // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
  169. spaceTerminated = code === 32;
  170. if (!valid) break;
  171. hex += lower[i];
  172. }
  173. if (hex.length === 0) return undefined;
  174. const codePoint = Number.parseInt(hex, 16);
  175. const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
  176. // Add special case for
  177. // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
  178. // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
  179. if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
  180. return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)];
  181. }
  182. return [
  183. String.fromCodePoint(codePoint),
  184. hex.length + (spaceTerminated ? 1 : 0)
  185. ];
  186. };
  187. /**
  188. * @param {string} str string
  189. * @returns {string} unescaped string
  190. */
  191. const unescapeIdentifier = (str) => {
  192. const needToProcess = CONTAINS_ESCAPE.test(str);
  193. if (!needToProcess) return str;
  194. let ret = "";
  195. for (let i = 0; i < str.length; i++) {
  196. if (str[i] === "\\") {
  197. const gobbled = gobbleHex(str.slice(i + 1, i + 7));
  198. if (gobbled !== undefined) {
  199. ret += gobbled[0];
  200. i += gobbled[1];
  201. continue;
  202. }
  203. // Retain a pair of \\ if double escaped `\\\\`
  204. // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
  205. if (str[i + 1] === "\\") {
  206. ret += "\\";
  207. i += 1;
  208. continue;
  209. }
  210. // if \\ is at the end of the string retain it
  211. // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
  212. if (str.length === i + 1) {
  213. ret += str[i];
  214. }
  215. continue;
  216. }
  217. ret += str[i];
  218. }
  219. return ret;
  220. };
  221. /**
  222. * A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
  223. * The <custom-property-name> production corresponds to this:
  224. * it’s defined as any <dashed-ident> (a valid identifier that starts with two dashes),
  225. * except -- itself, which is reserved for future use by CSS.
  226. * @param {string} identifier identifier
  227. * @returns {boolean} true when identifier is dashed, otherwise false
  228. */
  229. const isDashedIdentifier = (identifier) =>
  230. identifier.startsWith("--") && identifier.length >= 3;
  231. /** @type {Record<string, number>} */
  232. const PREDEFINED_COUNTER_STYLES = {
  233. decimal: 1,
  234. "decimal-leading-zero": 1,
  235. "arabic-indic": 1,
  236. armenian: 1,
  237. "upper-armenian": 1,
  238. "lower-armenian": 1,
  239. bengali: 1,
  240. cambodian: 1,
  241. khmer: 1,
  242. "cjk-decimal": 1,
  243. devanagari: 1,
  244. georgian: 1,
  245. gujarati: 1,
  246. /* cspell:disable-next-line */
  247. gurmukhi: 1,
  248. hebrew: 1,
  249. kannada: 1,
  250. lao: 1,
  251. malayalam: 1,
  252. mongolian: 1,
  253. myanmar: 1,
  254. oriya: 1,
  255. persian: 1,
  256. "lower-roman": 1,
  257. "upper-roman": 1,
  258. tamil: 1,
  259. telugu: 1,
  260. thai: 1,
  261. tibetan: 1,
  262. "lower-alpha": 1,
  263. "lower-latin": 1,
  264. "upper-alpha": 1,
  265. "upper-latin": 1,
  266. "lower-greek": 1,
  267. hiragana: 1,
  268. /* cspell:disable-next-line */
  269. "hiragana-iroha": 1,
  270. katakana: 1,
  271. /* cspell:disable-next-line */
  272. "katakana-iroha": 1,
  273. disc: 1,
  274. circle: 1,
  275. square: 1,
  276. "disclosure-open": 1,
  277. "disclosure-closed": 1,
  278. "cjk-earthly-branch": 1,
  279. "cjk-heavenly-stem": 1,
  280. "japanese-informal": 1,
  281. "japanese-formal": 1,
  282. "korean-hangul-formal": 1,
  283. /* cspell:disable-next-line */
  284. "korean-hanja-informal": 1,
  285. /* cspell:disable-next-line */
  286. "korean-hanja-formal": 1,
  287. "simp-chinese-informal": 1,
  288. "simp-chinese-formal": 1,
  289. "trad-chinese-informal": 1,
  290. "trad-chinese-formal": 1,
  291. "cjk-ideographic": 1,
  292. "ethiopic-numeric": 1
  293. };
  294. /** @type {Record<string, number>} */
  295. const GLOBAL_VALUES = {
  296. // Global values
  297. initial: Infinity,
  298. inherit: Infinity,
  299. unset: Infinity,
  300. revert: Infinity,
  301. "revert-layer": Infinity
  302. };
  303. /** @type {Record<string, number>} */
  304. const GRID_AREA_OR_COLUMN_OR_ROW = {
  305. auto: Infinity,
  306. span: Infinity,
  307. ...GLOBAL_VALUES
  308. };
  309. /** @type {Record<string, number>} */
  310. const GRID_AUTO_COLUMNS_OR_ROW = {
  311. "min-content": Infinity,
  312. "max-content": Infinity,
  313. auto: Infinity,
  314. ...GLOBAL_VALUES
  315. };
  316. /** @type {Record<string, number>} */
  317. const GRID_AUTO_FLOW = {
  318. row: 1,
  319. column: 1,
  320. dense: 1,
  321. ...GLOBAL_VALUES
  322. };
  323. /** @type {Record<string, number>} */
  324. const GRID_TEMPLATE_ARES = {
  325. // Special
  326. none: 1,
  327. ...GLOBAL_VALUES
  328. };
  329. /** @type {Record<string, number>} */
  330. const GRID_TEMPLATE_COLUMNS_OR_ROWS = {
  331. none: 1,
  332. subgrid: 1,
  333. masonry: 1,
  334. "max-content": Infinity,
  335. "min-content": Infinity,
  336. auto: Infinity,
  337. ...GLOBAL_VALUES
  338. };
  339. /** @type {Record<string, number>} */
  340. const GRID_TEMPLATE = {
  341. ...GRID_TEMPLATE_ARES,
  342. ...GRID_TEMPLATE_COLUMNS_OR_ROWS
  343. };
  344. /** @type {Record<string, number>} */
  345. const GRID = {
  346. "auto-flow": 1,
  347. dense: 1,
  348. ...GRID_AUTO_COLUMNS_OR_ROW,
  349. ...GRID_AUTO_FLOW,
  350. ...GRID_TEMPLATE_ARES,
  351. ...GRID_TEMPLATE_COLUMNS_OR_ROWS
  352. };
  353. /**
  354. * @param {{ animation?: boolean, container?: boolean, customIdents?: boolean, grid?: boolean }=} options options
  355. * @returns {Map<string, Record<string, number>>} list of known properties
  356. */
  357. const getKnownProperties = (options = {}) => {
  358. /** @type {Map<string, Record<string, number>>} */
  359. const knownProperties = new Map();
  360. if (options.animation) {
  361. knownProperties.set("animation", {
  362. // animation-direction
  363. normal: 1,
  364. reverse: 1,
  365. alternate: 1,
  366. "alternate-reverse": 1,
  367. // animation-fill-mode
  368. forwards: 1,
  369. backwards: 1,
  370. both: 1,
  371. // animation-iteration-count
  372. infinite: 1,
  373. // animation-play-state
  374. paused: 1,
  375. running: 1,
  376. // animation-timing-function
  377. ease: 1,
  378. "ease-in": 1,
  379. "ease-out": 1,
  380. "ease-in-out": 1,
  381. linear: 1,
  382. "step-end": 1,
  383. "step-start": 1,
  384. // Special
  385. none: Infinity, // No matter how many times you write none, it will never be an animation name
  386. ...GLOBAL_VALUES
  387. });
  388. knownProperties.set("animation-name", {
  389. // Special
  390. none: Infinity, // No matter how many times you write none, it will never be an animation name
  391. ...GLOBAL_VALUES
  392. });
  393. }
  394. if (options.container) {
  395. knownProperties.set("container", {
  396. // container-type
  397. normal: 1,
  398. size: 1,
  399. "inline-size": 1,
  400. "scroll-state": 1,
  401. // Special
  402. none: Infinity,
  403. ...GLOBAL_VALUES
  404. });
  405. knownProperties.set("container-name", {
  406. // Special
  407. none: Infinity,
  408. ...GLOBAL_VALUES
  409. });
  410. }
  411. if (options.customIdents) {
  412. knownProperties.set("list-style", {
  413. // list-style-position
  414. inside: 1,
  415. outside: 1,
  416. // list-style-type
  417. ...PREDEFINED_COUNTER_STYLES,
  418. // Special
  419. none: Infinity,
  420. ...GLOBAL_VALUES
  421. });
  422. knownProperties.set("list-style-type", {
  423. // list-style-type
  424. ...PREDEFINED_COUNTER_STYLES,
  425. // Special
  426. none: Infinity,
  427. ...GLOBAL_VALUES
  428. });
  429. knownProperties.set("system", {
  430. cyclic: 1,
  431. numeric: 1,
  432. alphabetic: 1,
  433. symbolic: 1,
  434. additive: 1,
  435. fixed: 1,
  436. extends: 1,
  437. ...PREDEFINED_COUNTER_STYLES
  438. });
  439. knownProperties.set("fallback", {
  440. ...PREDEFINED_COUNTER_STYLES
  441. });
  442. knownProperties.set("speak-as", {
  443. auto: 1,
  444. bullets: 1,
  445. numbers: 1,
  446. words: 1,
  447. "spell-out": 1,
  448. ...PREDEFINED_COUNTER_STYLES
  449. });
  450. }
  451. if (options.grid) {
  452. knownProperties.set("grid", GRID);
  453. knownProperties.set("grid-area", GRID_AREA_OR_COLUMN_OR_ROW);
  454. knownProperties.set("grid-column", GRID_AREA_OR_COLUMN_OR_ROW);
  455. knownProperties.set("grid-column-end", GRID_AREA_OR_COLUMN_OR_ROW);
  456. knownProperties.set("grid-column-start", GRID_AREA_OR_COLUMN_OR_ROW);
  457. knownProperties.set("grid-column-start", GRID_AREA_OR_COLUMN_OR_ROW);
  458. knownProperties.set("grid-row", GRID_AREA_OR_COLUMN_OR_ROW);
  459. knownProperties.set("grid-row-end", GRID_AREA_OR_COLUMN_OR_ROW);
  460. knownProperties.set("grid-row-start", GRID_AREA_OR_COLUMN_OR_ROW);
  461. knownProperties.set("grid-template", GRID_TEMPLATE);
  462. knownProperties.set("grid-template-areas", GRID_TEMPLATE_ARES);
  463. knownProperties.set("grid-template-columns", GRID_TEMPLATE_COLUMNS_OR_ROWS);
  464. knownProperties.set("grid-template-rows", GRID_TEMPLATE_COLUMNS_OR_ROWS);
  465. }
  466. return knownProperties;
  467. };
  468. class LocConverter {
  469. /**
  470. * @param {string} input input
  471. */
  472. constructor(input) {
  473. this._input = input;
  474. this.line = 1;
  475. this.column = 0;
  476. this.pos = 0;
  477. }
  478. /**
  479. * @param {number} pos position
  480. * @returns {LocConverter} location converter
  481. */
  482. get(pos) {
  483. if (this.pos !== pos) {
  484. if (this.pos < pos) {
  485. const str = this._input.slice(this.pos, pos);
  486. let i = str.lastIndexOf("\n");
  487. if (i === -1) {
  488. this.column += str.length;
  489. } else {
  490. this.column = str.length - i - 1;
  491. this.line++;
  492. while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) {
  493. this.line++;
  494. }
  495. }
  496. } else {
  497. let i = this._input.lastIndexOf("\n", this.pos);
  498. while (i >= pos) {
  499. this.line--;
  500. i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
  501. }
  502. this.column = pos - i;
  503. }
  504. this.pos = pos;
  505. }
  506. return this;
  507. }
  508. }
  509. const EMPTY_COMMENT_OPTIONS = {
  510. options: null,
  511. errors: null
  512. };
  513. const CSS_MODE_TOP_LEVEL = 0;
  514. const CSS_MODE_IN_BLOCK = 1;
  515. const LOCAL_MODE = 0;
  516. const GLOBAL_MODE = 1;
  517. const eatUntilSemi = walkCssTokens.eatUntil(";");
  518. const eatUntilLeftCurly = walkCssTokens.eatUntil("{");
  519. /**
  520. * @typedef {object} CssParserOwnOptions
  521. * @property {("pure" | "global" | "local" | "auto")=} defaultMode default mode
  522. */
  523. /** @typedef {CssModuleParserOptions & CssParserOwnOptions} CssParserOptions */
  524. class CssParser extends Parser {
  525. /**
  526. * @param {CssParserOptions=} options options
  527. */
  528. constructor(options = {}) {
  529. super();
  530. this.defaultMode =
  531. typeof options.defaultMode !== "undefined" ? options.defaultMode : "pure";
  532. this.options = {
  533. url: true,
  534. import: true,
  535. namedExports: true,
  536. animation: true,
  537. container: true,
  538. customIdents: true,
  539. dashedIdents: true,
  540. function: true,
  541. grid: true,
  542. ...options
  543. };
  544. /** @type {Comment[] | undefined} */
  545. this.comments = undefined;
  546. this.magicCommentContext = createMagicCommentContext();
  547. }
  548. /**
  549. * @param {ParserState} state parser state
  550. * @param {string} message warning message
  551. * @param {LocConverter} locConverter location converter
  552. * @param {number} start start offset
  553. * @param {number} end end offset
  554. */
  555. _emitWarning(state, message, locConverter, start, end) {
  556. const { line: sl, column: sc } = locConverter.get(start);
  557. const { line: el, column: ec } = locConverter.get(end);
  558. state.current.addWarning(
  559. new ModuleDependencyWarning(state.module, new WebpackError(message), {
  560. start: { line: sl, column: sc },
  561. end: { line: el, column: ec }
  562. })
  563. );
  564. }
  565. /**
  566. * @param {string | Buffer | PreparsedAst} source the source to parse
  567. * @param {ParserState} state the parser state
  568. * @returns {ParserState} the parser state
  569. */
  570. parse(source, state) {
  571. if (Buffer.isBuffer(source)) {
  572. source = source.toString("utf8");
  573. } else if (typeof source === "object") {
  574. throw new Error("webpackAst is unexpected for the CssParser");
  575. }
  576. if (source[0] === "\uFEFF") {
  577. source = source.slice(1);
  578. }
  579. let mode = this.defaultMode;
  580. const module = state.module;
  581. if (
  582. mode === "auto" &&
  583. module.type === CSS_MODULE_TYPE_AUTO &&
  584. IS_MODULES.test(
  585. // TODO matchResource
  586. parseResource(/** @type {string} */ (module.getResource())).path
  587. )
  588. ) {
  589. mode = "local";
  590. }
  591. const isModules = mode === "global" || mode === "local";
  592. const knownProperties = getKnownProperties({
  593. animation: this.options.animation,
  594. container: this.options.container,
  595. customIdents: this.options.customIdents,
  596. grid: this.options.grid
  597. });
  598. /** @type {BuildMeta} */
  599. (module.buildMeta).isCSSModule = isModules;
  600. const locConverter = new LocConverter(source);
  601. /** @type {number} */
  602. let scope = CSS_MODE_TOP_LEVEL;
  603. /** @type {boolean} */
  604. let allowImportAtRule = true;
  605. /** @type {[string, number, number, boolean?][]} */
  606. const balanced = [];
  607. let lastTokenEndForComments = 0;
  608. /** @type {boolean} */
  609. let isNextRulePrelude = isModules;
  610. /** @type {number} */
  611. let blockNestingLevel = 0;
  612. /** @type {0 | 1 | undefined} */
  613. let modeData;
  614. /** @type {string[]} */
  615. let lastLocalIdentifiers = [];
  616. /** @typedef {{ value: string, isReference?: boolean }} IcssDefinition */
  617. /** @type {Map<string, IcssDefinition>} */
  618. const icssDefinitions = new Map();
  619. /**
  620. * @param {string} input input
  621. * @param {number} pos position
  622. * @returns {boolean} true, when next is nested syntax
  623. */
  624. const isNextNestedSyntax = (input, pos) => {
  625. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  626. if (
  627. input.charCodeAt(pos) === CC_RIGHT_CURLY ||
  628. (input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
  629. input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS)
  630. ) {
  631. return false;
  632. }
  633. const identifier = walkCssTokens.eatIdentSequence(input, pos);
  634. if (!identifier) {
  635. return true;
  636. }
  637. const leftCurly = eatUntilLeftCurly(input, pos);
  638. const content = input.slice(identifier[0], leftCurly);
  639. if (content.includes(";") || content.includes("}")) {
  640. return false;
  641. }
  642. return true;
  643. };
  644. /**
  645. * @returns {boolean} true, when in local scope
  646. */
  647. const isLocalMode = () =>
  648. modeData === LOCAL_MODE || (mode === "local" && modeData === undefined);
  649. /**
  650. * @param {string} input input
  651. * @param {number} start start
  652. * @param {number} end end
  653. * @returns {number} end
  654. */
  655. const comment = (input, start, end) => {
  656. if (!this.comments) this.comments = [];
  657. const { line: sl, column: sc } = locConverter.get(start);
  658. const { line: el, column: ec } = locConverter.get(end);
  659. /** @type {Comment} */
  660. const comment = {
  661. value: input.slice(start + 2, end - 2),
  662. range: [start, end],
  663. loc: {
  664. start: { line: sl, column: sc },
  665. end: { line: el, column: ec }
  666. }
  667. };
  668. this.comments.push(comment);
  669. return end;
  670. };
  671. // Vanilla CSS stuff
  672. /**
  673. * @param {string} input input
  674. * @param {number} start name start position
  675. * @param {number} end name end position
  676. * @returns {number} position after handling
  677. */
  678. const processAtImport = (input, start, end) => {
  679. const tokens = walkCssTokens.eatImportTokens(input, end, {
  680. comment
  681. });
  682. if (!tokens[3]) return end;
  683. const semi = tokens[3][1];
  684. if (!tokens[0]) {
  685. this._emitWarning(
  686. state,
  687. `Expected URL in '${input.slice(start, semi)}'`,
  688. locConverter,
  689. start,
  690. semi
  691. );
  692. return end;
  693. }
  694. const urlToken = tokens[0];
  695. const url = normalizeUrl(input.slice(urlToken[2], urlToken[3]), true);
  696. const newline = walkCssTokens.eatWhiteLine(input, semi);
  697. const { options, errors: commentErrors } = this.parseCommentOptions([
  698. end,
  699. urlToken[1]
  700. ]);
  701. if (commentErrors) {
  702. for (const e of commentErrors) {
  703. const { comment } = e;
  704. state.module.addWarning(
  705. new CommentCompilationWarning(
  706. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  707. comment.loc
  708. )
  709. );
  710. }
  711. }
  712. if (options && options.webpackIgnore !== undefined) {
  713. if (typeof options.webpackIgnore !== "boolean") {
  714. const { line: sl, column: sc } = locConverter.get(start);
  715. const { line: el, column: ec } = locConverter.get(newline);
  716. state.module.addWarning(
  717. new UnsupportedFeatureWarning(
  718. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  719. {
  720. start: { line: sl, column: sc },
  721. end: { line: el, column: ec }
  722. }
  723. )
  724. );
  725. } else if (options.webpackIgnore) {
  726. return newline;
  727. }
  728. }
  729. if (url.length === 0) {
  730. const { line: sl, column: sc } = locConverter.get(start);
  731. const { line: el, column: ec } = locConverter.get(newline);
  732. const dep = new ConstDependency("", [start, newline]);
  733. module.addPresentationalDependency(dep);
  734. dep.setLoc(sl, sc, el, ec);
  735. return newline;
  736. }
  737. /** @type {undefined | string} */
  738. let layer;
  739. if (tokens[1]) {
  740. layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim();
  741. }
  742. /** @type {undefined | string} */
  743. let supports;
  744. if (tokens[2]) {
  745. supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim();
  746. }
  747. const last = tokens[2] || tokens[1] || tokens[0];
  748. const mediaStart = walkCssTokens.eatWhitespaceAndComments(
  749. input,
  750. last[1]
  751. )[0];
  752. /** @type {undefined | string} */
  753. let media;
  754. if (mediaStart !== semi - 1) {
  755. media = input.slice(mediaStart, semi - 1).trim();
  756. }
  757. const { line: sl, column: sc } = locConverter.get(start);
  758. const { line: el, column: ec } = locConverter.get(newline);
  759. const dep = new CssImportDependency(
  760. url,
  761. [start, newline],
  762. mode === "local" || mode === "global" ? mode : undefined,
  763. layer,
  764. supports && supports.length > 0 ? supports : undefined,
  765. media && media.length > 0 ? media : undefined
  766. );
  767. dep.setLoc(sl, sc, el, ec);
  768. module.addDependency(dep);
  769. return newline;
  770. };
  771. /**
  772. * @param {string} input input
  773. * @param {number} end end position
  774. * @param {string} name the name of function
  775. * @returns {number} position after handling
  776. */
  777. const processURLFunction = (input, end, name) => {
  778. const string = walkCssTokens.eatString(input, end);
  779. if (!string) return end;
  780. const { options, errors: commentErrors } = this.parseCommentOptions([
  781. lastTokenEndForComments,
  782. end
  783. ]);
  784. if (commentErrors) {
  785. for (const e of commentErrors) {
  786. const { comment } = e;
  787. state.module.addWarning(
  788. new CommentCompilationWarning(
  789. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  790. comment.loc
  791. )
  792. );
  793. }
  794. }
  795. if (options && options.webpackIgnore !== undefined) {
  796. if (typeof options.webpackIgnore !== "boolean") {
  797. const { line: sl, column: sc } = locConverter.get(string[0]);
  798. const { line: el, column: ec } = locConverter.get(string[1]);
  799. state.module.addWarning(
  800. new UnsupportedFeatureWarning(
  801. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  802. {
  803. start: { line: sl, column: sc },
  804. end: { line: el, column: ec }
  805. }
  806. )
  807. );
  808. } else if (options.webpackIgnore) {
  809. return end;
  810. }
  811. }
  812. const value = normalizeUrl(
  813. input.slice(string[0] + 1, string[1] - 1),
  814. true
  815. );
  816. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  817. if (value.length === 0) return end;
  818. const isUrl = name === "url" || name === "src";
  819. const dep = new CssUrlDependency(
  820. value,
  821. [string[0], string[1]],
  822. isUrl ? "string" : "url"
  823. );
  824. const { line: sl, column: sc } = locConverter.get(string[0]);
  825. const { line: el, column: ec } = locConverter.get(string[1]);
  826. dep.setLoc(sl, sc, el, ec);
  827. module.addDependency(dep);
  828. module.addCodeGenerationDependency(dep);
  829. return string[1];
  830. };
  831. /**
  832. * @param {string} input input
  833. * @param {number} start start position
  834. * @param {number} end end position
  835. * @param {number} contentStart start position
  836. * @param {number} contentEnd end position
  837. * @returns {number} position after handling
  838. */
  839. const processOldURLFunction = (
  840. input,
  841. start,
  842. end,
  843. contentStart,
  844. contentEnd
  845. ) => {
  846. const { options, errors: commentErrors } = this.parseCommentOptions([
  847. lastTokenEndForComments,
  848. end
  849. ]);
  850. if (commentErrors) {
  851. for (const e of commentErrors) {
  852. const { comment } = e;
  853. state.module.addWarning(
  854. new CommentCompilationWarning(
  855. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  856. comment.loc
  857. )
  858. );
  859. }
  860. }
  861. if (options && options.webpackIgnore !== undefined) {
  862. if (typeof options.webpackIgnore !== "boolean") {
  863. const { line: sl, column: sc } = locConverter.get(
  864. lastTokenEndForComments
  865. );
  866. const { line: el, column: ec } = locConverter.get(end);
  867. state.module.addWarning(
  868. new UnsupportedFeatureWarning(
  869. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  870. {
  871. start: { line: sl, column: sc },
  872. end: { line: el, column: ec }
  873. }
  874. )
  875. );
  876. } else if (options.webpackIgnore) {
  877. return end;
  878. }
  879. }
  880. const value = normalizeUrl(input.slice(contentStart, contentEnd), false);
  881. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  882. if (value.length === 0) return end;
  883. const dep = new CssUrlDependency(value, [start, end], "url");
  884. const { line: sl, column: sc } = locConverter.get(start);
  885. const { line: el, column: ec } = locConverter.get(end);
  886. dep.setLoc(sl, sc, el, ec);
  887. module.addDependency(dep);
  888. module.addCodeGenerationDependency(dep);
  889. return end;
  890. };
  891. /**
  892. * @param {string} input input
  893. * @param {number} start start position
  894. * @param {number} end end position
  895. * @returns {number} position after handling
  896. */
  897. const processImageSetFunction = (input, start, end) => {
  898. lastTokenEndForComments = end;
  899. const values = walkCssTokens.eatImageSetStrings(input, end, {
  900. comment
  901. });
  902. if (values.length === 0) return end;
  903. for (const [index, string] of values.entries()) {
  904. const value = normalizeUrl(
  905. input.slice(string[0] + 1, string[1] - 1),
  906. true
  907. );
  908. if (value.length === 0) return end;
  909. const { options, errors: commentErrors } = this.parseCommentOptions([
  910. index === 0 ? start : values[index - 1][1],
  911. string[1]
  912. ]);
  913. if (commentErrors) {
  914. for (const e of commentErrors) {
  915. const { comment } = e;
  916. state.module.addWarning(
  917. new CommentCompilationWarning(
  918. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  919. comment.loc
  920. )
  921. );
  922. }
  923. }
  924. if (options && options.webpackIgnore !== undefined) {
  925. if (typeof options.webpackIgnore !== "boolean") {
  926. const { line: sl, column: sc } = locConverter.get(string[0]);
  927. const { line: el, column: ec } = locConverter.get(string[1]);
  928. state.module.addWarning(
  929. new UnsupportedFeatureWarning(
  930. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  931. {
  932. start: { line: sl, column: sc },
  933. end: { line: el, column: ec }
  934. }
  935. )
  936. );
  937. } else if (options.webpackIgnore) {
  938. continue;
  939. }
  940. }
  941. const dep = new CssUrlDependency(value, [string[0], string[1]], "url");
  942. const { line: sl, column: sc } = locConverter.get(string[0]);
  943. const { line: el, column: ec } = locConverter.get(string[1]);
  944. dep.setLoc(sl, sc, el, ec);
  945. module.addDependency(dep);
  946. module.addCodeGenerationDependency(dep);
  947. }
  948. // Can contain `url()` inside, so let's return end to allow parse them
  949. return end;
  950. };
  951. // CSS modules stuff
  952. /**
  953. * @param {string} value value to resolve
  954. * @returns {string | [string, string, boolean]} resolved reexport
  955. */
  956. const getReexport = (value) => {
  957. const reexport = icssDefinitions.get(value);
  958. if (reexport) {
  959. if (reexport.isReference) {
  960. return [value, reexport.value, true];
  961. }
  962. return [value, reexport.value, false];
  963. }
  964. return value;
  965. };
  966. /**
  967. * @param {0 | 1} type import or export
  968. * @param {string} input input
  969. * @param {number} pos start position
  970. * @returns {number} position after parse
  971. */
  972. const processImportOrExport = (type, input, pos) => {
  973. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  974. /** @type {string | undefined} */
  975. let request;
  976. if (type === 0) {
  977. let cc = input.charCodeAt(pos);
  978. if (cc !== CC_LEFT_PARENTHESIS) {
  979. this._emitWarning(
  980. state,
  981. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`,
  982. locConverter,
  983. pos,
  984. pos
  985. );
  986. return pos;
  987. }
  988. pos++;
  989. const stringStart = pos;
  990. const str = walkCssTokens.eatString(input, pos);
  991. if (!str) {
  992. this._emitWarning(
  993. state,
  994. `Unexpected '${input[pos]}' at ${pos} during parsing of '${type ? ":import" : ":export"}' (expected string)`,
  995. locConverter,
  996. stringStart,
  997. pos
  998. );
  999. return pos;
  1000. }
  1001. request = input.slice(str[0] + 1, str[1] - 1);
  1002. pos = str[1];
  1003. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1004. cc = input.charCodeAt(pos);
  1005. if (cc !== CC_RIGHT_PARENTHESIS) {
  1006. this._emitWarning(
  1007. state,
  1008. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`,
  1009. locConverter,
  1010. pos,
  1011. pos
  1012. );
  1013. return pos;
  1014. }
  1015. pos++;
  1016. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1017. }
  1018. /**
  1019. * @param {string} name name
  1020. * @param {string} value value
  1021. * @param {number} start start of position
  1022. * @param {number} end end of position
  1023. */
  1024. const createDep = (name, value, start, end) => {
  1025. if (type === 0) {
  1026. const dep = new CssIcssImportDependency(
  1027. /** @type {string} */
  1028. (request),
  1029. [0, 0],
  1030. /** @type {"local" | "global"} */
  1031. (mode),
  1032. value
  1033. );
  1034. const { line: sl, column: sc } = locConverter.get(start);
  1035. const { line: el, column: ec } = locConverter.get(end);
  1036. dep.setLoc(sl, sc, el, ec);
  1037. module.addDependency(dep);
  1038. icssDefinitions.set(name, { value, isReference: true });
  1039. } else if (type === 1) {
  1040. const dep = new CssIcssExportDependency(name, getReexport(value));
  1041. const { line: sl, column: sc } = locConverter.get(start);
  1042. const { line: el, column: ec } = locConverter.get(end);
  1043. dep.setLoc(sl, sc, el, ec);
  1044. module.addDependency(dep);
  1045. }
  1046. };
  1047. let needTerminate = false;
  1048. let balanced = 0;
  1049. /** @type {undefined | 0 | 1 | 2} */
  1050. let scope;
  1051. /** @typedef {[number, number]} Name */
  1052. /** @type {Name | undefined} */
  1053. let name;
  1054. /** @type {number | undefined} */
  1055. let value;
  1056. /** @type {CssTokenCallbacks} */
  1057. const callbacks = {
  1058. leftCurlyBracket: (_input, _start, end) => {
  1059. balanced++;
  1060. if (scope === undefined) {
  1061. scope = 0;
  1062. }
  1063. return end;
  1064. },
  1065. rightCurlyBracket: (_input, _start, end) => {
  1066. balanced--;
  1067. if (scope === 2) {
  1068. const [nameStart, nameEnd] = /** @type {Name} */ (name);
  1069. createDep(
  1070. input.slice(nameStart, nameEnd),
  1071. input.slice(value, end - 1).trim(),
  1072. nameEnd,
  1073. end - 1
  1074. );
  1075. scope = 0;
  1076. }
  1077. if (balanced === 0 && scope === 0) {
  1078. needTerminate = true;
  1079. }
  1080. return end;
  1081. },
  1082. identifier: (_input, start, end) => {
  1083. if (scope === 0) {
  1084. name = [start, end];
  1085. scope = 1;
  1086. }
  1087. return end;
  1088. },
  1089. colon: (_input, _start, end) => {
  1090. if (scope === 1) {
  1091. scope = 2;
  1092. value = walkCssTokens.eatWhitespace(input, end);
  1093. return value;
  1094. }
  1095. return end;
  1096. },
  1097. semicolon: (input, _start, end) => {
  1098. if (scope === 2) {
  1099. const [nameStart, nameEnd] = /** @type {Name} */ (name);
  1100. createDep(
  1101. input.slice(nameStart, nameEnd),
  1102. input.slice(value, end - 1),
  1103. nameEnd,
  1104. end - 1
  1105. );
  1106. scope = 0;
  1107. }
  1108. return end;
  1109. },
  1110. needTerminate: () => needTerminate
  1111. };
  1112. pos = walkCssTokens(input, pos, callbacks);
  1113. pos = walkCssTokens.eatWhiteLine(input, pos);
  1114. return pos;
  1115. };
  1116. /**
  1117. * @param {string} input input
  1118. * @param {number} start name start position
  1119. * @param {number} end name end position
  1120. * @returns {number} position after handling
  1121. */
  1122. const processAtValue = (input, start, end) => {
  1123. const semi = eatUntilSemi(input, end);
  1124. const atRuleEnd = semi + 1;
  1125. const params = input.slice(end, semi);
  1126. let [alias, request] = params.split(/\s*from\s*/);
  1127. if (request) {
  1128. const aliases = alias
  1129. .replace(CSS_COMMENT, " ")
  1130. .trim()
  1131. .replace(/^\(\s*|\s*\)$/g, "")
  1132. .split(/\s*,\s*/);
  1133. request = request.replace(CSS_COMMENT, "").trim();
  1134. const isExplicitImport = request[0] === "'" || request[0] === '"';
  1135. if (isExplicitImport) {
  1136. request = request.slice(1, -1);
  1137. }
  1138. for (const alias of aliases) {
  1139. const [name, aliasName] = alias.split(/\s+as\s+/);
  1140. {
  1141. const reexport = icssDefinitions.get(request);
  1142. if (reexport) {
  1143. request = reexport.value.slice(1, -1);
  1144. }
  1145. const dep = new CssIcssImportDependency(
  1146. request,
  1147. [0, 0],
  1148. /** @type {"local" | "global"} */
  1149. (mode),
  1150. name
  1151. );
  1152. const { line: sl, column: sc } = locConverter.get(start);
  1153. const { line: el, column: ec } = locConverter.get(end);
  1154. dep.setLoc(sl, sc, el, ec);
  1155. module.addDependency(dep);
  1156. icssDefinitions.set(aliasName || name, {
  1157. value: name,
  1158. isReference: true
  1159. });
  1160. }
  1161. {
  1162. const dep = new CssIcssExportDependency(
  1163. aliasName || name,
  1164. getReexport(aliasName || name),
  1165. undefined,
  1166. false,
  1167. CssIcssExportDependency.EXPORT_MODE.REPLACE
  1168. );
  1169. const { line: sl, column: sc } = locConverter.get(start);
  1170. const { line: el, column: ec } = locConverter.get(end);
  1171. dep.setLoc(sl, sc, el, ec);
  1172. module.addDependency(dep);
  1173. }
  1174. }
  1175. } else {
  1176. const ident = walkCssTokens.eatIdentSequence(alias, 0);
  1177. if (!ident) {
  1178. this._emitWarning(
  1179. state,
  1180. `Broken '@value' at-rule: ${input.slice(start, atRuleEnd)}'`,
  1181. locConverter,
  1182. start,
  1183. atRuleEnd
  1184. );
  1185. const dep = new ConstDependency("", [start, atRuleEnd]);
  1186. module.addPresentationalDependency(dep);
  1187. return atRuleEnd;
  1188. }
  1189. const pos = walkCssTokens.eatWhitespaceAndComments(alias, ident[1])[0];
  1190. const name = alias.slice(ident[0], ident[1]);
  1191. let value =
  1192. alias.charCodeAt(pos) === CC_COLON
  1193. ? alias.slice(pos + 1)
  1194. : alias.slice(ident[1]);
  1195. if (value && !/^\s+$/.test(value.replace(CSS_COMMENT, ""))) {
  1196. value = value.trim();
  1197. }
  1198. if (icssDefinitions.has(value)) {
  1199. const def =
  1200. /** @type {IcssDefinition} */
  1201. (icssDefinitions.get(value));
  1202. value = def.value;
  1203. }
  1204. icssDefinitions.set(name, { value });
  1205. const dep = new CssIcssExportDependency(name, value);
  1206. const { line: sl, column: sc } = locConverter.get(start);
  1207. const { line: el, column: ec } = locConverter.get(end);
  1208. dep.setLoc(sl, sc, el, ec);
  1209. module.addDependency(dep);
  1210. }
  1211. const dep = new ConstDependency("", [start, atRuleEnd]);
  1212. module.addPresentationalDependency(dep);
  1213. return atRuleEnd;
  1214. };
  1215. /**
  1216. * @param {string} name ICSS symbol name
  1217. * @param {number} start start position
  1218. * @param {number} end end position
  1219. * @returns {number} position after handling
  1220. */
  1221. const processICSSSymbol = (name, start, end) => {
  1222. const { value, isReference } =
  1223. /** @type {IcssDefinition} */
  1224. (icssDefinitions.get(name));
  1225. const { line: sl, column: sc } = locConverter.get(start);
  1226. const { line: el, column: ec } = locConverter.get(end);
  1227. const dep = new CssIcssSymbolDependency(
  1228. name,
  1229. value,
  1230. [start, end],
  1231. isReference
  1232. );
  1233. dep.setLoc(sl, sc, el, ec);
  1234. module.addDependency(dep);
  1235. return end;
  1236. };
  1237. /**
  1238. * @param {string} input input
  1239. * @param {1 | 2} type type of function
  1240. * @param {number} start start position
  1241. * @param {number} end end position
  1242. * @returns {number} position after handling
  1243. */
  1244. const processLocalOrGlobalFunction = (input, type, start, end) => {
  1245. // Replace `local(`/` or `global(` (handle legacy `:local(` or `:global(` too)
  1246. {
  1247. const isColon = input.charCodeAt(start - 1) === CC_COLON;
  1248. const dep = new ConstDependency("", [isColon ? start - 1 : start, end]);
  1249. module.addPresentationalDependency(dep);
  1250. }
  1251. end = walkCssTokens.consumeUntil(
  1252. input,
  1253. start,
  1254. {
  1255. identifier(input, start, end) {
  1256. if (type === 1) {
  1257. let identifier = unescapeIdentifier(input.slice(start, end));
  1258. const { line: sl, column: sc } = locConverter.get(start);
  1259. const { line: el, column: ec } = locConverter.get(end);
  1260. const isDashedIdent = isDashedIdentifier(identifier);
  1261. if (isDashedIdent) {
  1262. identifier = identifier.slice(2);
  1263. }
  1264. const dep = new CssIcssExportDependency(
  1265. identifier,
  1266. getReexport(identifier),
  1267. [start, end],
  1268. true,
  1269. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1270. isDashedIdent
  1271. ? CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
  1272. : CssIcssExportDependency.EXPORT_TYPE.NORMAL
  1273. );
  1274. dep.setLoc(sl, sc, el, ec);
  1275. module.addDependency(dep);
  1276. }
  1277. return end;
  1278. }
  1279. },
  1280. {},
  1281. { onlyTopLevel: true, functionValue: true }
  1282. );
  1283. {
  1284. // Replace the last `)`
  1285. const dep = new ConstDependency("", [end, end + 1]);
  1286. module.addPresentationalDependency(dep);
  1287. }
  1288. return end;
  1289. };
  1290. /**
  1291. * @param {string} input input
  1292. * @param {number} end name end position
  1293. * @param {{ string?: boolean, identifier?: boolean | RegExp }} options types which allowed to handle
  1294. * @returns {number} position after handling
  1295. */
  1296. const processLocalAtRule = (input, end, options) => {
  1297. let found = false;
  1298. return walkCssTokens.consumeUntil(
  1299. input,
  1300. end,
  1301. {
  1302. string(_input, start, end) {
  1303. if (!found && options.string) {
  1304. const value = unescapeIdentifier(input.slice(start + 1, end - 1));
  1305. const { line: sl, column: sc } = locConverter.get(start);
  1306. const { line: el, column: ec } = locConverter.get(end);
  1307. const dep = new CssIcssExportDependency(
  1308. value,
  1309. value,
  1310. [start, end],
  1311. true,
  1312. CssIcssExportDependency.EXPORT_MODE.ONCE
  1313. );
  1314. dep.setLoc(sl, sc, el, ec);
  1315. module.addDependency(dep);
  1316. found = true;
  1317. }
  1318. return end;
  1319. },
  1320. identifier(input, start, end) {
  1321. if (!found) {
  1322. const value = input.slice(start, end);
  1323. if (options.identifier) {
  1324. const identifier = unescapeIdentifier(value);
  1325. if (
  1326. options.identifier instanceof RegExp &&
  1327. options.identifier.test(identifier)
  1328. ) {
  1329. return end;
  1330. }
  1331. const { line: sl, column: sc } = locConverter.get(start);
  1332. const { line: el, column: ec } = locConverter.get(end);
  1333. const dep = new CssIcssExportDependency(
  1334. identifier,
  1335. getReexport(identifier),
  1336. [start, end],
  1337. true,
  1338. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1339. CssIcssExportDependency.EXPORT_TYPE.NORMAL
  1340. );
  1341. dep.setLoc(sl, sc, el, ec);
  1342. module.addDependency(dep);
  1343. found = true;
  1344. }
  1345. }
  1346. return end;
  1347. }
  1348. },
  1349. {
  1350. function: (input, start, end) => {
  1351. // No need to handle `:` (COLON), because it's always a function
  1352. const name = input
  1353. .slice(start, end - 1)
  1354. .replace(/\\/g, "")
  1355. .toLowerCase();
  1356. const type =
  1357. name === "local" ? 1 : name === "global" ? 2 : undefined;
  1358. if (!found && type) {
  1359. found = true;
  1360. return processLocalOrGlobalFunction(input, type, start, end);
  1361. }
  1362. if (
  1363. this.options.dashedIdents &&
  1364. isLocalMode() &&
  1365. (name === "var" || name === "style")
  1366. ) {
  1367. return processDashedIdent(input, end, end);
  1368. }
  1369. return end;
  1370. }
  1371. },
  1372. { onlyTopLevel: true, atRulePrelude: true }
  1373. );
  1374. };
  1375. /**
  1376. * @param {string} input input
  1377. * @param {number} start start position
  1378. * @param {number} end end position
  1379. * @returns {number} position after handling
  1380. */
  1381. const processDashedIdent = (input, start, end) => {
  1382. const customIdent = walkCssTokens.eatIdentSequence(input, start);
  1383. if (!customIdent) return end;
  1384. const identifier = unescapeIdentifier(
  1385. input.slice(customIdent[0] + 2, customIdent[1])
  1386. );
  1387. const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments(
  1388. input,
  1389. customIdent[1]
  1390. )[0];
  1391. if (
  1392. input.charCodeAt(afterCustomIdent) === CC_LOWER_F ||
  1393. input.charCodeAt(afterCustomIdent) === CC_UPPER_F
  1394. ) {
  1395. const fromWord = walkCssTokens.eatIdentSequence(
  1396. input,
  1397. afterCustomIdent
  1398. );
  1399. if (
  1400. !fromWord ||
  1401. input.slice(fromWord[0], fromWord[1]).toLowerCase() !== "from"
  1402. ) {
  1403. return end;
  1404. }
  1405. const from = walkCssTokens.eatIdentSequenceOrString(
  1406. input,
  1407. walkCssTokens.eatWhitespaceAndComments(input, fromWord[1])[0]
  1408. );
  1409. if (!from) {
  1410. return end;
  1411. }
  1412. const path = input.slice(from[0], from[1]);
  1413. if (from[2] === true && path === "global") {
  1414. const dep = new ConstDependency("", [customIdent[1], from[1]]);
  1415. module.addPresentationalDependency(dep);
  1416. return end;
  1417. } else if (from[2] === false) {
  1418. const { line: sl, column: sc } = locConverter.get(customIdent[0]);
  1419. const { line: el, column: ec } = locConverter.get(from[1] - 1);
  1420. const dep = new CssIcssImportDependency(
  1421. path.slice(1, -1),
  1422. [customIdent[0], from[1] - 1],
  1423. /** @type {"local" | "global"} */
  1424. (mode),
  1425. identifier,
  1426. identifier,
  1427. CssIcssExportDependency.EXPORT_MODE.NONE,
  1428. CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
  1429. );
  1430. dep.setLoc(sl, sc, el, ec);
  1431. module.addDependency(dep);
  1432. {
  1433. const dep = new ConstDependency("", [fromWord[0], from[1]]);
  1434. module.addPresentationalDependency(dep);
  1435. return end;
  1436. }
  1437. }
  1438. } else {
  1439. const { line: sl, column: sc } = locConverter.get(customIdent[0]);
  1440. const { line: el, column: ec } = locConverter.get(customIdent[1]);
  1441. const dep = new CssIcssExportDependency(
  1442. identifier,
  1443. getReexport(identifier),
  1444. [customIdent[0], customIdent[1]],
  1445. true,
  1446. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1447. CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
  1448. );
  1449. dep.setLoc(sl, sc, el, ec);
  1450. module.addDependency(dep);
  1451. return end;
  1452. }
  1453. return end;
  1454. };
  1455. /**
  1456. * @param {string} input input
  1457. * @param {number} pos name start position
  1458. * @param {number} end name end position
  1459. * @returns {number} position after handling
  1460. */
  1461. const processLocalDeclaration = (input, pos, end) => {
  1462. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1463. const identifier = walkCssTokens.eatIdentSequence(input, pos);
  1464. if (!identifier) {
  1465. return end;
  1466. }
  1467. const propertyNameStart = identifier[0];
  1468. pos = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];
  1469. if (input.charCodeAt(pos) !== CC_COLON) {
  1470. return end;
  1471. }
  1472. pos += 1;
  1473. // Remove prefix and lowercase
  1474. const propertyName = input
  1475. .slice(identifier[0], identifier[1])
  1476. .replace(/^(-\w+-)/, "")
  1477. .toLowerCase();
  1478. if (isLocalMode() && knownProperties.has(propertyName)) {
  1479. /** @type {[number, number, boolean?][]} */
  1480. const values = [];
  1481. /** @type {Record<string, number>} */
  1482. let parsedKeywords = Object.create(null);
  1483. const isGridProperty = Boolean(propertyName.startsWith("grid"));
  1484. const isGridTemplate = isGridProperty
  1485. ? Boolean(
  1486. propertyName === "grid" ||
  1487. propertyName === "grid-template" ||
  1488. propertyName === "grid-template-columns" ||
  1489. propertyName === "grid-template-rows"
  1490. )
  1491. : false;
  1492. const end = walkCssTokens.consumeUntil(
  1493. input,
  1494. pos,
  1495. {
  1496. leftSquareBracket(input, start, end) {
  1497. let i = end;
  1498. while (true) {
  1499. i = walkCssTokens.eatWhitespaceAndComments(input, i)[0];
  1500. const name = walkCssTokens.eatIdentSequence(input, i);
  1501. if (!name) {
  1502. break;
  1503. }
  1504. values.push(name);
  1505. i = name[1];
  1506. }
  1507. return end;
  1508. },
  1509. string(_input, start, end) {
  1510. if (
  1511. propertyName === "animation" ||
  1512. propertyName === "animation-name"
  1513. ) {
  1514. values.push([start, end, true]);
  1515. }
  1516. if (
  1517. propertyName === "grid" ||
  1518. propertyName === "grid-template" ||
  1519. propertyName === "grid-template-areas"
  1520. ) {
  1521. const areas = unescapeIdentifier(
  1522. input.slice(start + 1, end - 1)
  1523. );
  1524. const matches = matchAll(/\b\w+\b/g, areas);
  1525. for (const match of matches) {
  1526. const areaStart = start + 1 + match.index;
  1527. values.push([areaStart, areaStart + match[0].length, false]);
  1528. }
  1529. }
  1530. return end;
  1531. },
  1532. identifier(input, start, end) {
  1533. if (isGridTemplate) {
  1534. return end;
  1535. }
  1536. const identifier = input.slice(start, end);
  1537. const keyword = identifier.toLowerCase();
  1538. parsedKeywords[keyword] =
  1539. typeof parsedKeywords[keyword] !== "undefined"
  1540. ? parsedKeywords[keyword] + 1
  1541. : 0;
  1542. const keywords =
  1543. /** @type {Record<string, number>} */
  1544. (knownProperties.get(propertyName));
  1545. if (
  1546. keywords[keyword] &&
  1547. parsedKeywords[keyword] < keywords[keyword]
  1548. ) {
  1549. return end;
  1550. }
  1551. values.push([start, end]);
  1552. return end;
  1553. },
  1554. comma(_input, _start, end) {
  1555. parsedKeywords = {};
  1556. return end;
  1557. }
  1558. },
  1559. {
  1560. function: (input, start, end) => {
  1561. const name = input
  1562. .slice(start, end - 1)
  1563. .replace(/\\/g, "")
  1564. .toLowerCase();
  1565. const type =
  1566. name === "local" ? 1 : name === "global" ? 2 : undefined;
  1567. if (type) {
  1568. return processLocalOrGlobalFunction(input, type, start, end);
  1569. }
  1570. if (
  1571. this.options.dashedIdents &&
  1572. isLocalMode() &&
  1573. name === "var"
  1574. ) {
  1575. return processDashedIdent(input, end, end);
  1576. }
  1577. if (this.options.url) {
  1578. if (name === "src" || name === "url") {
  1579. return processURLFunction(input, end, name);
  1580. } else if (IMAGE_SET_FUNCTION.test(name)) {
  1581. return processImageSetFunction(input, start, end);
  1582. }
  1583. }
  1584. return end;
  1585. }
  1586. },
  1587. {
  1588. onlyTopLevel: !isGridTemplate,
  1589. declarationValue: true
  1590. }
  1591. );
  1592. if (values.length > 0) {
  1593. for (const value of values) {
  1594. const { line: sl, column: sc } = locConverter.get(value[0]);
  1595. const { line: el, column: ec } = locConverter.get(value[1]);
  1596. const [start, end, isString] = value;
  1597. const name = unescapeIdentifier(
  1598. isString
  1599. ? input.slice(start + 1, end - 1)
  1600. : input.slice(start, end)
  1601. );
  1602. const dep = new CssIcssExportDependency(
  1603. name,
  1604. getReexport(name),
  1605. [start, end],
  1606. true,
  1607. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1608. isGridProperty
  1609. ? CssIcssExportDependency.EXPORT_TYPE.GRID_CUSTOM_IDENTIFIER
  1610. : CssIcssExportDependency.EXPORT_TYPE.NORMAL
  1611. );
  1612. dep.setLoc(sl, sc, el, ec);
  1613. module.addDependency(dep);
  1614. }
  1615. }
  1616. return end;
  1617. } else if (COMPOSES_PROPERTY.test(propertyName)) {
  1618. if (lastLocalIdentifiers.length > 1) {
  1619. const end = eatUntilSemi(input, pos);
  1620. this._emitWarning(
  1621. state,
  1622. `Composition is only allowed when selector is single local class name not in "${lastLocalIdentifiers.join('", "')}"`,
  1623. locConverter,
  1624. pos,
  1625. end
  1626. );
  1627. return end;
  1628. }
  1629. if (lastLocalIdentifiers.length !== 1) return pos;
  1630. const lastLocalIdentifier = lastLocalIdentifiers[0];
  1631. let end = pos;
  1632. /** @type {Set<[number, number]>} */
  1633. const classNames = new Set();
  1634. while (true) {
  1635. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1636. let className = walkCssTokens.eatIdentSequence(input, pos);
  1637. const ifFunction =
  1638. className && input.charCodeAt(className[1]) === CC_LEFT_PARENTHESIS;
  1639. let isGlobalFunction = false;
  1640. if (className && ifFunction) {
  1641. const name = input
  1642. .slice(className[0], className[1])
  1643. .replace(/\\/g, "")
  1644. .toLowerCase();
  1645. isGlobalFunction = name === "global";
  1646. pos = walkCssTokens.eatWhitespaceAndComments(
  1647. input,
  1648. className[1] + 1
  1649. )[0];
  1650. className = walkCssTokens.eatIdentSequence(input, pos);
  1651. if (className) {
  1652. pos = walkCssTokens.eatWhitespaceAndComments(
  1653. input,
  1654. className[1]
  1655. )[0];
  1656. pos += 1;
  1657. }
  1658. } else if (className) {
  1659. pos = walkCssTokens.eatWhitespaceAndComments(
  1660. input,
  1661. className[1]
  1662. )[0];
  1663. pos = className[1];
  1664. }
  1665. // True when we have multiple values
  1666. const isComma = input.charCodeAt(pos) === CC_COMMA;
  1667. const isSemicolon = input.charCodeAt(pos) === CC_SEMICOLON;
  1668. const isRightCurly = input.charCodeAt(pos) === CC_RIGHT_CURLY;
  1669. if (isComma || isSemicolon || isRightCurly) {
  1670. if (className) {
  1671. classNames.add(className);
  1672. }
  1673. for (const className of classNames) {
  1674. const [start, end] = className;
  1675. const identifier = unescapeIdentifier(input.slice(start, end));
  1676. const resolvedClassName = getReexport(identifier);
  1677. const dep = new CssIcssExportDependency(
  1678. lastLocalIdentifier,
  1679. resolvedClassName,
  1680. [start, end],
  1681. isGlobalFunction ? false : !Array.isArray(resolvedClassName),
  1682. isGlobalFunction
  1683. ? CssIcssExportDependency.EXPORT_MODE.APPEND
  1684. : CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE
  1685. );
  1686. const { line: sl, column: sc } = locConverter.get(start);
  1687. const { line: el, column: ec } = locConverter.get(end);
  1688. dep.setLoc(sl, sc, el, ec);
  1689. module.addDependency(dep);
  1690. }
  1691. classNames.clear();
  1692. if (isSemicolon || isRightCurly) {
  1693. end = isSemicolon
  1694. ? walkCssTokens.eatWhitespace(input, pos + 1)
  1695. : pos;
  1696. break;
  1697. }
  1698. pos += 1;
  1699. } else if (
  1700. classNames.size > 0 &&
  1701. className &&
  1702. input.slice(className[0], className[1]).toLowerCase() === "from"
  1703. ) {
  1704. let from = walkCssTokens.eatString(input, pos);
  1705. if (from) {
  1706. const request = input.slice(from[0] + 1, from[1] - 1);
  1707. for (const className of classNames) {
  1708. const [start, end] = className;
  1709. const identifier = unescapeIdentifier(input.slice(start, end));
  1710. const dep = new CssIcssImportDependency(
  1711. request,
  1712. [start, end],
  1713. /** @type {"local" | "global"} */
  1714. (mode),
  1715. identifier,
  1716. /** @type {string} */
  1717. (lastLocalIdentifier),
  1718. CssIcssExportDependency.EXPORT_MODE.APPEND
  1719. );
  1720. const { line: sl, column: sc } = locConverter.get(start);
  1721. const { line: el, column: ec } = locConverter.get(end);
  1722. dep.setLoc(sl, sc, el, ec);
  1723. module.addDependency(dep);
  1724. }
  1725. classNames.clear();
  1726. pos = from[1];
  1727. } else {
  1728. from = walkCssTokens.eatIdentSequence(input, pos);
  1729. if (from && input.slice(from[0], from[1]) === "global") {
  1730. for (const className of classNames) {
  1731. const [start, end] = className;
  1732. const identifier = unescapeIdentifier(
  1733. input.slice(start, end)
  1734. );
  1735. const dep = new CssIcssExportDependency(
  1736. /** @type {string} */
  1737. (lastLocalIdentifier),
  1738. getReexport(identifier),
  1739. [start, end],
  1740. false,
  1741. CssIcssExportDependency.EXPORT_MODE.APPEND
  1742. );
  1743. const { line: sl, column: sc } = locConverter.get(start);
  1744. const { line: el, column: ec } = locConverter.get(end);
  1745. dep.setLoc(sl, sc, el, ec);
  1746. module.addDependency(dep);
  1747. }
  1748. classNames.clear();
  1749. pos = from[1];
  1750. } else {
  1751. const end = eatUntilSemi(input, pos);
  1752. this._emitWarning(
  1753. state,
  1754. "Incorrect composition, expected global keyword or string value",
  1755. locConverter,
  1756. pos,
  1757. end
  1758. );
  1759. return end;
  1760. }
  1761. }
  1762. } else if (className) {
  1763. classNames.add(className);
  1764. } else {
  1765. const end = eatUntilSemi(input, pos);
  1766. this._emitWarning(
  1767. state,
  1768. "Incorrect composition, expected class named",
  1769. locConverter,
  1770. pos,
  1771. end
  1772. );
  1773. return end;
  1774. }
  1775. }
  1776. // Remove `composes` from source code
  1777. const dep = new ConstDependency("", [propertyNameStart, end]);
  1778. module.addPresentationalDependency(dep);
  1779. }
  1780. return pos;
  1781. };
  1782. /**
  1783. * @param {string} input input
  1784. * @param {number} start start position
  1785. * @param {number} end end position
  1786. * @returns {number} position after handling
  1787. */
  1788. const processIdSelector = (input, start, end) => {
  1789. const valueStart = start + 1;
  1790. const name = unescapeIdentifier(input.slice(valueStart, end));
  1791. const dep = new CssIcssExportDependency(
  1792. name,
  1793. getReexport(name),
  1794. [valueStart, end],
  1795. true,
  1796. CssIcssExportDependency.EXPORT_MODE.ONCE
  1797. );
  1798. const { line: sl, column: sc } = locConverter.get(start);
  1799. const { line: el, column: ec } = locConverter.get(end);
  1800. dep.setLoc(sl, sc, el, ec);
  1801. module.addDependency(dep);
  1802. return end;
  1803. };
  1804. /**
  1805. * @param {string} input input
  1806. * @param {number} start start position
  1807. * @param {number} end end position
  1808. * @returns {number} position after handling
  1809. */
  1810. const processClassSelector = (input, start, end) => {
  1811. const ident = walkCssTokens.skipCommentsAndEatIdentSequence(input, end);
  1812. if (!ident) return end;
  1813. const name = unescapeIdentifier(input.slice(ident[0], ident[1]));
  1814. lastLocalIdentifiers.push(name);
  1815. const dep = new CssIcssExportDependency(
  1816. name,
  1817. getReexport(name),
  1818. [ident[0], ident[1]],
  1819. true,
  1820. CssIcssExportDependency.EXPORT_MODE.ONCE
  1821. );
  1822. const { line: sl, column: sc } = locConverter.get(ident[0]);
  1823. const { line: el, column: ec } = locConverter.get(ident[1]);
  1824. dep.setLoc(sl, sc, el, ec);
  1825. module.addDependency(dep);
  1826. return ident[1];
  1827. };
  1828. /**
  1829. * @param {string} input input
  1830. * @param {number} start start position
  1831. * @param {number} end end position
  1832. * @returns {number} position after handling
  1833. */
  1834. const processAttributeSelector = (input, start, end) => {
  1835. end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
  1836. const identifier = walkCssTokens.eatIdentSequence(input, end);
  1837. if (!identifier) return end;
  1838. const name = unescapeIdentifier(
  1839. input.slice(identifier[0], identifier[1])
  1840. );
  1841. if (name.toLowerCase() !== "class") {
  1842. return end;
  1843. }
  1844. end = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];
  1845. const isTilde = input.charCodeAt(end) === CC_TILDE;
  1846. if (
  1847. input.charCodeAt(end) !== CC_EQUAL &&
  1848. input.charCodeAt(end) !== CC_TILDE
  1849. ) {
  1850. return end;
  1851. }
  1852. end += 1;
  1853. if (isTilde) {
  1854. if (input.charCodeAt(end) !== CC_EQUAL) {
  1855. return end;
  1856. }
  1857. end += 1;
  1858. }
  1859. end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
  1860. const value = walkCssTokens.eatIdentSequenceOrString(input, end);
  1861. if (!value) {
  1862. return end;
  1863. }
  1864. const classNameStart = value[2] ? value[0] : value[0] + 1;
  1865. const classNameEnd = value[2] ? value[1] : value[1] - 1;
  1866. const className = unescapeIdentifier(
  1867. input.slice(classNameStart, classNameEnd)
  1868. );
  1869. const dep = new CssIcssExportDependency(
  1870. className,
  1871. getReexport(className),
  1872. [classNameStart, classNameEnd],
  1873. true,
  1874. CssIcssExportDependency.EXPORT_MODE.NONE
  1875. );
  1876. const { line: sl, column: sc } = locConverter.get(classNameStart);
  1877. const { line: el, column: ec } = locConverter.get(classNameEnd);
  1878. dep.setLoc(sl, sc, el, ec);
  1879. module.addDependency(dep);
  1880. return value[2] ? classNameEnd : classNameEnd + 1;
  1881. };
  1882. walkCssTokens(source, 0, {
  1883. comment,
  1884. leftCurlyBracket: (input, start, end) => {
  1885. switch (scope) {
  1886. case CSS_MODE_TOP_LEVEL: {
  1887. allowImportAtRule = false;
  1888. scope = CSS_MODE_IN_BLOCK;
  1889. if (isModules) {
  1890. blockNestingLevel = 1;
  1891. isNextRulePrelude = isNextNestedSyntax(input, end);
  1892. }
  1893. break;
  1894. }
  1895. case CSS_MODE_IN_BLOCK: {
  1896. if (isModules) {
  1897. blockNestingLevel++;
  1898. isNextRulePrelude = isNextNestedSyntax(input, end);
  1899. }
  1900. break;
  1901. }
  1902. }
  1903. return end;
  1904. },
  1905. rightCurlyBracket: (input, start, end) => {
  1906. switch (scope) {
  1907. case CSS_MODE_IN_BLOCK: {
  1908. if (--blockNestingLevel === 0) {
  1909. scope = CSS_MODE_TOP_LEVEL;
  1910. if (isModules) {
  1911. isNextRulePrelude = true;
  1912. modeData = undefined;
  1913. lastLocalIdentifiers = [];
  1914. }
  1915. } else if (isModules) {
  1916. isNextRulePrelude = isNextNestedSyntax(input, end);
  1917. }
  1918. break;
  1919. }
  1920. }
  1921. return end;
  1922. },
  1923. url: (input, start, end, contentStart, contentEnd) => {
  1924. if (!this.options.url) {
  1925. return end;
  1926. }
  1927. return processOldURLFunction(
  1928. input,
  1929. start,
  1930. end,
  1931. contentStart,
  1932. contentEnd
  1933. );
  1934. },
  1935. atKeyword: (input, start, end) => {
  1936. const name = input.slice(start, end).toLowerCase();
  1937. switch (name) {
  1938. case "@namespace": {
  1939. this._emitWarning(
  1940. state,
  1941. "'@namespace' is not supported in bundled CSS",
  1942. locConverter,
  1943. start,
  1944. end
  1945. );
  1946. return eatUntilSemi(input, start);
  1947. }
  1948. case "@import": {
  1949. if (!this.options.import) {
  1950. return eatUntilSemi(input, end);
  1951. }
  1952. if (!allowImportAtRule) {
  1953. this._emitWarning(
  1954. state,
  1955. "Any '@import' rules must precede all other rules",
  1956. locConverter,
  1957. start,
  1958. end
  1959. );
  1960. return end;
  1961. }
  1962. return processAtImport(input, start, end);
  1963. }
  1964. default: {
  1965. if (isModules) {
  1966. if (name === "@value") {
  1967. return processAtValue(input, start, end);
  1968. } else if (
  1969. this.options.animation &&
  1970. OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) &&
  1971. isLocalMode()
  1972. ) {
  1973. return processLocalAtRule(input, end, {
  1974. string: true,
  1975. identifier: true
  1976. });
  1977. } else if (
  1978. this.options.customIdents &&
  1979. name === "@counter-style" &&
  1980. isLocalMode()
  1981. ) {
  1982. return processLocalAtRule(input, end, {
  1983. identifier: true
  1984. });
  1985. } else if (
  1986. this.options.container &&
  1987. name === "@container" &&
  1988. isLocalMode()
  1989. ) {
  1990. return processLocalAtRule(input, end, {
  1991. identifier: /^(none|and|or|not)$/
  1992. });
  1993. } else if (name === "@scope") {
  1994. isNextRulePrelude = true;
  1995. return end;
  1996. }
  1997. isNextRulePrelude = false;
  1998. }
  1999. }
  2000. }
  2001. return end;
  2002. },
  2003. semicolon: (input, start, end) => {
  2004. if (isModules && scope === CSS_MODE_IN_BLOCK) {
  2005. isNextRulePrelude = isNextNestedSyntax(input, end);
  2006. }
  2007. return end;
  2008. },
  2009. identifier: (input, start, end) => {
  2010. if (isModules) {
  2011. const identifier = input.slice(start, end);
  2012. if (icssDefinitions.has(identifier)) {
  2013. return processICSSSymbol(identifier, start, end);
  2014. }
  2015. if (
  2016. this.options.dashedIdents &&
  2017. isLocalMode() &&
  2018. isDashedIdentifier(identifier)
  2019. ) {
  2020. return processDashedIdent(input, start, end);
  2021. }
  2022. switch (scope) {
  2023. case CSS_MODE_IN_BLOCK: {
  2024. if (isModules && !isNextRulePrelude) {
  2025. // Handle only top level values and not inside functions
  2026. return processLocalDeclaration(input, start, end);
  2027. }
  2028. break;
  2029. }
  2030. }
  2031. }
  2032. return end;
  2033. },
  2034. delim: (input, start, end) => {
  2035. if (isNextRulePrelude && isLocalMode()) {
  2036. return processClassSelector(input, start, end);
  2037. }
  2038. return end;
  2039. },
  2040. hash: (input, start, end, isID) => {
  2041. if (isNextRulePrelude && isLocalMode() && isID) {
  2042. return processIdSelector(input, start, end);
  2043. }
  2044. return end;
  2045. },
  2046. colon: (input, start, end) => {
  2047. if (isModules) {
  2048. const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
  2049. input,
  2050. end
  2051. );
  2052. if (!ident) return end;
  2053. const name = input.slice(ident[0], ident[1]).toLowerCase();
  2054. switch (scope) {
  2055. case CSS_MODE_TOP_LEVEL: {
  2056. if (name === "import") {
  2057. const pos = processImportOrExport(0, input, ident[1]);
  2058. const dep = new ConstDependency("", [start, pos]);
  2059. module.addPresentationalDependency(dep);
  2060. return pos;
  2061. } else if (name === "export") {
  2062. const pos = processImportOrExport(1, input, ident[1]);
  2063. const dep = new ConstDependency("", [start, pos]);
  2064. module.addPresentationalDependency(dep);
  2065. return pos;
  2066. }
  2067. }
  2068. // falls through
  2069. default: {
  2070. if (isNextRulePrelude) {
  2071. const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS;
  2072. if (isFn && name === "local") {
  2073. // Eat extra whitespace
  2074. const end = walkCssTokens.eatWhitespaceAndComments(
  2075. input,
  2076. ident[1] + 1
  2077. )[0];
  2078. modeData = LOCAL_MODE;
  2079. const dep = new ConstDependency("", [start, end]);
  2080. module.addPresentationalDependency(dep);
  2081. balanced.push([":local", start, end, true]);
  2082. return end;
  2083. } else if (name === "local") {
  2084. modeData = LOCAL_MODE;
  2085. const found = walkCssTokens.eatWhitespaceAndComments(
  2086. input,
  2087. ident[1]
  2088. );
  2089. if (!found[1]) {
  2090. this._emitWarning(
  2091. state,
  2092. `Missing whitespace after ':local' in '${input.slice(
  2093. start,
  2094. eatUntilLeftCurly(input, end) + 1
  2095. )}'`,
  2096. locConverter,
  2097. start,
  2098. end
  2099. );
  2100. }
  2101. end = walkCssTokens.eatWhitespace(input, ident[1]);
  2102. const dep = new ConstDependency("", [start, end]);
  2103. module.addPresentationalDependency(dep);
  2104. return end;
  2105. } else if (isFn && name === "global") {
  2106. // Eat extra whitespace
  2107. const end = walkCssTokens.eatWhitespaceAndComments(
  2108. input,
  2109. ident[1] + 1
  2110. )[0];
  2111. modeData = GLOBAL_MODE;
  2112. const dep = new ConstDependency("", [start, end]);
  2113. module.addPresentationalDependency(dep);
  2114. balanced.push([":global", start, end, true]);
  2115. return end;
  2116. } else if (name === "global") {
  2117. modeData = GLOBAL_MODE;
  2118. // Eat extra whitespace
  2119. const found = walkCssTokens.eatWhitespaceAndComments(
  2120. input,
  2121. ident[1]
  2122. );
  2123. if (!found[1]) {
  2124. this._emitWarning(
  2125. state,
  2126. `Missing whitespace after ':global' in '${input.slice(
  2127. start,
  2128. eatUntilLeftCurly(input, end) + 1
  2129. )}'`,
  2130. locConverter,
  2131. start,
  2132. end
  2133. );
  2134. }
  2135. end = walkCssTokens.eatWhitespace(input, ident[1]);
  2136. const dep = new ConstDependency("", [start, end]);
  2137. module.addPresentationalDependency(dep);
  2138. return end;
  2139. }
  2140. }
  2141. }
  2142. }
  2143. }
  2144. lastTokenEndForComments = end;
  2145. return end;
  2146. },
  2147. function: (input, start, end) => {
  2148. const name = input
  2149. .slice(start, end - 1)
  2150. .replace(/\\/g, "")
  2151. .toLowerCase();
  2152. balanced.push([name, start, end]);
  2153. switch (name) {
  2154. case "src":
  2155. case "url": {
  2156. if (!this.options.url) {
  2157. return end;
  2158. }
  2159. return processURLFunction(input, end, name);
  2160. }
  2161. default: {
  2162. if (this.options.url && IMAGE_SET_FUNCTION.test(name)) {
  2163. return processImageSetFunction(input, start, end);
  2164. }
  2165. if (isModules) {
  2166. if (
  2167. this.options.function &&
  2168. isLocalMode() &&
  2169. isDashedIdentifier(name)
  2170. ) {
  2171. return processDashedIdent(input, start, end);
  2172. }
  2173. const type =
  2174. name === "local" ? 1 : name === "global" ? 2 : undefined;
  2175. if (type && !isNextRulePrelude) {
  2176. return processLocalOrGlobalFunction(input, type, start, end);
  2177. }
  2178. }
  2179. }
  2180. }
  2181. return end;
  2182. },
  2183. leftSquareBracket: (input, start, end) => {
  2184. if (isNextRulePrelude && isLocalMode()) {
  2185. return processAttributeSelector(input, start, end);
  2186. }
  2187. return end;
  2188. },
  2189. leftParenthesis: (input, start, end) => {
  2190. balanced.push(["(", start, end]);
  2191. return end;
  2192. },
  2193. rightParenthesis: (input, start, end) => {
  2194. const popped = balanced.pop();
  2195. if (isModules && popped) {
  2196. const isLocal = popped[0] === ":local";
  2197. const isGlobal = popped[0] === ":global";
  2198. if (isLocal || isGlobal) {
  2199. modeData = balanced[balanced.length - 1]
  2200. ? balanced[balanced.length - 1][0] === ":local"
  2201. ? LOCAL_MODE
  2202. : balanced[balanced.length - 1][0] === ":global"
  2203. ? GLOBAL_MODE
  2204. : undefined
  2205. : undefined;
  2206. if (popped[3] && isLocal) {
  2207. while (walkCssTokens.isWhiteSpace(input.charCodeAt(start - 1))) {
  2208. start -= 1;
  2209. }
  2210. }
  2211. const dep = new ConstDependency("", [start, end]);
  2212. module.addPresentationalDependency(dep);
  2213. } else if (isNextRulePrelude) {
  2214. modeData = undefined;
  2215. }
  2216. }
  2217. return end;
  2218. },
  2219. comma: (input, start, end) => {
  2220. if (isModules) {
  2221. const popped = balanced.pop();
  2222. if (!popped) {
  2223. // Reset stack for `:global .class :local .class-other` selector after
  2224. modeData = undefined;
  2225. }
  2226. }
  2227. lastTokenEndForComments = start;
  2228. return end;
  2229. }
  2230. });
  2231. /** @type {BuildInfo} */
  2232. (module.buildInfo).strict = true;
  2233. const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
  2234. buildMeta.exportsType = this.options.namedExports ? "namespace" : "default";
  2235. buildMeta.defaultObject = this.options.namedExports
  2236. ? false
  2237. : "redirect-warn";
  2238. buildMeta.exportType = this.options.exportType;
  2239. if (!buildMeta.exportType) {
  2240. // Inherit exportType from parent module to ensure consistency.
  2241. // When a CSS file is imported with syntax like `import "./basic.css" with { type: "css" }`,
  2242. // the parent module's exportType is set to "css-style-sheet".
  2243. // Child modules imported via @import should inherit this exportType
  2244. // instead of using the default "link", ensuring that the entire
  2245. // import chain uses the same export format.
  2246. const parent = state.compilation.moduleGraph.getIssuer(module);
  2247. if (parent instanceof CssModule) {
  2248. buildMeta.exportType = /** @type {BuildMeta} */ (
  2249. parent.buildMeta
  2250. ).exportType;
  2251. }
  2252. }
  2253. if (!buildMeta.exportType) {
  2254. buildMeta.exportType = "link";
  2255. }
  2256. // TODO this.namedExports?
  2257. if (
  2258. buildMeta.exportType === "text" ||
  2259. buildMeta.exportType === "css-style-sheet"
  2260. ) {
  2261. module.addDependency(new StaticExportsDependency(["default"], true));
  2262. } else {
  2263. module.addDependency(new StaticExportsDependency([], true));
  2264. }
  2265. return state;
  2266. }
  2267. /**
  2268. * @param {Range} range range
  2269. * @returns {Comment[]} comments in the range
  2270. */
  2271. getComments(range) {
  2272. if (!this.comments) return [];
  2273. const [rangeStart, rangeEnd] = range;
  2274. /**
  2275. * @param {Comment} comment comment
  2276. * @param {number} needle needle
  2277. * @returns {number} compared
  2278. */
  2279. const compare = (comment, needle) =>
  2280. /** @type {Range} */ (comment.range)[0] - needle;
  2281. const comments = /** @type {Comment[]} */ (this.comments);
  2282. let idx = binarySearchBounds.ge(comments, rangeStart, compare);
  2283. /** @type {Comment[]} */
  2284. const commentsInRange = [];
  2285. while (
  2286. comments[idx] &&
  2287. /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
  2288. ) {
  2289. commentsInRange.push(comments[idx]);
  2290. idx++;
  2291. }
  2292. return commentsInRange;
  2293. }
  2294. /**
  2295. * @param {Range} range range of the comment
  2296. * @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
  2297. */
  2298. parseCommentOptions(range) {
  2299. const comments = this.getComments(range);
  2300. if (comments.length === 0) {
  2301. return EMPTY_COMMENT_OPTIONS;
  2302. }
  2303. /** @type {Record<string, EXPECTED_ANY>} */
  2304. const options = {};
  2305. /** @type {(Error & { comment: Comment })[]} */
  2306. const errors = [];
  2307. for (const comment of comments) {
  2308. const { value } = comment;
  2309. if (value && webpackCommentRegExp.test(value)) {
  2310. // try compile only if webpack options comment is present
  2311. try {
  2312. for (let [key, val] of Object.entries(
  2313. vm.runInContext(
  2314. `(function(){return {${value}};})()`,
  2315. this.magicCommentContext
  2316. )
  2317. )) {
  2318. if (typeof val === "object" && val !== null) {
  2319. val =
  2320. val.constructor.name === "RegExp"
  2321. ? new RegExp(val)
  2322. : JSON.parse(JSON.stringify(val));
  2323. }
  2324. options[key] = val;
  2325. }
  2326. } catch (err) {
  2327. const newErr = new Error(String(/** @type {Error} */ (err).message));
  2328. newErr.stack = String(/** @type {Error} */ (err).stack);
  2329. Object.assign(newErr, { comment });
  2330. errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
  2331. }
  2332. }
  2333. }
  2334. return { options, errors };
  2335. }
  2336. }
  2337. module.exports = CssParser;
  2338. module.exports.escapeIdentifier = escapeIdentifier;
  2339. module.exports.unescapeIdentifier = unescapeIdentifier;