CssParser.js 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. const Parser = require("../Parser");
  7. const ConstDependency = require("../dependencies/ConstDependency");
  8. const CssExportDependency = require("../dependencies/CssExportDependency");
  9. const CssImportDependency = require("../dependencies/CssImportDependency");
  10. const CssLocalIdentifierDependency = require("../dependencies/CssLocalIdentifierDependency");
  11. const CssSelfLocalIdentifierDependency = require("../dependencies/CssSelfLocalIdentifierDependency");
  12. const CssUrlDependency = require("../dependencies/CssUrlDependency");
  13. const StaticExportsDependency = require("../dependencies/StaticExportsDependency");
  14. const walkCssTokens = require("./walkCssTokens");
  15. /** @typedef {import("../Parser").ParserState} ParserState */
  16. /** @typedef {import("../Parser").PreparsedAst} PreparsedAst */
  17. const CC_LEFT_CURLY = "{".charCodeAt(0);
  18. const CC_RIGHT_CURLY = "}".charCodeAt(0);
  19. const CC_COLON = ":".charCodeAt(0);
  20. const CC_SLASH = "/".charCodeAt(0);
  21. const CC_SEMICOLON = ";".charCodeAt(0);
  22. // https://www.w3.org/TR/css-syntax-3/#newline
  23. // We don't have `preprocessing` stage, so we need specify all of them
  24. const STRING_MULTILINE = /\\[\n\r\f]/g;
  25. // https://www.w3.org/TR/css-syntax-3/#whitespace
  26. const TRIM_WHITE_SPACES = /(^[ \t\n\r\f]*|[ \t\n\r\f]*$)/g;
  27. const UNESCAPE = /\\([0-9a-fA-F]{1,6}[ \t\n\r\f]?|[\s\S])/g;
  28. const IMAGE_SET_FUNCTION = /^(-\w+-)?image-set$/i;
  29. const normalizeUrl = (str, isString) => {
  30. // Remove extra spaces and newlines:
  31. // `url("im\
  32. // g.png")`
  33. if (isString) {
  34. str = str.replace(STRING_MULTILINE, "");
  35. }
  36. str = str
  37. // Remove unnecessary spaces from `url(" img.png ")`
  38. .replace(TRIM_WHITE_SPACES, "")
  39. // Unescape
  40. .replace(UNESCAPE, match => {
  41. if (match.length > 2) {
  42. return String.fromCharCode(parseInt(match.slice(1).trim(), 16));
  43. } else {
  44. return match[1];
  45. }
  46. });
  47. if (/^data:/i.test(str)) {
  48. return str;
  49. }
  50. if (str.includes("%")) {
  51. // Convert `url('%2E/img.png')` -> `url('./img.png')`
  52. try {
  53. str = decodeURIComponent(str);
  54. } catch (error) {
  55. // Ignore
  56. }
  57. }
  58. return str;
  59. };
  60. class LocConverter {
  61. constructor(input) {
  62. this._input = input;
  63. this.line = 1;
  64. this.column = 0;
  65. this.pos = 0;
  66. }
  67. get(pos) {
  68. if (this.pos !== pos) {
  69. if (this.pos < pos) {
  70. const str = this._input.slice(this.pos, pos);
  71. let i = str.lastIndexOf("\n");
  72. if (i === -1) {
  73. this.column += str.length;
  74. } else {
  75. this.column = str.length - i - 1;
  76. this.line++;
  77. while (i > 0 && (i = str.lastIndexOf("\n", i - 1)) !== -1)
  78. this.line++;
  79. }
  80. } else {
  81. let i = this._input.lastIndexOf("\n", this.pos);
  82. while (i >= pos) {
  83. this.line--;
  84. i = i > 0 ? this._input.lastIndexOf("\n", i - 1) : -1;
  85. }
  86. this.column = pos - i;
  87. }
  88. this.pos = pos;
  89. }
  90. return this;
  91. }
  92. }
  93. const CSS_MODE_TOP_LEVEL = 0;
  94. const CSS_MODE_IN_RULE = 1;
  95. const CSS_MODE_IN_LOCAL_RULE = 2;
  96. const CSS_MODE_AT_IMPORT_EXPECT_URL = 3;
  97. // TODO implement layer and supports for @import
  98. const CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS = 4;
  99. const CSS_MODE_AT_IMPORT_EXPECT_MEDIA = 5;
  100. const CSS_MODE_AT_OTHER = 6;
  101. const explainMode = mode => {
  102. switch (mode) {
  103. case CSS_MODE_TOP_LEVEL:
  104. return "parsing top level css";
  105. case CSS_MODE_IN_RULE:
  106. return "parsing css rule content (global)";
  107. case CSS_MODE_IN_LOCAL_RULE:
  108. return "parsing css rule content (local)";
  109. case CSS_MODE_AT_IMPORT_EXPECT_URL:
  110. return "parsing @import (expecting url)";
  111. case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS:
  112. return "parsing @import (expecting optionally supports or media query)";
  113. case CSS_MODE_AT_IMPORT_EXPECT_MEDIA:
  114. return "parsing @import (expecting optionally media query)";
  115. case CSS_MODE_AT_OTHER:
  116. return "parsing at-rule";
  117. default:
  118. return mode;
  119. }
  120. };
  121. class CssParser extends Parser {
  122. constructor({
  123. allowPseudoBlocks = true,
  124. allowModeSwitch = true,
  125. defaultMode = "global"
  126. } = {}) {
  127. super();
  128. this.allowPseudoBlocks = allowPseudoBlocks;
  129. this.allowModeSwitch = allowModeSwitch;
  130. this.defaultMode = defaultMode;
  131. }
  132. /**
  133. * @param {string | Buffer | PreparsedAst} source the source to parse
  134. * @param {ParserState} state the parser state
  135. * @returns {ParserState} the parser state
  136. */
  137. parse(source, state) {
  138. if (Buffer.isBuffer(source)) {
  139. source = source.toString("utf-8");
  140. } else if (typeof source === "object") {
  141. throw new Error("webpackAst is unexpected for the CssParser");
  142. }
  143. if (source[0] === "\ufeff") {
  144. source = source.slice(1);
  145. }
  146. const module = state.module;
  147. const declaredCssVariables = new Set();
  148. const locConverter = new LocConverter(source);
  149. let mode = CSS_MODE_TOP_LEVEL;
  150. let modePos = 0;
  151. let modeNestingLevel = 0;
  152. let modeData = undefined;
  153. let singleClassSelector = undefined;
  154. let lastIdentifier = undefined;
  155. let awaitRightParenthesis = false;
  156. /** @type [string, number, number][] */
  157. const functionStack = [];
  158. const modeStack = [];
  159. const isTopLevelLocal = () =>
  160. modeData === "local" ||
  161. (this.defaultMode === "local" && modeData === undefined);
  162. const eatWhiteLine = (input, pos) => {
  163. for (;;) {
  164. const cc = input.charCodeAt(pos);
  165. if (cc === 32 || cc === 9) {
  166. pos++;
  167. continue;
  168. }
  169. if (cc === 10) pos++;
  170. break;
  171. }
  172. return pos;
  173. };
  174. const eatUntil = chars => {
  175. const charCodes = Array.from({ length: chars.length }, (_, i) =>
  176. chars.charCodeAt(i)
  177. );
  178. const arr = Array.from(
  179. { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 },
  180. () => false
  181. );
  182. charCodes.forEach(cc => (arr[cc] = true));
  183. return (input, pos) => {
  184. for (;;) {
  185. const cc = input.charCodeAt(pos);
  186. if (cc < arr.length && arr[cc]) {
  187. return pos;
  188. }
  189. pos++;
  190. if (pos === input.length) return pos;
  191. }
  192. };
  193. };
  194. const eatText = (input, pos, eater) => {
  195. let text = "";
  196. for (;;) {
  197. if (input.charCodeAt(pos) === CC_SLASH) {
  198. const newPos = walkCssTokens.eatComments(input, pos);
  199. if (pos !== newPos) {
  200. pos = newPos;
  201. if (pos === input.length) break;
  202. } else {
  203. text += "/";
  204. pos++;
  205. if (pos === input.length) break;
  206. }
  207. }
  208. const newPos = eater(input, pos);
  209. if (pos !== newPos) {
  210. text += input.slice(pos, newPos);
  211. pos = newPos;
  212. } else {
  213. break;
  214. }
  215. if (pos === input.length) break;
  216. }
  217. return [pos, text.trimEnd()];
  218. };
  219. const eatExportName = eatUntil(":};/");
  220. const eatExportValue = eatUntil("};/");
  221. const parseExports = (input, pos) => {
  222. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  223. const cc = input.charCodeAt(pos);
  224. if (cc !== CC_LEFT_CURLY)
  225. throw new Error(
  226. `Unexpected ${input[pos]} at ${pos} during parsing of ':export' (expected '{')`
  227. );
  228. pos++;
  229. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  230. for (;;) {
  231. if (input.charCodeAt(pos) === CC_RIGHT_CURLY) break;
  232. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  233. if (pos === input.length) return pos;
  234. let start = pos;
  235. let name;
  236. [pos, name] = eatText(input, pos, eatExportName);
  237. if (pos === input.length) return pos;
  238. if (input.charCodeAt(pos) !== CC_COLON) {
  239. throw new Error(
  240. `Unexpected ${input[pos]} at ${pos} during parsing of export name in ':export' (expected ':')`
  241. );
  242. }
  243. pos++;
  244. if (pos === input.length) return pos;
  245. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  246. if (pos === input.length) return pos;
  247. let value;
  248. [pos, value] = eatText(input, pos, eatExportValue);
  249. if (pos === input.length) return pos;
  250. const cc = input.charCodeAt(pos);
  251. if (cc === CC_SEMICOLON) {
  252. pos++;
  253. if (pos === input.length) return pos;
  254. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  255. if (pos === input.length) return pos;
  256. } else if (cc !== CC_RIGHT_CURLY) {
  257. throw new Error(
  258. `Unexpected ${input[pos]} at ${pos} during parsing of export value in ':export' (expected ';' or '}')`
  259. );
  260. }
  261. const dep = new CssExportDependency(name, value);
  262. const { line: sl, column: sc } = locConverter.get(start);
  263. const { line: el, column: ec } = locConverter.get(pos);
  264. dep.setLoc(sl, sc, el, ec);
  265. module.addDependency(dep);
  266. }
  267. pos++;
  268. if (pos === input.length) return pos;
  269. pos = eatWhiteLine(input, pos);
  270. return pos;
  271. };
  272. const eatPropertyName = eatUntil(":{};");
  273. const processLocalDeclaration = (input, pos) => {
  274. modeData = undefined;
  275. const start = pos;
  276. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  277. const propertyNameStart = pos;
  278. const [propertyNameEnd, propertyName] = eatText(
  279. input,
  280. pos,
  281. eatPropertyName
  282. );
  283. if (input.charCodeAt(propertyNameEnd) !== CC_COLON) return start;
  284. pos = propertyNameEnd + 1;
  285. if (propertyName.startsWith("--")) {
  286. // CSS Variable
  287. const { line: sl, column: sc } = locConverter.get(propertyNameStart);
  288. const { line: el, column: ec } = locConverter.get(propertyNameEnd);
  289. const name = propertyName.slice(2);
  290. const dep = new CssLocalIdentifierDependency(
  291. name,
  292. [propertyNameStart, propertyNameEnd],
  293. "--"
  294. );
  295. dep.setLoc(sl, sc, el, ec);
  296. module.addDependency(dep);
  297. declaredCssVariables.add(name);
  298. } else if (
  299. propertyName.toLowerCase() === "animation-name" ||
  300. propertyName.toLowerCase() === "animation"
  301. ) {
  302. modeData = "animation";
  303. lastIdentifier = undefined;
  304. }
  305. return pos;
  306. };
  307. const processDeclarationValueDone = (input, pos) => {
  308. if (modeData === "animation" && lastIdentifier) {
  309. const { line: sl, column: sc } = locConverter.get(lastIdentifier[0]);
  310. const { line: el, column: ec } = locConverter.get(lastIdentifier[1]);
  311. const name = input.slice(lastIdentifier[0], lastIdentifier[1]);
  312. const dep = new CssSelfLocalIdentifierDependency(name, lastIdentifier);
  313. dep.setLoc(sl, sc, el, ec);
  314. module.addDependency(dep);
  315. }
  316. };
  317. const eatAtRuleNested = eatUntil("{};/");
  318. const eatKeyframes = eatUntil("{};/");
  319. const eatNameInVar = eatUntil(",)};/");
  320. walkCssTokens(source, {
  321. isSelector: () => {
  322. return mode !== CSS_MODE_IN_RULE && mode !== CSS_MODE_IN_LOCAL_RULE;
  323. },
  324. url: (input, start, end, contentStart, contentEnd, isString) => {
  325. let value = normalizeUrl(
  326. input.slice(contentStart, contentEnd),
  327. isString
  328. );
  329. switch (mode) {
  330. case CSS_MODE_AT_IMPORT_EXPECT_URL: {
  331. modeData.url = value;
  332. mode = CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS;
  333. break;
  334. }
  335. case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS:
  336. case CSS_MODE_AT_IMPORT_EXPECT_MEDIA:
  337. throw new Error(
  338. `Unexpected ${input.slice(
  339. start,
  340. end
  341. )} at ${start} during ${explainMode(mode)}`
  342. );
  343. default: {
  344. if (
  345. // Ignore `url(#highlight)` URLs
  346. /^#/.test(value) ||
  347. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  348. value.length === 0
  349. ) {
  350. break;
  351. }
  352. const dep = new CssUrlDependency(value, [start, end], "url");
  353. const { line: sl, column: sc } = locConverter.get(start);
  354. const { line: el, column: ec } = locConverter.get(end);
  355. dep.setLoc(sl, sc, el, ec);
  356. module.addDependency(dep);
  357. module.addCodeGenerationDependency(dep);
  358. break;
  359. }
  360. }
  361. return end;
  362. },
  363. string: (input, start, end) => {
  364. switch (mode) {
  365. case CSS_MODE_AT_IMPORT_EXPECT_URL: {
  366. modeData.url = normalizeUrl(input.slice(start + 1, end - 1), true);
  367. mode = CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS;
  368. break;
  369. }
  370. default: {
  371. // TODO move escaped parsing to tokenizer
  372. const lastFunction = functionStack[functionStack.length - 1];
  373. if (
  374. lastFunction &&
  375. (lastFunction[0].replace(/\\/g, "").toLowerCase() === "url" ||
  376. IMAGE_SET_FUNCTION.test(lastFunction[0].replace(/\\/g, "")))
  377. ) {
  378. let value = normalizeUrl(input.slice(start + 1, end - 1), true);
  379. if (
  380. // Ignore `url(#highlight)` URLs
  381. /^#/.test(value) ||
  382. // Ignore `url()`, `url('')` and `url("")`, they are valid by spec
  383. value.length === 0
  384. ) {
  385. break;
  386. }
  387. const isUrl =
  388. lastFunction[0].replace(/\\/g, "").toLowerCase() === "url";
  389. const dep = new CssUrlDependency(
  390. value,
  391. [start, end],
  392. isUrl ? "string" : "url"
  393. );
  394. const { line: sl, column: sc } = locConverter.get(start);
  395. const { line: el, column: ec } = locConverter.get(end);
  396. dep.setLoc(sl, sc, el, ec);
  397. module.addDependency(dep);
  398. module.addCodeGenerationDependency(dep);
  399. }
  400. }
  401. }
  402. return end;
  403. },
  404. atKeyword: (input, start, end) => {
  405. const name = input.slice(start, end).toLowerCase();
  406. if (name === "@namespace") {
  407. throw new Error("@namespace is not supported in bundled CSS");
  408. }
  409. if (name === "@import") {
  410. if (mode !== CSS_MODE_TOP_LEVEL) {
  411. throw new Error(
  412. `Unexpected @import at ${start} during ${explainMode(mode)}`
  413. );
  414. }
  415. mode = CSS_MODE_AT_IMPORT_EXPECT_URL;
  416. modePos = end;
  417. modeData = {
  418. start: start,
  419. url: undefined,
  420. supports: undefined
  421. };
  422. }
  423. if (name === "@keyframes") {
  424. let pos = end;
  425. pos = walkCssTokens.eatWhitespaceAndComments(input, pos);
  426. if (pos === input.length) return pos;
  427. const [newPos, name] = eatText(input, pos, eatKeyframes);
  428. const { line: sl, column: sc } = locConverter.get(pos);
  429. const { line: el, column: ec } = locConverter.get(newPos);
  430. const dep = new CssLocalIdentifierDependency(name, [pos, newPos]);
  431. dep.setLoc(sl, sc, el, ec);
  432. module.addDependency(dep);
  433. pos = newPos;
  434. if (pos === input.length) return pos;
  435. if (input.charCodeAt(pos) !== CC_LEFT_CURLY) {
  436. throw new Error(
  437. `Unexpected ${input[pos]} at ${pos} during parsing of @keyframes (expected '{')`
  438. );
  439. }
  440. mode = CSS_MODE_IN_LOCAL_RULE;
  441. modeNestingLevel = 1;
  442. return pos + 1;
  443. }
  444. if (name === "@media" || name === "@supports") {
  445. let pos = end;
  446. const [newPos] = eatText(input, pos, eatAtRuleNested);
  447. pos = newPos;
  448. if (pos === input.length) return pos;
  449. if (input.charCodeAt(pos) !== CC_LEFT_CURLY) {
  450. throw new Error(
  451. `Unexpected ${input[pos]} at ${pos} during parsing of @media or @supports (expected '{')`
  452. );
  453. }
  454. return pos + 1;
  455. }
  456. return end;
  457. },
  458. semicolon: (input, start, end) => {
  459. switch (mode) {
  460. case CSS_MODE_AT_IMPORT_EXPECT_URL:
  461. throw new Error(`Expected URL for @import at ${start}`);
  462. case CSS_MODE_AT_IMPORT_EXPECT_MEDIA:
  463. case CSS_MODE_AT_IMPORT_EXPECT_SUPPORTS: {
  464. const { line: sl, column: sc } = locConverter.get(modeData.start);
  465. const { line: el, column: ec } = locConverter.get(end);
  466. end = eatWhiteLine(input, end);
  467. const media = input.slice(modePos, start).trim();
  468. const dep = new CssImportDependency(
  469. modeData.url,
  470. [modeData.start, end],
  471. modeData.supports,
  472. media
  473. );
  474. dep.setLoc(sl, sc, el, ec);
  475. module.addDependency(dep);
  476. break;
  477. }
  478. case CSS_MODE_IN_LOCAL_RULE: {
  479. processDeclarationValueDone(input, start);
  480. return processLocalDeclaration(input, end);
  481. }
  482. case CSS_MODE_IN_RULE: {
  483. return end;
  484. }
  485. }
  486. mode = CSS_MODE_TOP_LEVEL;
  487. modeData = undefined;
  488. singleClassSelector = undefined;
  489. return end;
  490. },
  491. leftCurlyBracket: (input, start, end) => {
  492. switch (mode) {
  493. case CSS_MODE_TOP_LEVEL:
  494. mode = isTopLevelLocal()
  495. ? CSS_MODE_IN_LOCAL_RULE
  496. : CSS_MODE_IN_RULE;
  497. modeNestingLevel = 1;
  498. if (mode === CSS_MODE_IN_LOCAL_RULE)
  499. return processLocalDeclaration(input, end);
  500. break;
  501. case CSS_MODE_IN_RULE:
  502. case CSS_MODE_IN_LOCAL_RULE:
  503. modeNestingLevel++;
  504. break;
  505. }
  506. return end;
  507. },
  508. rightCurlyBracket: (input, start, end) => {
  509. switch (mode) {
  510. case CSS_MODE_IN_LOCAL_RULE:
  511. processDeclarationValueDone(input, start);
  512. /* falls through */
  513. case CSS_MODE_IN_RULE:
  514. if (--modeNestingLevel === 0) {
  515. mode = CSS_MODE_TOP_LEVEL;
  516. modeData = undefined;
  517. singleClassSelector = undefined;
  518. }
  519. break;
  520. }
  521. return end;
  522. },
  523. id: (input, start, end) => {
  524. singleClassSelector = false;
  525. switch (mode) {
  526. case CSS_MODE_TOP_LEVEL:
  527. if (isTopLevelLocal()) {
  528. const name = input.slice(start + 1, end);
  529. const dep = new CssLocalIdentifierDependency(name, [
  530. start + 1,
  531. end
  532. ]);
  533. const { line: sl, column: sc } = locConverter.get(start);
  534. const { line: el, column: ec } = locConverter.get(end);
  535. dep.setLoc(sl, sc, el, ec);
  536. module.addDependency(dep);
  537. }
  538. break;
  539. }
  540. return end;
  541. },
  542. identifier: (input, start, end) => {
  543. singleClassSelector = false;
  544. switch (mode) {
  545. case CSS_MODE_IN_LOCAL_RULE:
  546. if (modeData === "animation") {
  547. lastIdentifier = [start, end];
  548. }
  549. break;
  550. }
  551. return end;
  552. },
  553. class: (input, start, end) => {
  554. switch (mode) {
  555. case CSS_MODE_TOP_LEVEL: {
  556. if (isTopLevelLocal()) {
  557. const name = input.slice(start + 1, end);
  558. const dep = new CssLocalIdentifierDependency(name, [
  559. start + 1,
  560. end
  561. ]);
  562. const { line: sl, column: sc } = locConverter.get(start);
  563. const { line: el, column: ec } = locConverter.get(end);
  564. dep.setLoc(sl, sc, el, ec);
  565. module.addDependency(dep);
  566. if (singleClassSelector === undefined) singleClassSelector = name;
  567. } else {
  568. singleClassSelector = false;
  569. }
  570. break;
  571. }
  572. }
  573. return end;
  574. },
  575. leftParenthesis: (input, start, end) => {
  576. switch (mode) {
  577. case CSS_MODE_TOP_LEVEL: {
  578. modeStack.push(false);
  579. break;
  580. }
  581. }
  582. return end;
  583. },
  584. rightParenthesis: (input, start, end) => {
  585. functionStack.pop();
  586. switch (mode) {
  587. case CSS_MODE_TOP_LEVEL: {
  588. if (awaitRightParenthesis) {
  589. awaitRightParenthesis = false;
  590. }
  591. const newModeData = modeStack.pop();
  592. if (newModeData !== false) {
  593. modeData = newModeData;
  594. const dep = new ConstDependency("", [start, end]);
  595. module.addPresentationalDependency(dep);
  596. }
  597. break;
  598. }
  599. }
  600. return end;
  601. },
  602. pseudoClass: (input, start, end) => {
  603. singleClassSelector = false;
  604. switch (mode) {
  605. case CSS_MODE_TOP_LEVEL: {
  606. const name = input.slice(start, end).toLowerCase();
  607. if (this.allowModeSwitch && name === ":global") {
  608. modeData = "global";
  609. const dep = new ConstDependency("", [start, end]);
  610. module.addPresentationalDependency(dep);
  611. } else if (this.allowModeSwitch && name === ":local") {
  612. modeData = "local";
  613. const dep = new ConstDependency("", [start, end]);
  614. module.addPresentationalDependency(dep);
  615. } else if (this.allowPseudoBlocks && name === ":export") {
  616. const pos = parseExports(input, end);
  617. const dep = new ConstDependency("", [start, pos]);
  618. module.addPresentationalDependency(dep);
  619. return pos;
  620. }
  621. break;
  622. }
  623. }
  624. return end;
  625. },
  626. pseudoFunction: (input, start, end) => {
  627. let name = input.slice(start, end - 1);
  628. functionStack.push([name, start, end]);
  629. switch (mode) {
  630. case CSS_MODE_TOP_LEVEL: {
  631. name = name.toLowerCase();
  632. if (this.allowModeSwitch && name === ":global") {
  633. modeStack.push(modeData);
  634. modeData = "global";
  635. const dep = new ConstDependency("", [start, end]);
  636. module.addPresentationalDependency(dep);
  637. } else if (this.allowModeSwitch && name === ":local") {
  638. modeStack.push(modeData);
  639. modeData = "local";
  640. const dep = new ConstDependency("", [start, end]);
  641. module.addPresentationalDependency(dep);
  642. } else {
  643. awaitRightParenthesis = true;
  644. modeStack.push(false);
  645. }
  646. break;
  647. }
  648. }
  649. return end;
  650. },
  651. function: (input, start, end) => {
  652. let name = input.slice(start, end - 1);
  653. functionStack.push([name, start, end]);
  654. switch (mode) {
  655. case CSS_MODE_IN_LOCAL_RULE: {
  656. name = name.toLowerCase();
  657. if (name === "var") {
  658. let pos = walkCssTokens.eatWhitespaceAndComments(input, end);
  659. if (pos === input.length) return pos;
  660. const [newPos, name] = eatText(input, pos, eatNameInVar);
  661. if (!name.startsWith("--")) return end;
  662. const { line: sl, column: sc } = locConverter.get(pos);
  663. const { line: el, column: ec } = locConverter.get(newPos);
  664. const dep = new CssSelfLocalIdentifierDependency(
  665. name.slice(2),
  666. [pos, newPos],
  667. "--",
  668. declaredCssVariables
  669. );
  670. dep.setLoc(sl, sc, el, ec);
  671. module.addDependency(dep);
  672. return newPos;
  673. }
  674. break;
  675. }
  676. }
  677. return end;
  678. },
  679. comma: (input, start, end) => {
  680. switch (mode) {
  681. case CSS_MODE_TOP_LEVEL:
  682. if (!awaitRightParenthesis) {
  683. modeData = undefined;
  684. modeStack.length = 0;
  685. }
  686. break;
  687. case CSS_MODE_IN_LOCAL_RULE:
  688. processDeclarationValueDone(input, start);
  689. break;
  690. }
  691. return end;
  692. }
  693. });
  694. module.buildInfo.strict = true;
  695. module.buildMeta.exportsType = "namespace";
  696. module.addDependency(new StaticExportsDependency([], true));
  697. return state;
  698. }
  699. }
  700. module.exports = CssParser;