CssParser.js 45 KB

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