CssParser.js 45 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624
  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 ModuleDependencyWarning = require("../ModuleDependencyWarning");
  9. const { CSS_MODULE_TYPE_AUTO } = require("../ModuleTypeConstants");
  10. const Parser = require("../Parser");
  11. const UnsupportedFeatureWarning = require("../UnsupportedFeatureWarning");
  12. const WebpackError = require("../WebpackError");
  13. const ConstDependency = require("../dependencies/ConstDependency");
  14. const CssIcssExportDependency = require("../dependencies/CssIcssExportDependency");
  15. const CssIcssImportDependency = require("../dependencies/CssIcssImportDependency");
  16. const CssIcssSymbolDependency = require("../dependencies/CssIcssSymbolDependency");
  17. const CssImportDependency = require("../dependencies/CssImportDependency");
  18. const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
  19. const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
  20. const CssUrlDependency = require("../dependencies/CssUrlDependency");
  21. const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
  22. const binarySearchBounds = require("../util/binarySearchBounds");
  23. const { parseResource } = require("../util/identifier");
  24. const {
  25. createMagicCommentContext,
  26. webpackCommentRegExp
  27. } = require("../util/magicComment");
  28. const walkCssTokens = require("./walkCssTokens");
  29. /** @typedef {import("../Module").BuildInfo} BuildInfo */
  30. /** @typedef {import("../Module").BuildMeta} BuildMeta */
  31. /** @typedef {import("../Parser").ParserState} ParserState */
  32. /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
  33. /** @typedef {import("./walkCssTokens").CssTokenCallbacks} CssTokenCallbacks */
  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_SLASH = "/".charCodeAt(0);
  39. const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
  40. const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
  41. const CC_LOWER_F = "f".charCodeAt(0);
  42. const CC_UPPER_F = "F".charCodeAt(0);
  43. // https://www.w3.org/TR/css-syntax-3/#newline
  44. // We don't have `preprocessing` stage, so we need specify all of them
  45. const STRING_MULTILINE = /\\[\n\r\f]/g;
  46. // https://www.w3.org/TR/css-syntax-3/#whitespace
  47. const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
  48. const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g;
  49. const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i;
  50. const OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE = /^@(-\w+-)?keyframes$/;
  51. const OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY =
  52. /^(-\w+-)?animation(-name)?$/i;
  53. const IS_MODULES = /\.module(s)?\.[^.]+$/i;
  54. const CSS_COMMENT = /\/\*((?!\*\/).*?)\*\//g;
  55. /**
  56. * @param {string} str url string
  57. * @param {boolean} isString is url wrapped in quotes
  58. * @returns {string} normalized url
  59. */
  60. const normalizeUrl = (str, isString) => {
  61. // Remove extra spaces and newlines:
  62. // `url("im\
  63. // g.png")`
  64. if (isString) {
  65. str = str.replace(STRING_MULTILINE, "");
  66. }
  67. str = str
  68. // Remove unnecessary spaces from `url(" img.png ")`
  69. .replace(TRIM_WHITE_SPACES, "")
  70. // Unescape
  71. .replace(UNESCAPE, match => {
  72. if (match.length > 2) {
  73. return String.fromCharCode(Number.parseInt(match.slice(1).trim(), 16));
  74. }
  75. return match[1];
  76. });
  77. if (/^data:/i.test(str)) {
  78. return str;
  79. }
  80. if (str.includes("%")) {
  81. // Convert `url('%2E/img.png')` -> `url('./img.png')`
  82. try {
  83. str = decodeURIComponent(str);
  84. } catch (_err) {
  85. // Ignore
  86. }
  87. }
  88. return str;
  89. };
  90. // eslint-disable-next-line no-useless-escape
  91. const regexSingleEscape = /[ -,.\/:-@[\]\^`{-~]/;
  92. const regexExcessiveSpaces =
  93. /(^|\\+)?(\\[A-F0-9]{1,6})\u0020(?![a-fA-F0-9\u0020])/g;
  94. /**
  95. * @param {string} str string
  96. * @returns {string} escaped identifier
  97. */
  98. const escapeIdentifier = str => {
  99. let output = "";
  100. let counter = 0;
  101. while (counter < str.length) {
  102. const character = str.charAt(counter++);
  103. let value;
  104. // eslint-disable-next-line no-control-regex
  105. if (/[\t\n\f\r\u000B]/.test(character)) {
  106. const codePoint = character.charCodeAt(0);
  107. value = `\\${codePoint.toString(16).toUpperCase()} `;
  108. } else if (character === "\\" || regexSingleEscape.test(character)) {
  109. value = `\\${character}`;
  110. } else {
  111. value = character;
  112. }
  113. output += value;
  114. }
  115. const firstChar = str.charAt(0);
  116. if (/^-[-\d]/.test(output)) {
  117. output = `\\-${output.slice(1)}`;
  118. } else if (/\d/.test(firstChar)) {
  119. output = `\\3${firstChar} ${output.slice(1)}`;
  120. }
  121. // Remove spaces after `\HEX` escapes that are not followed by a hex digit,
  122. // since they’re redundant. Note that this is only possible if the escape
  123. // sequence isn’t preceded by an odd number of backslashes.
  124. output = output.replace(regexExcessiveSpaces, ($0, $1, $2) => {
  125. if ($1 && $1.length % 2) {
  126. // It’s not safe to remove the space, so don’t.
  127. return $0;
  128. }
  129. // Strip the space.
  130. return ($1 || "") + $2;
  131. });
  132. return output;
  133. };
  134. const CONTAINS_ESCAPE = /\\/;
  135. /**
  136. * @param {string} str string
  137. * @returns {[string, number] | undefined} hex
  138. */
  139. const gobbleHex = str => {
  140. const lower = str.toLowerCase();
  141. let hex = "";
  142. let spaceTerminated = false;
  143. for (let i = 0; i < 6 && lower[i] !== undefined; i++) {
  144. const code = lower.charCodeAt(i);
  145. // check to see if we are dealing with a valid hex char [a-f|0-9]
  146. const valid = (code >= 97 && code <= 102) || (code >= 48 && code <= 57);
  147. // https://drafts.csswg.org/css-syntax/#consume-escaped-code-point
  148. spaceTerminated = code === 32;
  149. if (!valid) break;
  150. hex += lower[i];
  151. }
  152. if (hex.length === 0) return undefined;
  153. const codePoint = Number.parseInt(hex, 16);
  154. const isSurrogate = codePoint >= 0xd800 && codePoint <= 0xdfff;
  155. // Add special case for
  156. // "If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point"
  157. // https://drafts.csswg.org/css-syntax/#maximum-allowed-code-point
  158. if (isSurrogate || codePoint === 0x0000 || codePoint > 0x10ffff) {
  159. return ["\uFFFD", hex.length + (spaceTerminated ? 1 : 0)];
  160. }
  161. return [
  162. String.fromCodePoint(codePoint),
  163. hex.length + (spaceTerminated ? 1 : 0)
  164. ];
  165. };
  166. /**
  167. * @param {string} str string
  168. * @returns {string} unescaped string
  169. */
  170. const unescapeIdentifier = str => {
  171. const needToProcess = CONTAINS_ESCAPE.test(str);
  172. if (!needToProcess) return str;
  173. let ret = "";
  174. for (let i = 0; i < str.length; i++) {
  175. if (str[i] === "\\") {
  176. const gobbled = gobbleHex(str.slice(i + 1, i + 7));
  177. if (gobbled !== undefined) {
  178. ret += gobbled[0];
  179. i += gobbled[1];
  180. continue;
  181. }
  182. // Retain a pair of \\ if double escaped `\\\\`
  183. // https://github.com/postcss/postcss-selector-parser/commit/268c9a7656fb53f543dc620aa5b73a30ec3ff20e
  184. if (str[i + 1] === "\\") {
  185. ret += "\\";
  186. i += 1;
  187. continue;
  188. }
  189. // if \\ is at the end of the string retain it
  190. // https://github.com/postcss/postcss-selector-parser/commit/01a6b346e3612ce1ab20219acc26abdc259ccefb
  191. if (str.length === i + 1) {
  192. ret += str[i];
  193. }
  194. continue;
  195. }
  196. ret += str[i];
  197. }
  198. return ret;
  199. };
  200. class LocConverter {
  201. /**
  202. * @param {string} input input
  203. */
  204. constructor(input) {
  205. this._input = input;
  206. this.line = 1;
  207. this.column = 0;
  208. this.pos = 0;
  209. }
  210. /**
  211. * @param {number} pos position
  212. * @returns {LocConverter} location converter
  213. */
  214. get(pos) {
  215. if (this.pos !== pos) {
  216. if (this.pos < pos) {
  217. const str = this._input.slice(this.pos, pos);
  218. let i = str.lastIndexOf("\n");
  219. if (i === -1) {
  220. this.column += str.length;
  221. } else {
  222. this.column = str.length - i - 1;
  223. this.line++;
  224. while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1) {
  225. this.line++;
  226. }
  227. }
  228. } else {
  229. let i = this._input.lastIndexOf("\n", this.pos);
  230. while (i >= pos) {
  231. this.line--;
  232. i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
  233. }
  234. this.column = pos - i;
  235. }
  236. this.pos = pos;
  237. }
  238. return this;
  239. }
  240. }
  241. const EMPTY_COMMENT_OPTIONS = {
  242. options: null,
  243. errors: null
  244. };
  245. const CSS_MODE_TOP_LEVEL = 0;
  246. const CSS_MODE_IN_BLOCK = 1;
  247. const eatUntilSemi = walkCssTokens.eatUntil(";");
  248. const eatUntilLeftCurly = walkCssTokens.eatUntil("{");
  249. const eatSemi = walkCssTokens.eatUntil(";");
  250. /**
  251. * @typedef {object} CssParserOptions
  252. * @property {boolean=} importOption need handle `@import`
  253. * @property {boolean=} url need handle URLs
  254. * @property {("pure" | "global" | "local" | "auto")=} defaultMode default mode
  255. * @property {boolean=} namedExports is named exports
  256. */
  257. class CssParser extends Parser {
  258. /**
  259. * @param {CssParserOptions=} options options
  260. */
  261. constructor({
  262. defaultMode = "pure",
  263. importOption = true,
  264. url = true,
  265. namedExports = true
  266. } = {}) {
  267. super();
  268. this.defaultMode = defaultMode;
  269. this.import = importOption;
  270. this.url = url;
  271. this.namedExports = namedExports;
  272. /** @type {Comment[] | undefined} */
  273. this.comments = undefined;
  274. this.magicCommentContext = createMagicCommentContext();
  275. }
  276. /**
  277. * @param {ParserState} state parser state
  278. * @param {string} message warning message
  279. * @param {LocConverter} locConverter location converter
  280. * @param {number} start start offset
  281. * @param {number} end end offset
  282. */
  283. _emitWarning(state, message, locConverter, start, end) {
  284. const { line: sl, column: sc } = locConverter.get(start);
  285. const { line: el, column: ec } = locConverter.get(end);
  286. state.current.addWarning(
  287. new ModuleDependencyWarning(state.module, new WebpackError(message), {
  288. start: { line: sl, column: sc },
  289. end: { line: el, column: ec }
  290. })
  291. );
  292. }
  293. /**
  294. * @param {string | Buffer | PreparsedAst} source the source to parse
  295. * @param {ParserState} state the parser state
  296. * @returns {ParserState} the parser state
  297. */
  298. parse(source, state) {
  299. if (Buffer.isBuffer(source)) {
  300. source = source.toString("utf8");
  301. } else if (typeof source === "object") {
  302. throw new Error("webpackAst is unexpected for the CssParser");
  303. }
  304. if (source[0] === "\uFEFF") {
  305. source = source.slice(1);
  306. }
  307. let mode = this.defaultMode;
  308. const module = state.module;
  309. if (
  310. mode === "auto" &&
  311. module.type === CSS_MODULE_TYPE_AUTO &&
  312. IS_MODULES.test(
  313. parseResource(module.matchResource || module.resource).path
  314. )
  315. ) {
  316. mode = "local";
  317. }
  318. const isModules = mode === "global" || mode === "local";
  319. /** @type {BuildMeta} */
  320. (module.buildMeta).isCSSModule = isModules;
  321. const locConverter = new LocConverter(source);
  322. /** @type {number} */
  323. let scope = CSS_MODE_TOP_LEVEL;
  324. /** @type {boolean} */
  325. let allowImportAtRule = true;
  326. /** @type [string, number, number][] */
  327. const balanced = [];
  328. let lastTokenEndForComments = 0;
  329. /** @type {boolean} */
  330. let isNextRulePrelude = isModules;
  331. /** @type {number} */
  332. let blockNestingLevel = 0;
  333. /** @type {"local" | "global" | undefined} */
  334. let modeData;
  335. /** @type {boolean} */
  336. let inAnimationProperty = false;
  337. /** @type {[number, number, boolean] | undefined} */
  338. let lastIdentifier;
  339. /** @type {Set<string>} */
  340. const declaredCssVariables = new Set();
  341. /** @typedef {{ path?: string, value: string }} IcssDefinition */
  342. /** @type {Map<string, IcssDefinition>} */
  343. const icssDefinitions = new Map();
  344. /**
  345. * @param {string} input input
  346. * @param {number} pos position
  347. * @returns {boolean} true, when next is nested syntax
  348. */
  349. const isNextNestedSyntax = (input, pos) => {
  350. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  351. if (input[pos] === "}") {
  352. return false;
  353. }
  354. // According spec only identifier can be used as a property name
  355. const isIdentifier = walkCssTokens.isIdentStartCodePoint(
  356. input.charCodeAt(pos)
  357. );
  358. return !isIdentifier;
  359. };
  360. /**
  361. * @returns {boolean} true, when in local scope
  362. */
  363. const isLocalMode = () =>
  364. modeData === "local" || (mode === "local" && modeData === undefined);
  365. /**
  366. * @param {string} input input
  367. * @param {number} pos start position
  368. * @param {(input: string, pos: number) => number} eater eater
  369. * @returns {[number,string]} new position and text
  370. */
  371. const eatText = (input, pos, eater) => {
  372. let text = "";
  373. for (;;) {
  374. if (input.charCodeAt(pos) === CC_SLASH) {
  375. const newPos = walkCssTokens.eatComments(input, pos);
  376. if (pos !== newPos) {
  377. pos = newPos;
  378. if (pos === input.length) break;
  379. } else {
  380. text += "/";
  381. pos++;
  382. if (pos === input.length) break;
  383. }
  384. }
  385. const newPos = eater(input, pos);
  386. if (pos !== newPos) {
  387. text += input.slice(pos, newPos);
  388. pos = newPos;
  389. } else {
  390. break;
  391. }
  392. if (pos === input.length) break;
  393. }
  394. return [pos, text.trimEnd()];
  395. };
  396. /**
  397. * @param {0 | 1} type import or export
  398. * @param {string} input input
  399. * @param {number} pos start position
  400. * @returns {number} position after parse
  401. */
  402. const parseImportOrExport = (type, input, pos) => {
  403. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  404. /** @type {string | undefined} */
  405. let importPath;
  406. if (type === 0) {
  407. let cc = input.charCodeAt(pos);
  408. if (cc !== CC_LEFT_PARENTHESIS) {
  409. this._emitWarning(
  410. state,
  411. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected '(')`,
  412. locConverter,
  413. pos,
  414. pos
  415. );
  416. return pos;
  417. }
  418. pos++;
  419. const stringStart = pos;
  420. const str = walkCssTokens.eatString(input, pos);
  421. if (!str) {
  422. this._emitWarning(
  423. state,
  424. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected string)`,
  425. locConverter,
  426. stringStart,
  427. pos
  428. );
  429. return pos;
  430. }
  431. importPath = input.slice(str[0] + 1, str[1] - 1);
  432. pos = str[1];
  433. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  434. cc = input.charCodeAt(pos);
  435. if (cc !== CC_RIGHT_PARENTHESIS) {
  436. this._emitWarning(
  437. state,
  438. `Unexpected '${input[pos]}' at ${pos} during parsing of ':import' (expected ')')`,
  439. locConverter,
  440. pos,
  441. pos
  442. );
  443. return pos;
  444. }
  445. pos++;
  446. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  447. }
  448. /**
  449. * @param {string} name name
  450. * @param {string} value value
  451. * @param {number} start start of position
  452. * @param {number} end end of position
  453. */
  454. const createDep = (name, value, start, end) => {
  455. if (type === 0) {
  456. icssDefinitions.set(name, {
  457. path: /** @type {string} */ (importPath),
  458. value
  459. });
  460. } else if (type === 1) {
  461. const dep = new CssIcssExportDependency(name, value);
  462. const { line: sl, column: sc } = locConverter.get(start);
  463. const { line: el, column: ec } = locConverter.get(end);
  464. dep.setLoc(sl, sc, el, ec);
  465. module.addDependency(dep);
  466. }
  467. };
  468. let needTerminate = false;
  469. let balanced = 0;
  470. /** @type {undefined | 0 | 1 | 2} */
  471. let scope;
  472. /** @typedef {[number, number]} Name */
  473. /** @type {Name | undefined} */
  474. let name;
  475. /** @type {number | undefined} */
  476. let value;
  477. /** @type {CssTokenCallbacks} */
  478. const callbacks = {
  479. leftCurlyBracket: (_input, _start, end) => {
  480. balanced++;
  481. if (scope === undefined) {
  482. scope = 0;
  483. }
  484. return end;
  485. },
  486. rightCurlyBracket: (_input, _start, end) => {
  487. balanced--;
  488. if (scope === 2) {
  489. const [nameStart, nameEnd] = /** @type {Name} */ (name);
  490. createDep(
  491. input.slice(nameStart, nameEnd),
  492. input.slice(value, end - 1).trim(),
  493. nameEnd,
  494. end - 1
  495. );
  496. scope = 0;
  497. }
  498. if (balanced === 0 && scope === 0) {
  499. needTerminate = true;
  500. }
  501. return end;
  502. },
  503. identifier: (_input, start, end) => {
  504. if (scope === 0) {
  505. name = [start, end];
  506. scope = 1;
  507. }
  508. return end;
  509. },
  510. colon: (_input, _start, end) => {
  511. if (scope === 1) {
  512. scope = 2;
  513. value = walkCssTokens.eatWhitespace(input, end);
  514. return value;
  515. }
  516. return end;
  517. },
  518. semicolon: (input, _start, end) => {
  519. if (scope === 2) {
  520. const [nameStart, nameEnd] = /** @type {Name} */ (name);
  521. createDep(
  522. input.slice(nameStart, nameEnd),
  523. input.slice(value, end - 1),
  524. nameEnd,
  525. end - 1
  526. );
  527. scope = 0;
  528. }
  529. return end;
  530. },
  531. needTerminate: () => needTerminate
  532. };
  533. pos = walkCssTokens(input, pos, callbacks);
  534. pos = walkCssTokens.eatWhiteLine(input, pos);
  535. return pos;
  536. };
  537. const eatPropertyName = walkCssTokens.eatUntil(":{};");
  538. /**
  539. * @param {string} input input
  540. * @param {number} pos name start position
  541. * @param {number} end name end position
  542. * @returns {number} position after handling
  543. */
  544. const processLocalDeclaration = (input, pos, end) => {
  545. modeData = undefined;
  546. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  547. const propertyNameStart = pos;
  548. const [propertyNameEnd, propertyName] = eatText(
  549. input,
  550. pos,
  551. eatPropertyName
  552. );
  553. if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return end;
  554. pos = propertyNameEnd + 1;
  555. if (propertyName.startsWith("--") && propertyName.length >= 3) {
  556. // CSS Variable
  557. const { line: sl, column: sc } = locConverter.get(propertyNameStart);
  558. const { line: el, column: ec } = locConverter.get(propertyNameEnd);
  559. const name = unescapeIdentifier(propertyName.slice(2));
  560. const dep = new CssLocalIdentifierDependency(
  561. name,
  562. [propertyNameStart, propertyNameEnd],
  563. "--"
  564. );
  565. dep.setLoc(sl, sc, el, ec);
  566. module.addDependency(dep);
  567. declaredCssVariables.add(name);
  568. } else if (
  569. OPTIONALLY_VENDOR_PREFIXED_ANIMATION_PROPERTY.test(propertyName)
  570. ) {
  571. inAnimationProperty = true;
  572. }
  573. return pos;
  574. };
  575. /**
  576. * @param {string} input input
  577. */
  578. const processDeclarationValueDone = input => {
  579. if (inAnimationProperty && lastIdentifier) {
  580. const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]);
  581. const { line: el, column: ec } = locConverter.get(lastIdentifier[1]);
  582. const name = unescapeIdentifier(
  583. lastIdentifier[2]
  584. ? input.slice(lastIdentifier[0], lastIdentifier[1])
  585. : input.slice(lastIdentifier[0] + 1, lastIdentifier[1] - 1)
  586. );
  587. const dep = new CssSelfLocalIdentifierDependency(name, [
  588. lastIdentifier[0],
  589. lastIdentifier[1]
  590. ]);
  591. dep.setLoc(sl, sc, el, ec);
  592. module.addDependency(dep);
  593. lastIdentifier = undefined;
  594. }
  595. };
  596. /**
  597. * @param {string} input input
  598. * @param {number} start start
  599. * @param {number} end end
  600. * @returns {number} end
  601. */
  602. const comment = (input, start, end) => {
  603. if (!this.comments) this.comments = [];
  604. const { line: sl, column: sc } = locConverter.get(start);
  605. const { line: el, column: ec } = locConverter.get(end);
  606. /** @type {Comment} */
  607. const comment = {
  608. value: input.slice(start + 2, end - 2),
  609. range: [start, end],
  610. loc: {
  611. start: { line: sl, column: sc },
  612. end: { line: el, column: ec }
  613. }
  614. };
  615. this.comments.push(comment);
  616. return end;
  617. };
  618. walkCssTokens(source, 0, {
  619. comment,
  620. leftCurlyBracket: (input, start, end) => {
  621. switch (scope) {
  622. case CSS_MODE_TOP_LEVEL: {
  623. allowImportAtRule = false;
  624. scope = CSS_MODE_IN_BLOCK;
  625. if (isModules) {
  626. blockNestingLevel = 1;
  627. isNextRulePrelude = isNextNestedSyntax(input, end);
  628. }
  629. break;
  630. }
  631. case CSS_MODE_IN_BLOCK: {
  632. if (isModules) {
  633. blockNestingLevel++;
  634. isNextRulePrelude = isNextNestedSyntax(input, end);
  635. }
  636. break;
  637. }
  638. }
  639. return end;
  640. },
  641. rightCurlyBracket: (input, start, end) => {
  642. switch (scope) {
  643. case CSS_MODE_IN_BLOCK: {
  644. if (--blockNestingLevel === 0) {
  645. scope = CSS_MODE_TOP_LEVEL;
  646. if (isModules) {
  647. isNextRulePrelude = true;
  648. modeData = undefined;
  649. }
  650. } else if (isModules) {
  651. if (isLocalMode()) {
  652. processDeclarationValueDone(input);
  653. inAnimationProperty = false;
  654. }
  655. isNextRulePrelude = isNextNestedSyntax(input, end);
  656. }
  657. break;
  658. }
  659. }
  660. return end;
  661. },
  662. url: (input, start, end, contentStart, contentEnd) => {
  663. if (!this.url) {
  664. return end;
  665. }
  666. const { options, errors: commentErrors } = this.parseCommentOptions([
  667. lastTokenEndForComments,
  668. end
  669. ]);
  670. if (commentErrors) {
  671. for (const e of commentErrors) {
  672. const { comment } = e;
  673. state.module.addWarning(
  674. new CommentCompilationWarning(
  675. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  676. comment.loc
  677. )
  678. );
  679. }
  680. }
  681. if (options && options.webpackIgnore !== undefined) {
  682. if (typeof options.webpackIgnore !== "boolean") {
  683. const { line: sl, column: sc } = locConverter.get(
  684. lastTokenEndForComments
  685. );
  686. const { line: el, column: ec } = locConverter.get(end);
  687. state.module.addWarning(
  688. new UnsupportedFeatureWarning(
  689. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  690. {
  691. start: { line: sl, column: sc },
  692. end: { line: el, column: ec }
  693. }
  694. )
  695. );
  696. } else if (options.webpackIgnore) {
  697. return end;
  698. }
  699. }
  700. const value = normalizeUrl(
  701. input.slice(contentStart, contentEnd),
  702. false
  703. );
  704. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  705. if (value.length === 0) return end;
  706. const dep = new CssUrlDependency(value, [start, end], "url");
  707. const { line: sl, column: sc } = locConverter.get(start);
  708. const { line: el, column: ec } = locConverter.get(end);
  709. dep.setLoc(sl, sc, el, ec);
  710. module.addDependency(dep);
  711. module.addCodeGenerationDependency(dep);
  712. return end;
  713. },
  714. string: (_input, start, end) => {
  715. switch (scope) {
  716. case CSS_MODE_IN_BLOCK: {
  717. if (inAnimationProperty && balanced.length === 0) {
  718. lastIdentifier = [start, end, false];
  719. }
  720. }
  721. }
  722. return end;
  723. },
  724. atKeyword: (input, start, end) => {
  725. const name = input.slice(start, end).toLowerCase();
  726. switch (name) {
  727. case "@namespace": {
  728. this._emitWarning(
  729. state,
  730. "'@namespace' is not supported in bundled CSS",
  731. locConverter,
  732. start,
  733. end
  734. );
  735. return eatUntilSemi(input, start);
  736. }
  737. case "@import": {
  738. if (!this.import) {
  739. return eatSemi(input, end);
  740. }
  741. if (!allowImportAtRule) {
  742. this._emitWarning(
  743. state,
  744. "Any '@import' rules must precede all other rules",
  745. locConverter,
  746. start,
  747. end
  748. );
  749. return end;
  750. }
  751. const tokens = walkCssTokens.eatImportTokens(input, end, {
  752. comment
  753. });
  754. if (!tokens[3]) return end;
  755. const semi = tokens[3][1];
  756. if (!tokens[0]) {
  757. this._emitWarning(
  758. state,
  759. `Expected URL in '${input.slice(start, semi)}'`,
  760. locConverter,
  761. start,
  762. semi
  763. );
  764. return end;
  765. }
  766. const urlToken = tokens[0];
  767. const url = normalizeUrl(
  768. input.slice(urlToken[2], urlToken[3]),
  769. true
  770. );
  771. const newline = walkCssTokens.eatWhiteLine(input, semi);
  772. const { options, errors: commentErrors } = this.parseCommentOptions(
  773. [end, urlToken[1]]
  774. );
  775. if (commentErrors) {
  776. for (const e of commentErrors) {
  777. const { comment } = e;
  778. state.module.addWarning(
  779. new CommentCompilationWarning(
  780. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  781. comment.loc
  782. )
  783. );
  784. }
  785. }
  786. if (options && options.webpackIgnore !== undefined) {
  787. if (typeof options.webpackIgnore !== "boolean") {
  788. const { line: sl, column: sc } = locConverter.get(start);
  789. const { line: el, column: ec } = locConverter.get(newline);
  790. state.module.addWarning(
  791. new UnsupportedFeatureWarning(
  792. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  793. {
  794. start: { line: sl, column: sc },
  795. end: { line: el, column: ec }
  796. }
  797. )
  798. );
  799. } else if (options.webpackIgnore) {
  800. return newline;
  801. }
  802. }
  803. if (url.length === 0) {
  804. const { line: sl, column: sc } = locConverter.get(start);
  805. const { line: el, column: ec } = locConverter.get(newline);
  806. const dep = new ConstDependency("", [start, newline]);
  807. module.addPresentationalDependency(dep);
  808. dep.setLoc(sl, sc, el, ec);
  809. return newline;
  810. }
  811. let layer;
  812. if (tokens[1]) {
  813. layer = input.slice(tokens[1][0] + 6, tokens[1][1] - 1).trim();
  814. }
  815. let supports;
  816. if (tokens[2]) {
  817. supports = input.slice(tokens[2][0] + 9, tokens[2][1] - 1).trim();
  818. }
  819. const last = tokens[2] || tokens[1] || tokens[0];
  820. const mediaStart = walkCssTokens.eatWhitespaceAndComments(
  821. input,
  822. last[1]
  823. );
  824. let media;
  825. if (mediaStart !== semi - 1) {
  826. media = input.slice(mediaStart, semi - 1).trim();
  827. }
  828. const { line: sl, column: sc } = locConverter.get(start);
  829. const { line: el, column: ec } = locConverter.get(newline);
  830. const dep = new CssImportDependency(
  831. url,
  832. [start, newline],
  833. layer,
  834. supports && supports.length > 0 ? supports : undefined,
  835. media && media.length > 0 ? media : undefined
  836. );
  837. dep.setLoc(sl, sc, el, ec);
  838. module.addDependency(dep);
  839. return newline;
  840. }
  841. default: {
  842. if (isModules) {
  843. if (name === "@value") {
  844. const semi = eatUntilSemi(input, end);
  845. const atRuleEnd = semi + 1;
  846. const params = input.slice(end, semi);
  847. let [alias, from] = params.split(/\s*from\s*/);
  848. if (from) {
  849. const aliases = alias
  850. .replace(CSS_COMMENT, " ")
  851. .trim()
  852. .replace(/^\(|\)$/g, "")
  853. .split(/\s*,\s*/);
  854. from = from.replace(CSS_COMMENT, "").trim();
  855. const isExplicitImport = from[0] === "'" || from[0] === '"';
  856. if (isExplicitImport) {
  857. from = from.slice(1, -1);
  858. }
  859. for (const alias of aliases) {
  860. const [name, aliasName] = alias.split(/\s*as\s*/);
  861. icssDefinitions.set(aliasName || name, {
  862. value: name,
  863. path: from
  864. });
  865. }
  866. } else {
  867. const ident = walkCssTokens.eatIdentSequence(alias, 0);
  868. if (!ident) {
  869. this._emitWarning(
  870. state,
  871. `Broken '@value' at-rule: ${input.slice(
  872. start,
  873. atRuleEnd
  874. )}'`,
  875. locConverter,
  876. start,
  877. atRuleEnd
  878. );
  879. const dep = new ConstDependency("", [start, atRuleEnd]);
  880. module.addPresentationalDependency(dep);
  881. return atRuleEnd;
  882. }
  883. const pos = walkCssTokens.eatWhitespaceAndComments(
  884. alias,
  885. ident[1]
  886. );
  887. const name = alias.slice(ident[0], ident[1]);
  888. let value =
  889. alias.charCodeAt(pos) === CC_COLON
  890. ? alias.slice(pos + 1)
  891. : alias.slice(ident[1]);
  892. if (value && !/^\s+$/.test(value)) {
  893. value = value.trim();
  894. }
  895. if (icssDefinitions.has(value)) {
  896. const def =
  897. /** @type {IcssDefinition} */
  898. (icssDefinitions.get(value));
  899. value = def.value;
  900. }
  901. icssDefinitions.set(name, { value });
  902. const dep = new CssIcssExportDependency(name, value);
  903. const { line: sl, column: sc } = locConverter.get(start);
  904. const { line: el, column: ec } = locConverter.get(end);
  905. dep.setLoc(sl, sc, el, ec);
  906. module.addDependency(dep);
  907. }
  908. const dep = new ConstDependency("", [start, atRuleEnd]);
  909. module.addPresentationalDependency(dep);
  910. return atRuleEnd;
  911. } else if (
  912. OPTIONALLY_VENDOR_PREFIXED_KEYFRAMES_AT_RULE.test(name) &&
  913. isLocalMode()
  914. ) {
  915. const ident = walkCssTokens.eatIdentSequenceOrString(
  916. input,
  917. end
  918. );
  919. if (!ident) return end;
  920. const name = unescapeIdentifier(
  921. ident[2] === true
  922. ? input.slice(ident[0], ident[1])
  923. : input.slice(ident[0] + 1, ident[1] - 1)
  924. );
  925. const { line: sl, column: sc } = locConverter.get(ident[0]);
  926. const { line: el, column: ec } = locConverter.get(ident[1]);
  927. const dep = new CssLocalIdentifierDependency(name, [
  928. ident[0],
  929. ident[1]
  930. ]);
  931. dep.setLoc(sl, sc, el, ec);
  932. module.addDependency(dep);
  933. return ident[1];
  934. } else if (name === "@property" && isLocalMode()) {
  935. const ident = walkCssTokens.eatIdentSequence(input, end);
  936. if (!ident) return end;
  937. let name = input.slice(ident[0], ident[1]);
  938. if (!name.startsWith("--") || name.length < 3) return end;
  939. name = unescapeIdentifier(name.slice(2));
  940. declaredCssVariables.add(name);
  941. const { line: sl, column: sc } = locConverter.get(ident[0]);
  942. const { line: el, column: ec } = locConverter.get(ident[1]);
  943. const dep = new CssLocalIdentifierDependency(
  944. name,
  945. [ident[0], ident[1]],
  946. "--"
  947. );
  948. dep.setLoc(sl, sc, el, ec);
  949. module.addDependency(dep);
  950. return ident[1];
  951. } else if (name === "@scope") {
  952. isNextRulePrelude = true;
  953. return end;
  954. }
  955. isNextRulePrelude = false;
  956. }
  957. }
  958. }
  959. return end;
  960. },
  961. semicolon: (input, start, end) => {
  962. if (isModules && scope === CSS_MODE_IN_BLOCK) {
  963. if (isLocalMode()) {
  964. processDeclarationValueDone(input);
  965. inAnimationProperty = false;
  966. }
  967. isNextRulePrelude = isNextNestedSyntax(input, end);
  968. }
  969. return end;
  970. },
  971. identifier: (input, start, end) => {
  972. if (isModules) {
  973. const name = input.slice(start, end);
  974. if (icssDefinitions.has(name)) {
  975. let { path, value } =
  976. /** @type {IcssDefinition} */
  977. (icssDefinitions.get(name));
  978. if (path) {
  979. if (icssDefinitions.has(path)) {
  980. const definition =
  981. /** @type {IcssDefinition} */
  982. (icssDefinitions.get(path));
  983. path = definition.value.slice(1, -1);
  984. }
  985. const dep = new CssIcssImportDependency(path, value, [
  986. start,
  987. end - 1
  988. ]);
  989. const { line: sl, column: sc } = locConverter.get(start);
  990. const { line: el, column: ec } = locConverter.get(end - 1);
  991. dep.setLoc(sl, sc, el, ec);
  992. module.addDependency(dep);
  993. } else {
  994. const { line: sl, column: sc } = locConverter.get(start);
  995. const { line: el, column: ec } = locConverter.get(end);
  996. const dep = new CssIcssSymbolDependency(name, value, [
  997. start,
  998. end
  999. ]);
  1000. dep.setLoc(sl, sc, el, ec);
  1001. module.addDependency(dep);
  1002. }
  1003. return end;
  1004. }
  1005. switch (scope) {
  1006. case CSS_MODE_IN_BLOCK: {
  1007. if (isLocalMode()) {
  1008. // Handle only top level values and not inside functions
  1009. if (inAnimationProperty && balanced.length === 0) {
  1010. lastIdentifier = [start, end, true];
  1011. } else {
  1012. return processLocalDeclaration(input, start, end);
  1013. }
  1014. }
  1015. break;
  1016. }
  1017. }
  1018. }
  1019. return end;
  1020. },
  1021. delim: (input, start, end) => {
  1022. if (isNextRulePrelude && isLocalMode()) {
  1023. const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
  1024. input,
  1025. end
  1026. );
  1027. if (!ident) return end;
  1028. const name = unescapeIdentifier(input.slice(ident[0], ident[1]));
  1029. const dep = new CssLocalIdentifierDependency(name, [
  1030. ident[0],
  1031. ident[1]
  1032. ]);
  1033. const { line: sl, column: sc } = locConverter.get(ident[0]);
  1034. const { line: el, column: ec } = locConverter.get(ident[1]);
  1035. dep.setLoc(sl, sc, el, ec);
  1036. module.addDependency(dep);
  1037. return ident[1];
  1038. }
  1039. return end;
  1040. },
  1041. hash: (input, start, end, isID) => {
  1042. if (isNextRulePrelude && isLocalMode() && isID) {
  1043. const valueStart = start + 1;
  1044. const name = unescapeIdentifier(input.slice(valueStart, end));
  1045. const dep = new CssLocalIdentifierDependency(name, [valueStart, end]);
  1046. const { line: sl, column: sc } = locConverter.get(start);
  1047. const { line: el, column: ec } = locConverter.get(end);
  1048. dep.setLoc(sl, sc, el, ec);
  1049. module.addDependency(dep);
  1050. }
  1051. return end;
  1052. },
  1053. colon: (input, start, end) => {
  1054. if (isModules) {
  1055. const ident = walkCssTokens.skipCommentsAndEatIdentSequence(
  1056. input,
  1057. end
  1058. );
  1059. if (!ident) return end;
  1060. const name = input.slice(ident[0], ident[1]).toLowerCase();
  1061. switch (scope) {
  1062. case CSS_MODE_TOP_LEVEL: {
  1063. if (name === "import") {
  1064. const pos = parseImportOrExport(0, input, ident[1]);
  1065. const dep = new ConstDependency("", [start, pos]);
  1066. module.addPresentationalDependency(dep);
  1067. return pos;
  1068. } else if (name === "export") {
  1069. const pos = parseImportOrExport(1, input, ident[1]);
  1070. const dep = new ConstDependency("", [start, pos]);
  1071. module.addPresentationalDependency(dep);
  1072. return pos;
  1073. }
  1074. }
  1075. // falls through
  1076. default: {
  1077. if (isNextRulePrelude) {
  1078. const isFn = input.charCodeAt(ident[1]) === CC_LEFT_PARENTHESIS;
  1079. if (isFn && name === "local") {
  1080. const end = ident[1] + 1;
  1081. modeData = "local";
  1082. const dep = new ConstDependency("", [start, end]);
  1083. module.addPresentationalDependency(dep);
  1084. balanced.push([":local", start, end]);
  1085. return end;
  1086. } else if (name === "local") {
  1087. modeData = "local";
  1088. // Eat extra whitespace
  1089. end = walkCssTokens.eatWhitespace(input, ident[1]);
  1090. if (ident[1] === end) {
  1091. this._emitWarning(
  1092. state,
  1093. `Missing whitespace after ':local' in '${input.slice(
  1094. start,
  1095. eatUntilLeftCurly(input, end) + 1
  1096. )}'`,
  1097. locConverter,
  1098. start,
  1099. end
  1100. );
  1101. }
  1102. const dep = new ConstDependency("", [start, end]);
  1103. module.addPresentationalDependency(dep);
  1104. return end;
  1105. } else if (isFn && name === "global") {
  1106. const end = ident[1] + 1;
  1107. modeData = "global";
  1108. const dep = new ConstDependency("", [start, end]);
  1109. module.addPresentationalDependency(dep);
  1110. balanced.push([":global", start, end]);
  1111. return end;
  1112. } else if (name === "global") {
  1113. modeData = "global";
  1114. // Eat extra whitespace
  1115. end = walkCssTokens.eatWhitespace(input, ident[1]);
  1116. if (ident[1] === end) {
  1117. this._emitWarning(
  1118. state,
  1119. `Missing whitespace after ':global' in '${input.slice(
  1120. start,
  1121. eatUntilLeftCurly(input, end) + 1
  1122. )}'`,
  1123. locConverter,
  1124. start,
  1125. end
  1126. );
  1127. }
  1128. const dep = new ConstDependency("", [start, end]);
  1129. module.addPresentationalDependency(dep);
  1130. return end;
  1131. }
  1132. }
  1133. }
  1134. }
  1135. }
  1136. lastTokenEndForComments = end;
  1137. return end;
  1138. },
  1139. function: (input, start, end) => {
  1140. const name = input
  1141. .slice(start, end - 1)
  1142. .replace(/\\/g, "")
  1143. .toLowerCase();
  1144. balanced.push([name, start, end]);
  1145. switch (name) {
  1146. case "src":
  1147. case "url": {
  1148. if (!this.url) {
  1149. return end;
  1150. }
  1151. const string = walkCssTokens.eatString(input, end);
  1152. if (!string) return end;
  1153. const { options, errors: commentErrors } = this.parseCommentOptions(
  1154. [lastTokenEndForComments, end]
  1155. );
  1156. if (commentErrors) {
  1157. for (const e of commentErrors) {
  1158. const { comment } = e;
  1159. state.module.addWarning(
  1160. new CommentCompilationWarning(
  1161. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  1162. comment.loc
  1163. )
  1164. );
  1165. }
  1166. }
  1167. if (options && options.webpackIgnore !== undefined) {
  1168. if (typeof options.webpackIgnore !== "boolean") {
  1169. const { line: sl, column: sc } = locConverter.get(string[0]);
  1170. const { line: el, column: ec } = locConverter.get(string[1]);
  1171. state.module.addWarning(
  1172. new UnsupportedFeatureWarning(
  1173. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  1174. {
  1175. start: { line: sl, column: sc },
  1176. end: { line: el, column: ec }
  1177. }
  1178. )
  1179. );
  1180. } else if (options.webpackIgnore) {
  1181. return end;
  1182. }
  1183. }
  1184. const value = normalizeUrl(
  1185. input.slice(string[0] + 1, string[1] - 1),
  1186. true
  1187. );
  1188. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  1189. if (value.length === 0) return end;
  1190. const isUrl = name === "url" || name === "src";
  1191. const dep = new CssUrlDependency(
  1192. value,
  1193. [string[0], string[1]],
  1194. isUrl ? "string" : "url"
  1195. );
  1196. const { line: sl, column: sc } = locConverter.get(string[0]);
  1197. const { line: el, column: ec } = locConverter.get(string[1]);
  1198. dep.setLoc(sl, sc, el, ec);
  1199. module.addDependency(dep);
  1200. module.addCodeGenerationDependency(dep);
  1201. return string[1];
  1202. }
  1203. default: {
  1204. if (this.url && IMAGE_SET_FUNCTION.test(name)) {
  1205. lastTokenEndForComments = end;
  1206. const values = walkCssTokens.eatImageSetStrings(input, end, {
  1207. comment
  1208. });
  1209. if (values.length === 0) return end;
  1210. for (const [index, string] of values.entries()) {
  1211. const value = normalizeUrl(
  1212. input.slice(string[0] + 1, string[1] - 1),
  1213. true
  1214. );
  1215. if (value.length === 0) return end;
  1216. const { options, errors: commentErrors } =
  1217. this.parseCommentOptions([
  1218. index === 0 ? start : values[index - 1][1],
  1219. string[1]
  1220. ]);
  1221. if (commentErrors) {
  1222. for (const e of commentErrors) {
  1223. const { comment } = e;
  1224. state.module.addWarning(
  1225. new CommentCompilationWarning(
  1226. `Compilation error while processing magic comment(-s): /*${comment.value}*/: ${e.message}`,
  1227. comment.loc
  1228. )
  1229. );
  1230. }
  1231. }
  1232. if (options && options.webpackIgnore !== undefined) {
  1233. if (typeof options.webpackIgnore !== "boolean") {
  1234. const { line: sl, column: sc } = locConverter.get(
  1235. string[0]
  1236. );
  1237. const { line: el, column: ec } = locConverter.get(
  1238. string[1]
  1239. );
  1240. state.module.addWarning(
  1241. new UnsupportedFeatureWarning(
  1242. `\`webpackIgnore\` expected a boolean, but received: ${options.webpackIgnore}.`,
  1243. {
  1244. start: { line: sl, column: sc },
  1245. end: { line: el, column: ec }
  1246. }
  1247. )
  1248. );
  1249. } else if (options.webpackIgnore) {
  1250. continue;
  1251. }
  1252. }
  1253. const dep = new CssUrlDependency(
  1254. value,
  1255. [string[0], string[1]],
  1256. "url"
  1257. );
  1258. const { line: sl, column: sc } = locConverter.get(string[0]);
  1259. const { line: el, column: ec } = locConverter.get(string[1]);
  1260. dep.setLoc(sl, sc, el, ec);
  1261. module.addDependency(dep);
  1262. module.addCodeGenerationDependency(dep);
  1263. }
  1264. // Can contain `url()` inside, so let's return end to allow parse them
  1265. return end;
  1266. } else if (isLocalMode()) {
  1267. // Don't rename animation name when we have `var()` function
  1268. if (inAnimationProperty && balanced.length === 1) {
  1269. lastIdentifier = undefined;
  1270. }
  1271. if (name === "var") {
  1272. const customIdent = walkCssTokens.eatIdentSequence(input, end);
  1273. if (!customIdent) return end;
  1274. let name = input.slice(customIdent[0], customIdent[1]);
  1275. // A custom property is any property whose name starts with two dashes (U+002D HYPHEN-MINUS), like --foo.
  1276. // The <custom-property-name> production corresponds to this:
  1277. // it’s defined as any <dashed-ident> (a valid identifier that starts with two dashes),
  1278. // except -- itself, which is reserved for future use by CSS.
  1279. if (!name.startsWith("--") || name.length < 3) return end;
  1280. name = unescapeIdentifier(
  1281. input.slice(customIdent[0] + 2, customIdent[1])
  1282. );
  1283. const afterCustomIdent = walkCssTokens.eatWhitespaceAndComments(
  1284. input,
  1285. customIdent[1]
  1286. );
  1287. if (
  1288. input.charCodeAt(afterCustomIdent) === CC_LOWER_F ||
  1289. input.charCodeAt(afterCustomIdent) === CC_UPPER_F
  1290. ) {
  1291. const fromWord = walkCssTokens.eatIdentSequence(
  1292. input,
  1293. afterCustomIdent
  1294. );
  1295. if (
  1296. !fromWord ||
  1297. input.slice(fromWord[0], fromWord[1]).toLowerCase() !==
  1298. "from"
  1299. ) {
  1300. return end;
  1301. }
  1302. const from = walkCssTokens.eatIdentSequenceOrString(
  1303. input,
  1304. walkCssTokens.eatWhitespaceAndComments(input, fromWord[1])
  1305. );
  1306. if (!from) {
  1307. return end;
  1308. }
  1309. const path = input.slice(from[0], from[1]);
  1310. if (from[2] === true && path === "global") {
  1311. const dep = new ConstDependency("", [
  1312. customIdent[1],
  1313. from[1]
  1314. ]);
  1315. module.addPresentationalDependency(dep);
  1316. return end;
  1317. } else if (from[2] === false) {
  1318. const dep = new CssIcssImportDependency(
  1319. path.slice(1, -1),
  1320. name,
  1321. [customIdent[0], from[1] - 1]
  1322. );
  1323. const { line: sl, column: sc } = locConverter.get(
  1324. customIdent[0]
  1325. );
  1326. const { line: el, column: ec } = locConverter.get(
  1327. from[1] - 1
  1328. );
  1329. dep.setLoc(sl, sc, el, ec);
  1330. module.addDependency(dep);
  1331. }
  1332. } else {
  1333. const { line: sl, column: sc } = locConverter.get(
  1334. customIdent[0]
  1335. );
  1336. const { line: el, column: ec } = locConverter.get(
  1337. customIdent[1]
  1338. );
  1339. const dep = new CssSelfLocalIdentifierDependency(
  1340. name,
  1341. [customIdent[0], customIdent[1]],
  1342. "--",
  1343. declaredCssVariables
  1344. );
  1345. dep.setLoc(sl, sc, el, ec);
  1346. module.addDependency(dep);
  1347. return end;
  1348. }
  1349. }
  1350. }
  1351. }
  1352. }
  1353. return end;
  1354. },
  1355. leftParenthesis: (input, start, end) => {
  1356. balanced.push(["(", start, end]);
  1357. return end;
  1358. },
  1359. rightParenthesis: (input, start, end) => {
  1360. const popped = balanced.pop();
  1361. if (
  1362. isModules &&
  1363. popped &&
  1364. (popped[0] === ":local" || popped[0] === ":global")
  1365. ) {
  1366. modeData = balanced[balanced.length - 1]
  1367. ? /** @type {"local" | "global"} */
  1368. (balanced[balanced.length - 1][0])
  1369. : undefined;
  1370. const dep = new ConstDependency("", [start, end]);
  1371. module.addPresentationalDependency(dep);
  1372. }
  1373. return end;
  1374. },
  1375. comma: (input, start, end) => {
  1376. if (isModules) {
  1377. // Reset stack for `:global .class :local .class-other` selector after
  1378. modeData = undefined;
  1379. if (scope === CSS_MODE_IN_BLOCK && isLocalMode()) {
  1380. processDeclarationValueDone(input);
  1381. }
  1382. }
  1383. lastTokenEndForComments = start;
  1384. return end;
  1385. }
  1386. });
  1387. /** @type {BuildInfo} */
  1388. (module.buildInfo).strict = true;
  1389. /** @type {BuildMeta} */
  1390. (module.buildMeta).exportsType = this.namedExports
  1391. ? "namespace"
  1392. : "default";
  1393. if (!this.namedExports) {
  1394. /** @type {BuildMeta} */
  1395. (module.buildMeta).defaultObject = "redirect";
  1396. }
  1397. module.addDependency(new StaticExportsDependency([], true));
  1398. return state;
  1399. }
  1400. /**
  1401. * @param {Range} range range
  1402. * @returns {Comment[]} comments in the range
  1403. */
  1404. getComments(range) {
  1405. if (!this.comments) return [];
  1406. const [rangeStart, rangeEnd] = range;
  1407. /**
  1408. * @param {Comment} comment comment
  1409. * @param {number} needle needle
  1410. * @returns {number} compared
  1411. */
  1412. const compare = (comment, needle) =>
  1413. /** @type {Range} */ (comment.range)[0] - needle;
  1414. const comments = /** @type {Comment[]} */ (this.comments);
  1415. let idx = binarySearchBounds.ge(comments, rangeStart, compare);
  1416. /** @type {Comment[]} */
  1417. const commentsInRange = [];
  1418. while (
  1419. comments[idx] &&
  1420. /** @type {Range} */ (comments[idx].range)[1] <= rangeEnd
  1421. ) {
  1422. commentsInRange.push(comments[idx]);
  1423. idx++;
  1424. }
  1425. return commentsInRange;
  1426. }
  1427. /**
  1428. * @param {Range} range range of the comment
  1429. * @returns {{ options: Record<string, EXPECTED_ANY> | null, errors: (Error & { comment: Comment })[] | null }} result
  1430. */
  1431. parseCommentOptions(range) {
  1432. const comments = this.getComments(range);
  1433. if (comments.length === 0) {
  1434. return EMPTY_COMMENT_OPTIONS;
  1435. }
  1436. /** @type {Record<string, EXPECTED_ANY> } */
  1437. const options = {};
  1438. /** @type {(Error & { comment: Comment })[]} */
  1439. const errors = [];
  1440. for (const comment of comments) {
  1441. const { value } = comment;
  1442. if (value && webpackCommentRegExp.test(value)) {
  1443. // try compile only if webpack options comment is present
  1444. try {
  1445. for (let [key, val] of Object.entries(
  1446. vm.runInContext(
  1447. `(function(){return {${value}};})()`,
  1448. this.magicCommentContext
  1449. )
  1450. )) {
  1451. if (typeof val === "object" && val !== null) {
  1452. val =
  1453. val.constructor.name === "RegExp"
  1454. ? new RegExp(val)
  1455. : JSON.parse(JSON.stringify(val));
  1456. }
  1457. options[key] = val;
  1458. }
  1459. } catch (err) {
  1460. const newErr = new Error(String(/** @type {Error} */ (err).message));
  1461. newErr.stack = String(/** @type {Error} */ (err).stack);
  1462. Object.assign(newErr, { comment });
  1463. errors.push(/** @type (Error & { comment: Comment }) */ (newErr));
  1464. }
  1465. }
  1466. }
  1467. return { options, errors };
  1468. }
  1469. }
  1470. module.exports = CssParser;
  1471. module.exports.escapeIdentifier = escapeIdentifier;
  1472. module.exports.unescapeIdentifier = unescapeIdentifier;