CssParser.js 69 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618
  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-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g;
  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 = /\.module(s)?\.[^.]+$/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. let match;
  68. // Use a while loop with exec() to find all matches
  69. while ((match = regexp.exec(str)) !== null) {
  70. result.push(match);
  71. }
  72. // Return an array to be easily iterable (note: a true spec-compliant polyfill
  73. // returns an iterator object, but an array spread often suffices for basic use)
  74. return result;
  75. };
  76. /**
  77. * @param {string} str url string
  78. * @param {boolean} isString is url wrapped in quotes
  79. * @returns {string} normalized url
  80. */
  81. const normalizeUrl = (str, isString) => {
  82. // Remove extra spaces and newlines:
  83. // `url("im\
  84. // g.png")`
  85. if (isString) {
  86. str = str.replace(STRING_MULTILINE, "");
  87. }
  88. str = str
  89. // Remove unnecessary spaces from `url(" img.png ")`
  90. .replace(TRIM_WHITE_SPACES, "")
  91. // Unescape
  92. .replace(UNESCAPE, (match) => {
  93. if (match.length > 2) {
  94. return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
  95. }
  96. return match[1];
  97. });
  98. if (/^data:/i.test(str)) {
  99. return str;
  100. }
  101. if (str.includes("%")) {
  102. // Convert `url('%2E/img.png')` -> `url('./img.png')`
  103. try {
  104. str = decodeURIComponent(str);
  105. } catch (_err) {
  106. // Ignore
  107. }
  108. }
  109. return str;
  110. };
  111. // eslint-disable-next-line no-useless-escape
  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. let value;
  125. // eslint-disable-next-line no-control-regex
  126. if (/[\t\n\f\r\u000B]/.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. let layer;
  738. if (tokens[1]) {
  739. layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim();
  740. }
  741. let supports;
  742. if (tokens[2]) {
  743. supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim();
  744. }
  745. const last = tokens[2] || tokens[1] || tokens[0];
  746. const mediaStart = walkCssTokens.eatWhitespaceAndComments(
  747. input,
  748. last[1]
  749. )[0];
  750. let media;
  751. if (mediaStart !== semi - 1) {
  752. media = input.slice(mediaStart, semi - 1).trim();
  753. }
  754. const { line: sl, column: sc } = locConverter.get(start);
  755. const { line: el, column: ec } = locConverter.get(newline);
  756. const dep = new CssImportDependency(
  757. url,
  758. [start, newline],
  759. mode === "local" || mode === "global" ? mode : undefined,
  760. layer,
  761. supports && supports.length > 0 ? supports : undefined,
  762. media && media.length > 0 ? media : undefined
  763. );
  764. dep.setLoc(sl, sc, el, ec);
  765. module.addDependency(dep);
  766. return newline;
  767. };
  768. /**
  769. * @param {string} input input
  770. * @param {number} end end position
  771. * @param {string} name the name of function
  772. * @returns {number} position after handling
  773. */
  774. const processURLFunction = (input, end, name) => {
  775. const string = walkCssTokens.eatString(input, end);
  776. if (!string) return end;
  777. const { options, errors: commentErrors } = this.parseCommentOptions([
  778. lastTokenEndForComments,
  779. end
  780. ]);
  781. if (commentErrors) {
  782. for (const e of commentErrors) {
  783. const { comment } = e;
  784. state.module.addWarning(
  785. new CommentCompilationWarning(
  786. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  787. comment.loc
  788. )
  789. );
  790. }
  791. }
  792. if (options && options.webpackIgnore !== undefined) {
  793. if (typeof options.webpackIgnore !== "boolean") {
  794. const { line: sl, column: sc } = locConverter.get(string[0]);
  795. const { line: el, column: ec } = locConverter.get(string[1]);
  796. state.module.addWarning(
  797. new UnsupportedFeatureWarning(
  798. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  799. {
  800. start: { line: sl, column: sc },
  801. end: { line: el, column: ec }
  802. }
  803. )
  804. );
  805. } else if (options.webpackIgnore) {
  806. return end;
  807. }
  808. }
  809. const value = normalizeUrl(
  810. input.slice(string[0] + 1, string[1] - 1),
  811. true
  812. );
  813. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  814. if (value.length === 0) return end;
  815. const isUrl = name === "url" || name === "src";
  816. const dep = new CssUrlDependency(
  817. value,
  818. [string[0], string[1]],
  819. isUrl ? "string" : "url"
  820. );
  821. const { line: sl, column: sc } = locConverter.get(string[0]);
  822. const { line: el, column: ec } = locConverter.get(string[1]);
  823. dep.setLoc(sl, sc, el, ec);
  824. module.addDependency(dep);
  825. module.addCodeGenerationDependency(dep);
  826. return string[1];
  827. };
  828. /**
  829. * @param {string} input input
  830. * @param {number} start start position
  831. * @param {number} end end position
  832. * @param {number} contentStart start position
  833. * @param {number} contentEnd end position
  834. * @returns {number} position after handling
  835. */
  836. const processOldURLFunction = (
  837. input,
  838. start,
  839. end,
  840. contentStart,
  841. contentEnd
  842. ) => {
  843. const { options, errors: commentErrors } = this.parseCommentOptions([
  844. lastTokenEndForComments,
  845. end
  846. ]);
  847. if (commentErrors) {
  848. for (const e of commentErrors) {
  849. const { comment } = e;
  850. state.module.addWarning(
  851. new CommentCompilationWarning(
  852. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  853. comment.loc
  854. )
  855. );
  856. }
  857. }
  858. if (options && options.webpackIgnore !== undefined) {
  859. if (typeof options.webpackIgnore !== "boolean") {
  860. const { line: sl, column: sc } = locConverter.get(
  861. lastTokenEndForComments
  862. );
  863. const { line: el, column: ec } = locConverter.get(end);
  864. state.module.addWarning(
  865. new UnsupportedFeatureWarning(
  866. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  867. {
  868. start: { line: sl, column: sc },
  869. end: { line: el, column: ec }
  870. }
  871. )
  872. );
  873. } else if (options.webpackIgnore) {
  874. return end;
  875. }
  876. }
  877. const value = normalizeUrl(input.slice(contentStart, contentEnd), false);
  878. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  879. if (value.length === 0) return end;
  880. const dep = new CssUrlDependency(value, [start, end], "url");
  881. const { line: sl, column: sc } = locConverter.get(start);
  882. const { line: el, column: ec } = locConverter.get(end);
  883. dep.setLoc(sl, sc, el, ec);
  884. module.addDependency(dep);
  885. module.addCodeGenerationDependency(dep);
  886. return end;
  887. };
  888. /**
  889. * @param {string} input input
  890. * @param {number} start start position
  891. * @param {number} end end position
  892. * @returns {number} position after handling
  893. */
  894. const processImageSetFunction = (input, start, end) => {
  895. lastTokenEndForComments = end;
  896. const values = walkCssTokens.eatImageSetStrings(input, end, {
  897. comment
  898. });
  899. if (values.length === 0) return end;
  900. for (const [index, string] of values.entries()) {
  901. const value = normalizeUrl(
  902. input.slice(string[0] + 1, string[1] - 1),
  903. true
  904. );
  905. if (value.length === 0) return end;
  906. const { options, errors: commentErrors } = this.parseCommentOptions([
  907. index === 0 ? start : values[index - 1][1],
  908. string[1]
  909. ]);
  910. if (commentErrors) {
  911. for (const e of commentErrors) {
  912. const { comment } = e;
  913. state.module.addWarning(
  914. new CommentCompilationWarning(
  915. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  916. comment.loc
  917. )
  918. );
  919. }
  920. }
  921. if (options && options.webpackIgnore !== undefined) {
  922. if (typeof options.webpackIgnore !== "boolean") {
  923. const { line: sl, column: sc } = locConverter.get(string[0]);
  924. const { line: el, column: ec } = locConverter.get(string[1]);
  925. state.module.addWarning(
  926. new UnsupportedFeatureWarning(
  927. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  928. {
  929. start: { line: sl, column: sc },
  930. end: { line: el, column: ec }
  931. }
  932. )
  933. );
  934. } else if (options.webpackIgnore) {
  935. continue;
  936. }
  937. }
  938. const dep = new CssUrlDependency(value, [string[0], string[1]], "url");
  939. const { line: sl, column: sc } = locConverter.get(string[0]);
  940. const { line: el, column: ec } = locConverter.get(string[1]);
  941. dep.setLoc(sl, sc, el, ec);
  942. module.addDependency(dep);
  943. module.addCodeGenerationDependency(dep);
  944. }
  945. // Can contain `url()` inside, so let's return end to allow parse them
  946. return end;
  947. };
  948. // CSS modules stuff
  949. /**
  950. * @param {string} value value to resolve
  951. * @returns {string | [string, string, boolean]} resolved reexport
  952. */
  953. const getReexport = (value) => {
  954. const reexport = icssDefinitions.get(value);
  955. if (reexport) {
  956. if (reexport.isReference) {
  957. return [value, reexport.value, true];
  958. }
  959. return [value, reexport.value, false];
  960. }
  961. return value;
  962. };
  963. /**
  964. * @param {0 | 1} type import or export
  965. * @param {string} input input
  966. * @param {number} pos start position
  967. * @returns {number} position after parse
  968. */
  969. const processImportOrExport = (type, input, pos) => {
  970. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  971. /** @type {string | undefined} */
  972. let request;
  973. if (type === 0) {
  974. let cc = input.charCodeAt(pos);
  975. if (cc !== CC_LEFT_PARENTHESIS) {
  976. this._emitWarning(
  977. state,
  978. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`,
  979. locConverter,
  980. pos,
  981. pos
  982. );
  983. return pos;
  984. }
  985. pos++;
  986. const stringStart = pos;
  987. const str = walkCssTokens.eatString(input, pos);
  988. if (!str) {
  989. this._emitWarning(
  990. state,
  991. `Unexpected '${input[pos]}' at ${pos} during parsing of '${type ? ":import" : ":export"}' (expected string)`,
  992. locConverter,
  993. stringStart,
  994. pos
  995. );
  996. return pos;
  997. }
  998. request = input.slice(str[0] + 1, str[1] - 1);
  999. pos = str[1];
  1000. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1001. cc = input.charCodeAt(pos);
  1002. if (cc !== CC_RIGHT_PARENTHESIS) {
  1003. this._emitWarning(
  1004. state,
  1005. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`,
  1006. locConverter,
  1007. pos,
  1008. pos
  1009. );
  1010. return pos;
  1011. }
  1012. pos++;
  1013. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1014. }
  1015. /**
  1016. * @param {string} name name
  1017. * @param {string} value value
  1018. * @param {number} start start of position
  1019. * @param {number} end end of position
  1020. */
  1021. const createDep = (name, value, start, end) => {
  1022. if (type === 0) {
  1023. const dep = new CssIcssImportDependency(
  1024. /** @type {string} */
  1025. (request),
  1026. [0, 0],
  1027. /** @type {"local" | "global"} */
  1028. (mode),
  1029. value
  1030. );
  1031. const { line: sl, column: sc } = locConverter.get(start);
  1032. const { line: el, column: ec } = locConverter.get(end);
  1033. dep.setLoc(sl, sc, el, ec);
  1034. module.addDependency(dep);
  1035. icssDefinitions.set(name, { value, isReference: true });
  1036. } else if (type === 1) {
  1037. const dep = new CssIcssExportDependency(name, getReexport(value));
  1038. const { line: sl, column: sc } = locConverter.get(start);
  1039. const { line: el, column: ec } = locConverter.get(end);
  1040. dep.setLoc(sl, sc, el, ec);
  1041. module.addDependency(dep);
  1042. }
  1043. };
  1044. let needTerminate = false;
  1045. let balanced = 0;
  1046. /** @type {undefined | 0 | 1 | 2} */
  1047. let scope;
  1048. /** @typedef {[number, number]} Name */
  1049. /** @type {Name | undefined} */
  1050. let name;
  1051. /** @type {number | undefined} */
  1052. let value;
  1053. /** @type {CssTokenCallbacks} */
  1054. const callbacks = {
  1055. leftCurlyBracket: (_input, _start, end) => {
  1056. balanced++;
  1057. if (scope === undefined) {
  1058. scope = 0;
  1059. }
  1060. return end;
  1061. },
  1062. rightCurlyBracket: (_input, _start, end) => {
  1063. balanced--;
  1064. if (scope === 2) {
  1065. const [nameStart, nameEnd] = /** @type {Name} */ (name);
  1066. createDep(
  1067. input.slice(nameStart, nameEnd),
  1068. input.slice(value, end - 1).trim(),
  1069. nameEnd,
  1070. end - 1
  1071. );
  1072. scope = 0;
  1073. }
  1074. if (balanced === 0 && scope === 0) {
  1075. needTerminate = true;
  1076. }
  1077. return end;
  1078. },
  1079. identifier: (_input, start, end) => {
  1080. if (scope === 0) {
  1081. name = [start, end];
  1082. scope = 1;
  1083. }
  1084. return end;
  1085. },
  1086. colon: (_input, _start, end) => {
  1087. if (scope === 1) {
  1088. scope = 2;
  1089. value = walkCssTokens.eatWhitespace(input, end);
  1090. return value;
  1091. }
  1092. return end;
  1093. },
  1094. semicolon: (input, _start, end) => {
  1095. if (scope === 2) {
  1096. const [nameStart, nameEnd] = /** @type {Name} */ (name);
  1097. createDep(
  1098. input.slice(nameStart, nameEnd),
  1099. input.slice(value, end - 1),
  1100. nameEnd,
  1101. end - 1
  1102. );
  1103. scope = 0;
  1104. }
  1105. return end;
  1106. },
  1107. needTerminate: () => needTerminate
  1108. };
  1109. pos = walkCssTokens(input, pos, callbacks);
  1110. pos = walkCssTokens.eatWhiteLine(input, pos);
  1111. return pos;
  1112. };
  1113. /**
  1114. * @param {string} input input
  1115. * @param {number} start name start position
  1116. * @param {number} end name end position
  1117. * @returns {number} position after handling
  1118. */
  1119. const processAtValue = (input, start, end) => {
  1120. const semi = eatUntilSemi(input, end);
  1121. const atRuleEnd = semi + 1;
  1122. const params = input.slice(end, semi);
  1123. let [alias, request] = params.split(/\s*from\s*/);
  1124. if (request) {
  1125. const aliases = alias
  1126. .replace(CSS_COMMENT, " ")
  1127. .trim()
  1128. .replace(/^\(\s*|\s*\)$/g, "")
  1129. .split(/\s*,\s*/);
  1130. request = request.replace(CSS_COMMENT, "").trim();
  1131. const isExplicitImport = request[0] === "'" || request[0] === '"';
  1132. if (isExplicitImport) {
  1133. request = request.slice(1, -1);
  1134. }
  1135. for (const alias of aliases) {
  1136. const [name, aliasName] = alias.split(/\s+as\s+/);
  1137. {
  1138. const reexport = icssDefinitions.get(request);
  1139. if (reexport) {
  1140. request = reexport.value.slice(1, -1);
  1141. }
  1142. const dep = new CssIcssImportDependency(
  1143. request,
  1144. [0, 0],
  1145. /** @type {"local" | "global"} */
  1146. (mode),
  1147. name
  1148. );
  1149. const { line: sl, column: sc } = locConverter.get(start);
  1150. const { line: el, column: ec } = locConverter.get(end);
  1151. dep.setLoc(sl, sc, el, ec);
  1152. module.addDependency(dep);
  1153. icssDefinitions.set(aliasName || name, {
  1154. value: name,
  1155. isReference: true
  1156. });
  1157. }
  1158. {
  1159. const dep = new CssIcssExportDependency(
  1160. aliasName || name,
  1161. getReexport(aliasName || name),
  1162. undefined,
  1163. false,
  1164. CssIcssExportDependency.EXPORT_MODE.REPLACE
  1165. );
  1166. const { line: sl, column: sc } = locConverter.get(start);
  1167. const { line: el, column: ec } = locConverter.get(end);
  1168. dep.setLoc(sl, sc, el, ec);
  1169. module.addDependency(dep);
  1170. }
  1171. }
  1172. } else {
  1173. const ident = walkCssTokens.eatIdentSequence(alias, 0);
  1174. if (!ident) {
  1175. this._emitWarning(
  1176. state,
  1177. `Broken '@value' at-rule: ${input.slice(start, atRuleEnd)}'`,
  1178. locConverter,
  1179. start,
  1180. atRuleEnd
  1181. );
  1182. const dep = new ConstDependency("", [start, atRuleEnd]);
  1183. module.addPresentationalDependency(dep);
  1184. return atRuleEnd;
  1185. }
  1186. const pos = walkCssTokens.eatWhitespaceAndComments(alias, ident[1])[0];
  1187. const name = alias.slice(ident[0], ident[1]);
  1188. let value =
  1189. alias.charCodeAt(pos) === CC_COLON
  1190. ? alias.slice(pos + 1)
  1191. : alias.slice(ident[1]);
  1192. if (value && !/^\s+$/.test(value.replace(CSS_COMMENT, ""))) {
  1193. value = value.trim();
  1194. }
  1195. if (icssDefinitions.has(value)) {
  1196. const def =
  1197. /** @type {IcssDefinition} */
  1198. (icssDefinitions.get(value));
  1199. value = def.value;
  1200. }
  1201. icssDefinitions.set(name, { value });
  1202. const dep = new CssIcssExportDependency(name, value);
  1203. const { line: sl, column: sc } = locConverter.get(start);
  1204. const { line: el, column: ec } = locConverter.get(end);
  1205. dep.setLoc(sl, sc, el, ec);
  1206. module.addDependency(dep);
  1207. }
  1208. const dep = new ConstDependency("", [start, atRuleEnd]);
  1209. module.addPresentationalDependency(dep);
  1210. return atRuleEnd;
  1211. };
  1212. /**
  1213. * @param {string} name ICSS symbol name
  1214. * @param {number} start start position
  1215. * @param {number} end end position
  1216. * @returns {number} position after handling
  1217. */
  1218. const processICSSSymbol = (name, start, end) => {
  1219. const { value, isReference } =
  1220. /** @type {IcssDefinition} */
  1221. (icssDefinitions.get(name));
  1222. const { line: sl, column: sc } = locConverter.get(start);
  1223. const { line: el, column: ec } = locConverter.get(end);
  1224. const dep = new CssIcssSymbolDependency(
  1225. name,
  1226. value,
  1227. [start, end],
  1228. isReference
  1229. );
  1230. dep.setLoc(sl, sc, el, ec);
  1231. module.addDependency(dep);
  1232. return end;
  1233. };
  1234. /**
  1235. * @param {string} input input
  1236. * @param {1 | 2} type type of function
  1237. * @param {number} start start position
  1238. * @param {number} end end position
  1239. * @returns {number} position after handling
  1240. */
  1241. const processLocalOrGlobalFunction = (input, type, start, end) => {
  1242. // Replace `local(`/` or `global(` (handle legacy `:local(` or `:global(` too)
  1243. {
  1244. const isColon = input.charCodeAt(start - 1) === CC_COLON;
  1245. const dep = new ConstDependency("", [isColon ? start - 1 : start, end]);
  1246. module.addPresentationalDependency(dep);
  1247. }
  1248. end = walkCssTokens.consumeUntil(
  1249. input,
  1250. start,
  1251. {
  1252. identifier(input, start, end) {
  1253. if (type === 1) {
  1254. let identifier = unescapeIdentifier(input.slice(start, end));
  1255. const { line: sl, column: sc } = locConverter.get(start);
  1256. const { line: el, column: ec } = locConverter.get(end);
  1257. const isDashedIdent = isDashedIdentifier(identifier);
  1258. if (isDashedIdent) {
  1259. identifier = identifier.slice(2);
  1260. }
  1261. const dep = new CssIcssExportDependency(
  1262. identifier,
  1263. getReexport(identifier),
  1264. [start, end],
  1265. true,
  1266. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1267. isDashedIdent
  1268. ? CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
  1269. : CssIcssExportDependency.EXPORT_TYPE.NORMAL
  1270. );
  1271. dep.setLoc(sl, sc, el, ec);
  1272. module.addDependency(dep);
  1273. }
  1274. return end;
  1275. }
  1276. },
  1277. {},
  1278. { onlyTopLevel: true, functionValue: true }
  1279. );
  1280. {
  1281. // Replace the last `)`
  1282. const dep = new ConstDependency("", [end, end + 1]);
  1283. module.addPresentationalDependency(dep);
  1284. }
  1285. return end;
  1286. };
  1287. /**
  1288. * @param {string} input input
  1289. * @param {number} end name end position
  1290. * @param {{ string?: boolean, identifier?: boolean | RegExp }} options types which allowed to handle
  1291. * @returns {number} position after handling
  1292. */
  1293. const processLocalAtRule = (input, end, options) => {
  1294. let found = false;
  1295. return walkCssTokens.consumeUntil(
  1296. input,
  1297. end,
  1298. {
  1299. string(_input, start, end) {
  1300. if (!found && options.string) {
  1301. const value = unescapeIdentifier(input.slice(start + 1, end - 1));
  1302. const { line: sl, column: sc } = locConverter.get(start);
  1303. const { line: el, column: ec } = locConverter.get(end);
  1304. const dep = new CssIcssExportDependency(
  1305. value,
  1306. value,
  1307. [start, end],
  1308. true,
  1309. CssIcssExportDependency.EXPORT_MODE.ONCE
  1310. );
  1311. dep.setLoc(sl, sc, el, ec);
  1312. module.addDependency(dep);
  1313. found = true;
  1314. }
  1315. return end;
  1316. },
  1317. identifier(input, start, end) {
  1318. if (!found) {
  1319. const value = input.slice(start, end);
  1320. if (options.identifier) {
  1321. const identifier = unescapeIdentifier(value);
  1322. if (
  1323. options.identifier instanceof RegExp &&
  1324. options.identifier.test(identifier)
  1325. ) {
  1326. return end;
  1327. }
  1328. const { line: sl, column: sc } = locConverter.get(start);
  1329. const { line: el, column: ec } = locConverter.get(end);
  1330. const dep = new CssIcssExportDependency(
  1331. identifier,
  1332. getReexport(identifier),
  1333. [start, end],
  1334. true,
  1335. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1336. CssIcssExportDependency.EXPORT_TYPE.NORMAL
  1337. );
  1338. dep.setLoc(sl, sc, el, ec);
  1339. module.addDependency(dep);
  1340. found = true;
  1341. }
  1342. }
  1343. return end;
  1344. }
  1345. },
  1346. {
  1347. function: (input, start, end) => {
  1348. // No need to handle `:` (COLON), because it's always a function
  1349. const name = input
  1350. .slice(start, end - 1)
  1351. .replace(/\\/g, "")
  1352. .toLowerCase();
  1353. const type =
  1354. name === "local" ? 1 : name === "global" ? 2 : undefined;
  1355. if (!found && type) {
  1356. found = true;
  1357. return processLocalOrGlobalFunction(input, type, start, end);
  1358. }
  1359. if (
  1360. this.options.dashedIdents &&
  1361. isLocalMode() &&
  1362. (name === "var" || name === "style")
  1363. ) {
  1364. return processDashedIdent(input, end, end);
  1365. }
  1366. return end;
  1367. }
  1368. },
  1369. { onlyTopLevel: true, atRulePrelude: true }
  1370. );
  1371. };
  1372. /**
  1373. * @param {string} input input
  1374. * @param {number} start start position
  1375. * @param {number} end end position
  1376. * @returns {number} position after handling
  1377. */
  1378. const processDashedIdent = (input, start, end) => {
  1379. const customIdent = walkCssTokens.eatIdentSequence(input, start);
  1380. if (!customIdent) return end;
  1381. const identifier = unescapeIdentifier(
  1382. input.slice(customIdent[0] + 2, customIdent[1])
  1383. );
  1384. const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments(
  1385. input,
  1386. customIdent[1]
  1387. )[0];
  1388. if (
  1389. input.charCodeAt(afterCustomIdent) === CC_LOWER_F ||
  1390. input.charCodeAt(afterCustomIdent) === CC_UPPER_F
  1391. ) {
  1392. const fromWord = walkCssTokens.eatIdentSequence(
  1393. input,
  1394. afterCustomIdent
  1395. );
  1396. if (
  1397. !fromWord ||
  1398. input.slice(fromWord[0], fromWord[1]).toLowerCase() !== "from"
  1399. ) {
  1400. return end;
  1401. }
  1402. const from = walkCssTokens.eatIdentSequenceOrString(
  1403. input,
  1404. walkCssTokens.eatWhitespaceAndComments(input, fromWord[1])[0]
  1405. );
  1406. if (!from) {
  1407. return end;
  1408. }
  1409. const path = input.slice(from[0], from[1]);
  1410. if (from[2] === true && path === "global") {
  1411. const dep = new ConstDependency("", [customIdent[1], from[1]]);
  1412. module.addPresentationalDependency(dep);
  1413. return end;
  1414. } else if (from[2] === false) {
  1415. const { line: sl, column: sc } = locConverter.get(customIdent[0]);
  1416. const { line: el, column: ec } = locConverter.get(from[1] - 1);
  1417. const dep = new CssIcssImportDependency(
  1418. path.slice(1, -1),
  1419. [customIdent[0], from[1] - 1],
  1420. /** @type {"local" | "global"} */
  1421. (mode),
  1422. identifier,
  1423. identifier,
  1424. CssIcssExportDependency.EXPORT_MODE.NONE,
  1425. CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
  1426. );
  1427. dep.setLoc(sl, sc, el, ec);
  1428. module.addDependency(dep);
  1429. {
  1430. const dep = new ConstDependency("", [fromWord[0], from[1]]);
  1431. module.addPresentationalDependency(dep);
  1432. return end;
  1433. }
  1434. }
  1435. } else {
  1436. const { line: sl, column: sc } = locConverter.get(customIdent[0]);
  1437. const { line: el, column: ec } = locConverter.get(customIdent[1]);
  1438. const dep = new CssIcssExportDependency(
  1439. identifier,
  1440. getReexport(identifier),
  1441. [customIdent[0], customIdent[1]],
  1442. true,
  1443. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1444. CssIcssExportDependency.EXPORT_TYPE.CUSTOM_VARIABLE
  1445. );
  1446. dep.setLoc(sl, sc, el, ec);
  1447. module.addDependency(dep);
  1448. return end;
  1449. }
  1450. return end;
  1451. };
  1452. /**
  1453. * @param {string} input input
  1454. * @param {number} pos name start position
  1455. * @param {number} end name end position
  1456. * @returns {number} position after handling
  1457. */
  1458. const processLocalDeclaration = (input, pos, end) => {
  1459. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1460. const identifier = walkCssTokens.eatIdentSequence(input, pos);
  1461. if (!identifier) {
  1462. return end;
  1463. }
  1464. const propertyNameStart = identifier[0];
  1465. pos = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];
  1466. if (input.charCodeAt(pos) !== CC_COLON) {
  1467. return end;
  1468. }
  1469. pos += 1;
  1470. // Remove prefix and lowercase
  1471. const propertyName = input
  1472. .slice(identifier[0], identifier[1])
  1473. .replace(/^(-\w+-)/, "")
  1474. .toLowerCase();
  1475. if (isLocalMode() && knownProperties.has(propertyName)) {
  1476. /** @type {[number, number, boolean?][]} */
  1477. const values = [];
  1478. /** @type {Record<string, number>} */
  1479. let parsedKeywords = Object.create(null);
  1480. const isGridProperty = Boolean(propertyName.startsWith("grid"));
  1481. const isGridTemplate = isGridProperty
  1482. ? Boolean(
  1483. propertyName === "grid" ||
  1484. propertyName === "grid-template" ||
  1485. propertyName === "grid-template-columns" ||
  1486. propertyName === "grid-template-rows"
  1487. )
  1488. : false;
  1489. const end = walkCssTokens.consumeUntil(
  1490. input,
  1491. pos,
  1492. {
  1493. leftSquareBracket(input, start, end) {
  1494. let i = end;
  1495. while (true) {
  1496. i = walkCssTokens.eatWhitespaceAndComments(input, i)[0];
  1497. const name = walkCssTokens.eatIdentSequence(input, i);
  1498. if (!name) {
  1499. break;
  1500. }
  1501. values.push(name);
  1502. i = name[1];
  1503. }
  1504. return end;
  1505. },
  1506. string(_input, start, end) {
  1507. if (
  1508. propertyName === "animation" ||
  1509. propertyName === "animation-name"
  1510. ) {
  1511. values.push([start, end, true]);
  1512. }
  1513. if (
  1514. propertyName === "grid" ||
  1515. propertyName === "grid-template" ||
  1516. propertyName === "grid-template-areas"
  1517. ) {
  1518. const areas = unescapeIdentifier(
  1519. input.slice(start + 1, end - 1)
  1520. );
  1521. const matches = matchAll(/\b\w+\b/g, areas);
  1522. for (const match of matches) {
  1523. const areaStart = start + 1 + match.index;
  1524. values.push([areaStart, areaStart + match[0].length, false]);
  1525. }
  1526. }
  1527. return end;
  1528. },
  1529. identifier(input, start, end) {
  1530. if (isGridTemplate) {
  1531. return end;
  1532. }
  1533. const identifier = input.slice(start, end);
  1534. const keyword = identifier.toLowerCase();
  1535. parsedKeywords[keyword] =
  1536. typeof parsedKeywords[keyword] !== "undefined"
  1537. ? parsedKeywords[keyword] + 1
  1538. : 0;
  1539. const keywords =
  1540. /** @type {Record<string, number>} */
  1541. (knownProperties.get(propertyName));
  1542. if (
  1543. keywords[keyword] &&
  1544. parsedKeywords[keyword] < keywords[keyword]
  1545. ) {
  1546. return end;
  1547. }
  1548. values.push([start, end]);
  1549. return end;
  1550. },
  1551. comma(_input, _start, end) {
  1552. parsedKeywords = {};
  1553. return end;
  1554. }
  1555. },
  1556. {
  1557. function: (input, start, end) => {
  1558. const name = input
  1559. .slice(start, end - 1)
  1560. .replace(/\\/g, "")
  1561. .toLowerCase();
  1562. const type =
  1563. name === "local" ? 1 : name === "global" ? 2 : undefined;
  1564. if (type) {
  1565. return processLocalOrGlobalFunction(input, type, start, end);
  1566. }
  1567. if (
  1568. this.options.dashedIdents &&
  1569. isLocalMode() &&
  1570. name === "var"
  1571. ) {
  1572. return processDashedIdent(input, end, end);
  1573. }
  1574. if (this.options.url) {
  1575. if (name === "src" || name === "url") {
  1576. return processURLFunction(input, end, name);
  1577. } else if (IMAGE_SET_FUNCTION.test(name)) {
  1578. return processImageSetFunction(input, start, end);
  1579. }
  1580. }
  1581. return end;
  1582. }
  1583. },
  1584. {
  1585. onlyTopLevel: !isGridTemplate,
  1586. declarationValue: true
  1587. }
  1588. );
  1589. if (values.length > 0) {
  1590. for (const value of values) {
  1591. const { line: sl, column: sc } = locConverter.get(value[0]);
  1592. const { line: el, column: ec } = locConverter.get(value[1]);
  1593. const [start, end, isString] = value;
  1594. const name = unescapeIdentifier(
  1595. isString
  1596. ? input.slice(start + 1, end - 1)
  1597. : input.slice(start, end)
  1598. );
  1599. const dep = new CssIcssExportDependency(
  1600. name,
  1601. getReexport(name),
  1602. [start, end],
  1603. true,
  1604. CssIcssExportDependency.EXPORT_MODE.ONCE,
  1605. isGridProperty
  1606. ? CssIcssExportDependency.EXPORT_TYPE.GRID_CUSTOM_IDENTIFIER
  1607. : CssIcssExportDependency.EXPORT_TYPE.NORMAL
  1608. );
  1609. dep.setLoc(sl, sc, el, ec);
  1610. module.addDependency(dep);
  1611. }
  1612. }
  1613. return end;
  1614. } else if (COMPOSES_PROPERTY.test(propertyName)) {
  1615. if (lastLocalIdentifiers.length > 1) {
  1616. const end = eatUntilSemi(input, pos);
  1617. this._emitWarning(
  1618. state,
  1619. `Composition is only allowed when selector is single local class name not in "${lastLocalIdentifiers.join('", "')}"`,
  1620. locConverter,
  1621. pos,
  1622. end
  1623. );
  1624. return end;
  1625. }
  1626. if (lastLocalIdentifiers.length !== 1) return pos;
  1627. const lastLocalIdentifier = lastLocalIdentifiers[0];
  1628. let end = pos;
  1629. /** @type {Set<[number, number]>} */
  1630. const classNames = new Set();
  1631. while (true) {
  1632. pos = walkCssTokens.eatWhitespaceAndComments(input, pos)[0];
  1633. let className = walkCssTokens.eatIdentSequence(input, pos);
  1634. const ifFunction =
  1635. className && input.charCodeAt(className[1]) === CC_LEFT_PARENTHESIS;
  1636. let isGlobalFunction = false;
  1637. if (className && ifFunction) {
  1638. const name = input
  1639. .slice(className[0], className[1])
  1640. .replace(/\\/g, "")
  1641. .toLowerCase();
  1642. isGlobalFunction = name === "global";
  1643. pos = walkCssTokens.eatWhitespaceAndComments(
  1644. input,
  1645. className[1] + 1
  1646. )[0];
  1647. className = walkCssTokens.eatIdentSequence(input, pos);
  1648. if (className) {
  1649. pos = walkCssTokens.eatWhitespaceAndComments(
  1650. input,
  1651. className[1]
  1652. )[0];
  1653. pos += 1;
  1654. }
  1655. } else if (className) {
  1656. pos = walkCssTokens.eatWhitespaceAndComments(
  1657. input,
  1658. className[1]
  1659. )[0];
  1660. pos = className[1];
  1661. }
  1662. // True when we have multiple values
  1663. const isComma = input.charCodeAt(pos) === CC_COMMA;
  1664. const isSemicolon = input.charCodeAt(pos) === CC_SEMICOLON;
  1665. const isRightCurly = input.charCodeAt(pos) === CC_RIGHT_CURLY;
  1666. if (isComma || isSemicolon || isRightCurly) {
  1667. if (className) {
  1668. classNames.add(className);
  1669. }
  1670. for (const className of classNames) {
  1671. const [start, end] = className;
  1672. const identifier = unescapeIdentifier(input.slice(start, end));
  1673. const resolvedClassName = getReexport(identifier);
  1674. const dep = new CssIcssExportDependency(
  1675. lastLocalIdentifier,
  1676. resolvedClassName,
  1677. [start, end],
  1678. isGlobalFunction ? false : !Array.isArray(resolvedClassName),
  1679. isGlobalFunction
  1680. ? CssIcssExportDependency.EXPORT_MODE.APPEND
  1681. : CssIcssExportDependency.EXPORT_MODE.SELF_REFERENCE
  1682. );
  1683. const { line: sl, column: sc } = locConverter.get(start);
  1684. const { line: el, column: ec } = locConverter.get(end);
  1685. dep.setLoc(sl, sc, el, ec);
  1686. module.addDependency(dep);
  1687. }
  1688. classNames.clear();
  1689. if (isSemicolon || isRightCurly) {
  1690. end = isSemicolon
  1691. ? walkCssTokens.eatWhitespace(input, pos + 1)
  1692. : pos;
  1693. break;
  1694. }
  1695. pos += 1;
  1696. } else if (
  1697. classNames.size > 0 &&
  1698. className &&
  1699. input.slice(className[0], className[1]).toLowerCase() === "from"
  1700. ) {
  1701. let from = walkCssTokens.eatString(input, pos);
  1702. if (from) {
  1703. const request = input.slice(from[0] + 1, from[1] - 1);
  1704. for (const className of classNames) {
  1705. const [start, end] = className;
  1706. const identifier = unescapeIdentifier(input.slice(start, end));
  1707. const dep = new CssIcssImportDependency(
  1708. request,
  1709. [start, end],
  1710. /** @type {"local" | "global"} */
  1711. (mode),
  1712. identifier,
  1713. /** @type {string} */
  1714. (lastLocalIdentifier),
  1715. CssIcssExportDependency.EXPORT_MODE.APPEND
  1716. );
  1717. const { line: sl, column: sc } = locConverter.get(start);
  1718. const { line: el, column: ec } = locConverter.get(end);
  1719. dep.setLoc(sl, sc, el, ec);
  1720. module.addDependency(dep);
  1721. }
  1722. classNames.clear();
  1723. pos = from[1];
  1724. } else {
  1725. from = walkCssTokens.eatIdentSequence(input, pos);
  1726. if (from && input.slice(from[0], from[1]) === "global") {
  1727. for (const className of classNames) {
  1728. const [start, end] = className;
  1729. const identifier = unescapeIdentifier(
  1730. input.slice(start, end)
  1731. );
  1732. const dep = new CssIcssExportDependency(
  1733. /** @type {string} */
  1734. (lastLocalIdentifier),
  1735. getReexport(identifier),
  1736. [start, end],
  1737. false,
  1738. CssIcssExportDependency.EXPORT_MODE.APPEND
  1739. );
  1740. const { line: sl, column: sc } = locConverter.get(start);
  1741. const { line: el, column: ec } = locConverter.get(end);
  1742. dep.setLoc(sl, sc, el, ec);
  1743. module.addDependency(dep);
  1744. }
  1745. classNames.clear();
  1746. pos = from[1];
  1747. } else {
  1748. const end = eatUntilSemi(input, pos);
  1749. this._emitWarning(
  1750. state,
  1751. "Incorrect composition, expected global keyword or string value",
  1752. locConverter,
  1753. pos,
  1754. end
  1755. );
  1756. return end;
  1757. }
  1758. }
  1759. } else if (className) {
  1760. classNames.add(className);
  1761. } else {
  1762. const end = eatUntilSemi(input, pos);
  1763. this._emitWarning(
  1764. state,
  1765. "Incorrect composition, expected class named",
  1766. locConverter,
  1767. pos,
  1768. end
  1769. );
  1770. return end;
  1771. }
  1772. }
  1773. // Remove `composes` from source code
  1774. const dep = new ConstDependency("", [propertyNameStart, end]);
  1775. module.addPresentationalDependency(dep);
  1776. }
  1777. return pos;
  1778. };
  1779. /**
  1780. * @param {string} input input
  1781. * @param {number} start start position
  1782. * @param {number} end end position
  1783. * @returns {number} position after handling
  1784. */
  1785. const processIdSelector = (input, start, end) => {
  1786. const valueStart = start + 1;
  1787. const name = unescapeIdentifier(input.slice(valueStart, end));
  1788. const dep = new CssIcssExportDependency(
  1789. name,
  1790. getReexport(name),
  1791. [valueStart, end],
  1792. true,
  1793. CssIcssExportDependency.EXPORT_MODE.ONCE
  1794. );
  1795. const { line: sl, column: sc } = locConverter.get(start);
  1796. const { line: el, column: ec } = locConverter.get(end);
  1797. dep.setLoc(sl, sc, el, ec);
  1798. module.addDependency(dep);
  1799. return end;
  1800. };
  1801. /**
  1802. * @param {string} input input
  1803. * @param {number} start start position
  1804. * @param {number} end end position
  1805. * @returns {number} position after handling
  1806. */
  1807. const processClassSelector = (input, start, end) => {
  1808. const ident = walkCssTokens.skipCommentsAndEatIdentSequence(input, end);
  1809. if (!ident) return end;
  1810. const name = unescapeIdentifier(input.slice(ident[0], ident[1]));
  1811. lastLocalIdentifiers.push(name);
  1812. const dep = new CssIcssExportDependency(
  1813. name,
  1814. getReexport(name),
  1815. [ident[0], ident[1]],
  1816. true,
  1817. CssIcssExportDependency.EXPORT_MODE.ONCE
  1818. );
  1819. const { line: sl, column: sc } = locConverter.get(ident[0]);
  1820. const { line: el, column: ec } = locConverter.get(ident[1]);
  1821. dep.setLoc(sl, sc, el, ec);
  1822. module.addDependency(dep);
  1823. return ident[1];
  1824. };
  1825. /**
  1826. * @param {string} input input
  1827. * @param {number} start start position
  1828. * @param {number} end end position
  1829. * @returns {number} position after handling
  1830. */
  1831. const processAttributeSelector = (input, start, end) => {
  1832. end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
  1833. const identifier = walkCssTokens.eatIdentSequence(input, end);
  1834. if (!identifier) return end;
  1835. const name = unescapeIdentifier(
  1836. input.slice(identifier[0], identifier[1])
  1837. );
  1838. if (name.toLowerCase() !== "class") {
  1839. return end;
  1840. }
  1841. end = walkCssTokens.eatWhitespaceAndComments(input, identifier[1])[0];
  1842. const isTilde = input.charCodeAt(end) === CC_TILDE;
  1843. if (
  1844. input.charCodeAt(end) !== CC_EQUAL &&
  1845. input.charCodeAt(end) !== CC_TILDE
  1846. ) {
  1847. return end;
  1848. }
  1849. end += 1;
  1850. if (isTilde) {
  1851. if (input.charCodeAt(end) !== CC_EQUAL) {
  1852. return end;
  1853. }
  1854. end += 1;
  1855. }
  1856. end = walkCssTokens.eatWhitespaceAndComments(input, end)[0];
  1857. const value = walkCssTokens.eatIdentSequenceOrString(input, end);
  1858. if (!value) {
  1859. return end;
  1860. }
  1861. const classNameStart = value[2] ? value[0] : value[0] + 1;
  1862. const classNameEnd = value[2] ? value[1] : value[1] - 1;
  1863. const className = unescapeIdentifier(
  1864. input.slice(classNameStart, classNameEnd)
  1865. );
  1866. const dep = new CssIcssExportDependency(
  1867. className,
  1868. getReexport(className),
  1869. [classNameStart, classNameEnd],
  1870. true,
  1871. CssIcssExportDependency.EXPORT_MODE.NONE
  1872. );
  1873. const { line: sl, column: sc } = locConverter.get(classNameStart);
  1874. const { line: el, column: ec } = locConverter.get(classNameEnd);
  1875. dep.setLoc(sl, sc, el, ec);
  1876. module.addDependency(dep);
  1877. return value[2] ? classNameEnd : classNameEnd + 1;
  1878. };
  1879. walkCssTokens(source, 0, {
  1880. comment,
  1881. leftCurlyBracket: (input, start, end) => {
  1882. switch (scope) {
  1883. case CSS_MODE_TOP_LEVEL: {
  1884. allowImportAtRule = false;
  1885. scope = CSS_MODE_IN_BLOCK;
  1886. if (isModules) {
  1887. blockNestingLevel = 1;
  1888. isNextRulePrelude = isNextNestedSyntax(input, end);
  1889. }
  1890. break;
  1891. }
  1892. case CSS_MODE_IN_BLOCK: {
  1893. if (isModules) {
  1894. blockNestingLevel++;
  1895. isNextRulePrelude = isNextNestedSyntax(input, end);
  1896. }
  1897. break;
  1898. }
  1899. }
  1900. return end;
  1901. },
  1902. rightCurlyBracket: (input, start, end) => {
  1903. switch (scope) {
  1904. case CSS_MODE_IN_BLOCK: {
  1905. if (--blockNestingLevel === 0) {
  1906. scope = CSS_MODE_TOP_LEVEL;
  1907. if (isModules) {
  1908. isNextRulePrelude = true;
  1909. modeData = undefined;
  1910. lastLocalIdentifiers = [];
  1911. }
  1912. } else if (isModules) {
  1913. isNextRulePrelude = isNextNestedSyntax(input, end);
  1914. }
  1915. break;
  1916. }
  1917. }
  1918. return end;
  1919. },
  1920. url: (input, start, end, contentStart, contentEnd) => {
  1921. if (!this.options.url) {
  1922. return end;
  1923. }
  1924. return processOldURLFunction(
  1925. input,
  1926. start,
  1927. end,
  1928. contentStart,
  1929. contentEnd
  1930. );
  1931. },
  1932. atKeyword: (input, start, end) => {
  1933. const name = input.slice(start, end).toLowerCase();
  1934. switch (name) {
  1935. case "@namespace": {
  1936. this._emitWarning(
  1937. state,
  1938. "'@namespace' is not supported in bundled CSS",
  1939. locConverter,
  1940. start,
  1941. end
  1942. );
  1943. return eatUntilSemi(input, start);
  1944. }
  1945. case "@import": {
  1946. if (!this.options.import) {
  1947. return eatUntilSemi(input, end);
  1948. }
  1949. if (!allowImportAtRule) {
  1950. this._emitWarning(
  1951. state,
  1952. "Any '@import' rules must precede all other rules",
  1953. locConverter,
  1954. start,
  1955. end
  1956. );
  1957. return end;
  1958. }
  1959. return processAtImport(input, start, end);
  1960. }
  1961. default: {
  1962. if (isModules) {
  1963. if (name === "@value") {
  1964. return processAtValue(input, start, end);
  1965. } else if (
  1966. this.options.animation &&
  1967. OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) &&
  1968. isLocalMode()
  1969. ) {
  1970. return processLocalAtRule(input, end, {
  1971. string: true,
  1972. identifier: true
  1973. });
  1974. } else if (
  1975. this.options.customIdents &&
  1976. name === "@counter-style" &&
  1977. isLocalMode()
  1978. ) {
  1979. return processLocalAtRule(input, end, {
  1980. identifier: true
  1981. });
  1982. } else if (
  1983. this.options.container &&
  1984. name === "@container" &&
  1985. isLocalMode()
  1986. ) {
  1987. return processLocalAtRule(input, end, {
  1988. identifier: /^(none|and|or|not)$/
  1989. });
  1990. } else if (name === "@scope") {
  1991. isNextRulePrelude = true;
  1992. return end;
  1993. }
  1994. isNextRulePrelude = false;
  1995. }
  1996. }
  1997. }
  1998. return end;
  1999. },
  2000. semicolon: (input, start, end) => {
  2001. if (isModules && scope === CSS_MODE_IN_BLOCK) {
  2002. isNextRulePrelude = isNextNestedSyntax(input, end);
  2003. }
  2004. return end;
  2005. },
  2006. identifier: (input, start, end) => {
  2007. if (isModules) {
  2008. const identifier = input.slice(start, end);
  2009. if (icssDefinitions.has(identifier)) {
  2010. return processICSSSymbol(identifier, start, end);
  2011. }
  2012. if (
  2013. this.options.dashedIdents &&
  2014. isLocalMode() &&
  2015. isDashedIdentifier(identifier)
  2016. ) {
  2017. return processDashedIdent(input, start, end);
  2018. }
  2019. switch (scope) {
  2020. case CSS_MODE_IN_BLOCK: {
  2021. if (isModules && !isNextRulePrelude) {
  2022. // Handle only top level values and not inside functions
  2023. return processLocalDeclaration(input, start, end);
  2024. }
  2025. break;
  2026. }
  2027. }
  2028. }
  2029. return end;
  2030. },
  2031. delim: (input, start, end) => {
  2032. if (isNextRulePrelude && isLocalMode()) {
  2033. return processClassSelector(input, start, end);
  2034. }
  2035. return end;
  2036. },
  2037. hash: (input, start, end, isID) => {
  2038. if (isNextRulePrelude && isLocalMode() && isID) {
  2039. return processIdSelector(input, start, end);
  2040. }
  2041. return end;
  2042. },
  2043. colon: (input, start, end) => {
  2044. if (isModules) {
  2045. const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
  2046. input,
  2047. end
  2048. );
  2049. if (!ident) return end;
  2050. const name = input.slice(ident[0], ident[1]).toLowerCase();
  2051. switch (scope) {
  2052. case CSS_MODE_TOP_LEVEL: {
  2053. if (name === "import") {
  2054. const pos = processImportOrExport(0, input, ident[1]);
  2055. const dep = new ConstDependency("", [start, pos]);
  2056. module.addPresentationalDependency(dep);
  2057. return pos;
  2058. } else if (name === "export") {
  2059. const pos = processImportOrExport(1, input, ident[1]);
  2060. const dep = new ConstDependency("", [start, pos]);
  2061. module.addPresentationalDependency(dep);
  2062. return pos;
  2063. }
  2064. }
  2065. // falls through
  2066. default: {
  2067. if (isNextRulePrelude) {
  2068. const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS;
  2069. if (isFn && name === "local") {
  2070. // Eat extra whitespace
  2071. const end = walkCssTokens.eatWhitespaceAndComments(
  2072. input,
  2073. ident[1] + 1
  2074. )[0];
  2075. modeData = LOCAL_MODE;
  2076. const dep = new ConstDependency("", [start, end]);
  2077. module.addPresentationalDependency(dep);
  2078. balanced.push([":local", start, end, true]);
  2079. return end;
  2080. } else if (name === "local") {
  2081. modeData = LOCAL_MODE;
  2082. const found = walkCssTokens.eatWhitespaceAndComments(
  2083. input,
  2084. ident[1]
  2085. );
  2086. if (!found[1]) {
  2087. this._emitWarning(
  2088. state,
  2089. `Missing whitespace after ':local' in '${input.slice(
  2090. start,
  2091. eatUntilLeftCurly(input, end) + 1
  2092. )}'`,
  2093. locConverter,
  2094. start,
  2095. end
  2096. );
  2097. }
  2098. end = walkCssTokens.eatWhitespace(input, ident[1]);
  2099. const dep = new ConstDependency("", [start, end]);
  2100. module.addPresentationalDependency(dep);
  2101. return end;
  2102. } else if (isFn && name === "global") {
  2103. // Eat extra whitespace
  2104. const end = walkCssTokens.eatWhitespaceAndComments(
  2105. input,
  2106. ident[1] + 1
  2107. )[0];
  2108. modeData = GLOBAL_MODE;
  2109. const dep = new ConstDependency("", [start, end]);
  2110. module.addPresentationalDependency(dep);
  2111. balanced.push([":global", start, end, true]);
  2112. return end;
  2113. } else if (name === "global") {
  2114. modeData = GLOBAL_MODE;
  2115. // Eat extra whitespace
  2116. const found = walkCssTokens.eatWhitespaceAndComments(
  2117. input,
  2118. ident[1]
  2119. );
  2120. if (!found[1]) {
  2121. this._emitWarning(
  2122. state,
  2123. `Missing whitespace after ':global' in '${input.slice(
  2124. start,
  2125. eatUntilLeftCurly(input, end) + 1
  2126. )}'`,
  2127. locConverter,
  2128. start,
  2129. end
  2130. );
  2131. }
  2132. end = walkCssTokens.eatWhitespace(input, ident[1]);
  2133. const dep = new ConstDependency("", [start, end]);
  2134. module.addPresentationalDependency(dep);
  2135. return end;
  2136. }
  2137. }
  2138. }
  2139. }
  2140. }
  2141. lastTokenEndForComments = end;
  2142. return end;
  2143. },
  2144. function: (input, start, end) => {
  2145. const name = input
  2146. .slice(start, end - 1)
  2147. .replace(/\\/g, "")
  2148. .toLowerCase();
  2149. balanced.push([name, start, end]);
  2150. switch (name) {
  2151. case "src":
  2152. case "url": {
  2153. if (!this.options.url) {
  2154. return end;
  2155. }
  2156. return processURLFunction(input, end, name);
  2157. }
  2158. default: {
  2159. if (this.options.url && IMAGE_SET_FUNCTION.test(name)) {
  2160. return processImageSetFunction(input, start, end);
  2161. }
  2162. if (isModules) {
  2163. if (
  2164. this.options.function &&
  2165. isLocalMode() &&
  2166. isDashedIdentifier(name)
  2167. ) {
  2168. return processDashedIdent(input, start, end);
  2169. }
  2170. const type =
  2171. name === "local" ? 1 : name === "global" ? 2 : undefined;
  2172. if (type && !isNextRulePrelude) {
  2173. return processLocalOrGlobalFunction(input, type, start, end);
  2174. }
  2175. }
  2176. }
  2177. }
  2178. return end;
  2179. },
  2180. leftSquareBracket: (input, start, end) => {
  2181. if (isNextRulePrelude && isLocalMode()) {
  2182. return processAttributeSelector(input, start, end);
  2183. }
  2184. return end;
  2185. },
  2186. leftParenthesis: (input, start, end) => {
  2187. balanced.push(["(", start, end]);
  2188. return end;
  2189. },
  2190. rightParenthesis: (input, start, end) => {
  2191. const popped = balanced.pop();
  2192. if (isModules && popped) {
  2193. const isLocal = popped[0] === ":local";
  2194. const isGlobal = popped[0] === ":global";
  2195. if (isLocal || isGlobal) {
  2196. modeData = balanced[balanced.length - 1]
  2197. ? balanced[balanced.length - 1][0] === ":local"
  2198. ? LOCAL_MODE
  2199. : balanced[balanced.length - 1][0] === ":global"
  2200. ? GLOBAL_MODE
  2201. : undefined
  2202. : undefined;
  2203. if (popped[3] && isLocal) {
  2204. while (walkCssTokens.isWhiteSpace(input.charCodeAt(start - 1))) {
  2205. start -= 1;
  2206. }
  2207. }
  2208. const dep = new ConstDependency("", [start, end]);
  2209. module.addPresentationalDependency(dep);
  2210. } else if (isNextRulePrelude) {
  2211. modeData = undefined;
  2212. }
  2213. }
  2214. return end;
  2215. },
  2216. comma: (input, start, end) => {
  2217. if (isModules) {
  2218. const popped = balanced.pop();
  2219. if (!popped) {
  2220. // Reset stack for `:global .class :local .class-other` selector after
  2221. modeData = undefined;
  2222. }
  2223. }
  2224. lastTokenEndForComments = start;
  2225. return end;
  2226. }
  2227. });
  2228. /** @type {BuildInfo} */
  2229. (module.buildInfo).strict = true;
  2230. const buildMeta = /** @type {BuildMeta} */ (state.module.buildMeta);
  2231. buildMeta.exportsType = this.options.namedExports ? "namespace" : "default";
  2232. buildMeta.defaultObject = this.options.namedExports
  2233. ? false
  2234. : "redirect-warn";
  2235. buildMeta.exportType = this.options.exportType;
  2236. if (!buildMeta.exportType) {
  2237. // Inherit exportType from parent module to ensure consistency.
  2238. // When a CSS file is imported with syntax like `import "./basic.css" with { type: "css" }`,
  2239. // the parent module's exportType is set to "css-style-sheet".
  2240. // Child modules imported via @import should inherit this exportType
  2241. // instead of using the default "link", ensuring that the entire
  2242. // import chain uses the same export format.
  2243. const parent = state.compilation.moduleGraph.getIssuer(module);
  2244. if (parent instanceof CssModule) {
  2245. buildMeta.exportType = /** @type {BuildMeta} */ (
  2246. parent.buildMeta
  2247. ).exportType;
  2248. }
  2249. }
  2250. if (!buildMeta.exportType) {
  2251. buildMeta.exportType = "link";
  2252. }
  2253. // TODO this.namedExports?
  2254. if (
  2255. buildMeta.exportType === "text" ||
  2256. buildMeta.exportType === "css-style-sheet"
  2257. ) {
  2258. module.addDependency(new StaticExportsDependency(["default"], true));
  2259. } else {
  2260. module.addDependency(new StaticExportsDependency([], true));
  2261. }
  2262. return state;
  2263. }
  2264. /**
  2265. * @param {Range} range range
  2266. * @returns {Comment[]} comments in the range
  2267. */
  2268. getComments(range) {
  2269. if (!this.comments) return [];
  2270. const [rangeStart, rangeEnd] = range;
  2271. /**
  2272. * @param {Comment} comment comment
  2273. * @param {number} needle needle
  2274. * @returns {number} compared
  2275. */
  2276. const compare = (comment, needle) =>
  2277. /** @type {Range} */ (comment.range)[0] - needle;
  2278. const comments = /** @type {Comment[]} */ (this.comments);
  2279. let idx = binarySearchBounds.ge(comments, rangeStart, compare);
  2280. /** @type {Comment[]} */
  2281. const commentsInRange = [];
  2282. while (
  2283. comments[idx] &&
  2284. /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
  2285. ) {
  2286. commentsInRange.push(comments[idx]);
  2287. idx++;
  2288. }
  2289. return commentsInRange;
  2290. }
  2291. /**
  2292. * @param {Range} range range of the comment
  2293. * @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
  2294. */
  2295. parseCommentOptions(range) {
  2296. const comments = this.getComments(range);
  2297. if (comments.length === 0) {
  2298. return EMPTY_COMMENT_OPTIONS;
  2299. }
  2300. /** @type {Record<string, EXPECTED_ANY>} */
  2301. const options = {};
  2302. /** @type {(Error & { comment: Comment })[]} */
  2303. const errors = [];
  2304. for (const comment of comments) {
  2305. const { value } = comment;
  2306. if (value && webpackCommentRegExp.test(value)) {
  2307. // try compile only if webpack options comment is present
  2308. try {
  2309. for (let [key, val] of Object.entries(
  2310. vm.runInContext(
  2311. `(function(){return {${value}};})()`,
  2312. this.magicCommentContext
  2313. )
  2314. )) {
  2315. if (typeof val === "object" && val !== null) {
  2316. val =
  2317. val.constructor.name === "RegExp"
  2318. ? new RegExp(val)
  2319. : JSON.parse(JSON.stringify(val));
  2320. }
  2321. options[key] = val;
  2322. }
  2323. } catch (err) {
  2324. const newErr = new Error(String(/** @type {Error} */ (err).message));
  2325. newErr.stack = String(/** @type {Error} */ (err).stack);
  2326. Object.assign(newErr, { comment });
  2327. errors.push(/** @type {(Error & { comment: Comment })} */ (newErr));
  2328. }
  2329. }
  2330. }
  2331. return { options, errors };
  2332. }
  2333. }
  2334. module.exports = CssParser;
  2335. module.exports.escapeIdentifier = escapeIdentifier;
  2336. module.exports.unescapeIdentifier = unescapeIdentifier;