walkCssTokens.js 46 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /**
  7. * @typedef {object} CssTokenCallbacks
  8. * @property {((input: string, start: number, end: number, innerStart: number, innerEnd: number) => number)=} url
  9. * @property {((input: string, start: number, end: number) => number)=} comment
  10. * @property {((input: string, start: number, end: number) => number)=} string
  11. * @property {((input: string, start: number, end: number) => number)=} leftParenthesis
  12. * @property {((input: string, start: number, end: number) => number)=} rightParenthesis
  13. * @property {((input: string, start: number, end: number) => number)=} function
  14. * @property {((input: string, start: number, end: number) => number)=} colon
  15. * @property {((input: string, start: number, end: number) => number)=} atKeyword
  16. * @property {((input: string, start: number, end: number) => number)=} delim
  17. * @property {((input: string, start: number, end: number) => number)=} identifier
  18. * @property {((input: string, start: number, end: number, isId: boolean) => number)=} hash
  19. * @property {((input: string, start: number, end: number) => number)=} leftCurlyBracket
  20. * @property {((input: string, start: number, end: number) => number)=} rightCurlyBracket
  21. * @property {((input: string, start: number, end: number) => number)=} semicolon
  22. * @property {((input: string, start: number, end: number) => number)=} comma
  23. * @property {(() => boolean)=} needTerminate
  24. */
  25. /** @typedef {(input: string, pos: number, callbacks: CssTokenCallbacks) => number} CharHandler */
  26. // spec: https://drafts.csswg.org/css-syntax/
  27. const CC_LINE_FEED = "\n".charCodeAt(0);
  28. const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
  29. const CC_FORM_FEED = "\f".charCodeAt(0);
  30. const CC_TAB = "\t".charCodeAt(0);
  31. const CC_SPACE = " ".charCodeAt(0);
  32. const CC_SOLIDUS = "/".charCodeAt(0);
  33. const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0);
  34. const CC_ASTERISK = "*".charCodeAt(0);
  35. const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
  36. const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
  37. const CC_LEFT_CURLY = "{".charCodeAt(0);
  38. const CC_RIGHT_CURLY = "}".charCodeAt(0);
  39. const CC_LEFT_SQUARE = "[".charCodeAt(0);
  40. const CC_RIGHT_SQUARE = "]".charCodeAt(0);
  41. const CC_QUOTATION_MARK = '"'.charCodeAt(0);
  42. const CC_APOSTROPHE = "'".charCodeAt(0);
  43. const CC_FULL_STOP = ".".charCodeAt(0);
  44. const CC_COLON = ":".charCodeAt(0);
  45. const CC_SEMICOLON = ";".charCodeAt(0);
  46. const CC_COMMA = ",".charCodeAt(0);
  47. const CC_PERCENTAGE = "%".charCodeAt(0);
  48. const CC_AT_SIGN = "@".charCodeAt(0);
  49. const CC_LOW_LINE = "_".charCodeAt(0);
  50. const CC_LOWER_A = "a".charCodeAt(0);
  51. const CC_LOWER_F = "f".charCodeAt(0);
  52. const CC_LOWER_E = "e".charCodeAt(0);
  53. const CC_LOWER_U = "u".charCodeAt(0);
  54. const CC_LOWER_Z = "z".charCodeAt(0);
  55. const CC_UPPER_A = "A".charCodeAt(0);
  56. const CC_UPPER_F = "F".charCodeAt(0);
  57. const CC_UPPER_E = "E".charCodeAt(0);
  58. const CC_UPPER_U = "E".charCodeAt(0);
  59. const CC_UPPER_Z = "Z".charCodeAt(0);
  60. const CC_0 = "0".charCodeAt(0);
  61. const CC_9 = "9".charCodeAt(0);
  62. const CC_NUMBER_SIGN = "#".charCodeAt(0);
  63. const CC_PLUS_SIGN = "+".charCodeAt(0);
  64. const CC_HYPHEN_MINUS = "-".charCodeAt(0);
  65. const CC_LESS_THAN_SIGN = "<".charCodeAt(0);
  66. const CC_GREATER_THAN_SIGN = ">".charCodeAt(0);
  67. /** @type {CharHandler} */
  68. const consumeSpace = (input, pos, _callbacks) => {
  69. // Consume as much whitespace as possible.
  70. while (_isWhiteSpace(input.charCodeAt(pos))) {
  71. pos++;
  72. }
  73. // Return a <whitespace-token>.
  74. return pos;
  75. };
  76. // U+000A LINE FEED. Note that U+000D CARRIAGE RETURN and U+000C FORM FEED are not included in this definition,
  77. // as they are converted to U+000A LINE FEED during preprocessing.
  78. //
  79. // Replace any U+000D CARRIAGE RETURN (CR) code points, U+000C FORM FEED (FF) code points, or pairs of U+000D CARRIAGE RETURN (CR) followed by U+000A LINE FEED (LF) in input by a single U+000A LINE FEED (LF) code point.
  80. /**
  81. * @param {number} cc char code
  82. * @returns {boolean} true, if cc is a newline
  83. */
  84. const _isNewline = cc =>
  85. cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED;
  86. /**
  87. * @param {number} cc char code
  88. * @param {string} input input
  89. * @param {number} pos position
  90. * @returns {number} position
  91. */
  92. const consumeExtraNewline = (cc, input, pos) => {
  93. if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) {
  94. pos++;
  95. }
  96. return pos;
  97. };
  98. /**
  99. * @param {number} cc char code
  100. * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE)
  101. */
  102. const _isSpace = cc => cc === CC_TAB || cc === CC_SPACE;
  103. /**
  104. * @param {number} cc char code
  105. * @returns {boolean} true, if cc is a whitespace
  106. */
  107. const _isWhiteSpace = cc => _isNewline(cc) || _isSpace(cc);
  108. /**
  109. * ident-start code point
  110. *
  111. * A letter, a non-ASCII code point, or U+005F LOW LINE (_).
  112. * @param {number} cc char code
  113. * @returns {boolean} true, if cc is a start code point of an identifier
  114. */
  115. const isIdentStartCodePoint = cc =>
  116. (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
  117. (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
  118. cc === CC_LOW_LINE ||
  119. cc >= 0x80;
  120. /** @type {CharHandler} */
  121. const consumeDelimToken = (input, pos, _callbacks) =>
  122. // Return a <delim-token> with its value set to the current input code point.
  123. pos;
  124. /** @type {CharHandler} */
  125. const consumeComments = (input, pos, callbacks) => {
  126. // This section describes how to consume comments from a stream of code points. It returns nothing.
  127. // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A ASTERISK (*),
  128. // consume them and all following code points up to and including the first U+002A ASTERISK (*)
  129. // followed by a U+002F SOLIDUS (/), or up to an EOF code point.
  130. // Return to the start of this step.
  131. while (
  132. input.charCodeAt(pos) === CC_SOLIDUS &&
  133. input.charCodeAt(pos + 1) === CC_ASTERISK
  134. ) {
  135. const start = pos;
  136. pos += 2;
  137. for (;;) {
  138. if (pos === input.length) {
  139. // If the preceding paragraph ended by consuming an EOF code point, this is a parse error.
  140. return pos;
  141. }
  142. if (
  143. input.charCodeAt(pos) === CC_ASTERISK &&
  144. input.charCodeAt(pos + 1) === CC_SOLIDUS
  145. ) {
  146. pos += 2;
  147. if (callbacks.comment) {
  148. pos = callbacks.comment(input, start, pos);
  149. }
  150. break;
  151. }
  152. pos++;
  153. }
  154. }
  155. return pos;
  156. };
  157. /**
  158. * @param {number} cc char code
  159. * @returns {boolean} true, if cc is a hex digit
  160. */
  161. const _isHexDigit = cc =>
  162. _isDigit(cc) ||
  163. (cc >= CC_UPPER_A && cc <= CC_UPPER_F) ||
  164. (cc >= CC_LOWER_A && cc <= CC_LOWER_F);
  165. /**
  166. * @param {string} input input
  167. * @param {number} pos position
  168. * @returns {number} position
  169. */
  170. const _consumeAnEscapedCodePoint = (input, pos) => {
  171. // This section describes how to consume an escaped code point.
  172. // It assumes that the U+005C REVERSE SOLIDUS (\) has already been consumed and that the next input code point has already been verified to be part of a valid escape.
  173. // It will return a code point.
  174. // Consume the next input code point.
  175. const cc = input.charCodeAt(pos);
  176. pos++;
  177. // EOF
  178. // This is a parse error. Return U+FFFD REPLACEMENT CHARACTER (�).
  179. if (pos === input.length) {
  180. return pos;
  181. }
  182. // hex digit
  183. // Consume as many hex digits as possible, but no more than 5.
  184. // Note that this means 1-6 hex digits have been consumed in total.
  185. // If the next input code point is whitespace, consume it as well.
  186. // Interpret the hex digits as a hexadecimal number.
  187. // If this number is zero, or is for a surrogate, or is greater than the maximum allowed code point, return U+FFFD REPLACEMENT CHARACTER (�).
  188. // Otherwise, return the code point with that value.
  189. if (_isHexDigit(cc)) {
  190. for (let i = 0; i < 5; i++) {
  191. if (_isHexDigit(input.charCodeAt(pos))) {
  192. pos++;
  193. }
  194. }
  195. const cc = input.charCodeAt(pos);
  196. if (_isWhiteSpace(cc)) {
  197. pos++;
  198. pos = consumeExtraNewline(cc, input, pos);
  199. }
  200. return pos;
  201. }
  202. // anything else
  203. // Return the current input code point.
  204. return pos;
  205. };
  206. /** @type {CharHandler} */
  207. const consumeAStringToken = (input, pos, callbacks) => {
  208. // This section describes how to consume a string token from a stream of code points.
  209. // It returns either a <string-token> or <bad-string-token>.
  210. //
  211. // This algorithm may be called with an ending code point, which denotes the code point that ends the string.
  212. // If an ending code point is not specified, the current input code point is used.
  213. const start = pos - 1;
  214. const endingCodePoint = input.charCodeAt(pos - 1);
  215. // Initially create a <string-token> with its value set to the empty string.
  216. // Repeatedly consume the next input code point from the stream:
  217. for (;;) {
  218. // EOF
  219. // This is a parse error. Return the <string-token>.
  220. if (pos === input.length) {
  221. if (callbacks.string !== undefined) {
  222. return callbacks.string(input, start, pos);
  223. }
  224. return pos;
  225. }
  226. const cc = input.charCodeAt(pos);
  227. pos++;
  228. // ending code point
  229. // Return the <string-token>.
  230. if (cc === endingCodePoint) {
  231. if (callbacks.string !== undefined) {
  232. return callbacks.string(input, start, pos);
  233. }
  234. return pos;
  235. }
  236. // newline
  237. // This is a parse error.
  238. // Reconsume the current input code point, create a <bad-string-token>, and return it.
  239. else if (_isNewline(cc)) {
  240. pos--;
  241. // bad string
  242. return pos;
  243. }
  244. // U+005C REVERSE SOLIDUS (\)
  245. else if (cc === CC_REVERSE_SOLIDUS) {
  246. // If the next input code point is EOF, do nothing.
  247. if (pos === input.length) {
  248. return pos;
  249. }
  250. // Otherwise, if the next input code point is a newline, consume it.
  251. else if (_isNewline(input.charCodeAt(pos))) {
  252. const cc = input.charCodeAt(pos);
  253. pos++;
  254. pos = consumeExtraNewline(cc, input, pos);
  255. }
  256. // Otherwise, (the stream starts with a valid escape) consume an escaped code point and append the returned code point to the <string-token>’s value.
  257. else if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  258. pos = _consumeAnEscapedCodePoint(input, pos);
  259. }
  260. }
  261. // anything else
  262. // Append the current input code point to the <string-token>’s value.
  263. else {
  264. // Append
  265. }
  266. }
  267. };
  268. /**
  269. * @param {number} cc char code
  270. * @param {number} q char code
  271. * @returns {boolean} is non-ASCII code point
  272. */
  273. const isNonASCIICodePoint = (cc, q) =>
  274. // Simplify
  275. cc > 0x80;
  276. /**
  277. * @param {number} cc char code
  278. * @returns {boolean} is letter
  279. */
  280. const isLetter = cc =>
  281. (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
  282. (cc >= CC_UPPER_A && cc <= CC_UPPER_Z);
  283. /**
  284. * @param {number} cc char code
  285. * @param {number} q char code
  286. * @returns {boolean} is identifier start code
  287. */
  288. const _isIdentStartCodePoint = (cc, q) =>
  289. isLetter(cc) || isNonASCIICodePoint(cc, q) || cc === CC_LOW_LINE;
  290. /**
  291. * @param {number} cc char code
  292. * @param {number} q char code
  293. * @returns {boolean} is identifier code
  294. */
  295. const _isIdentCodePoint = (cc, q) =>
  296. _isIdentStartCodePoint(cc, q) || _isDigit(cc) || cc === CC_HYPHEN_MINUS;
  297. /**
  298. * @param {number} cc char code
  299. * @returns {boolean} is digit
  300. */
  301. const _isDigit = cc => cc >= CC_0 && cc <= CC_9;
  302. /**
  303. * @param {string} input input
  304. * @param {number} pos position
  305. * @param {number=} f first code point
  306. * @param {number=} s second code point
  307. * @returns {boolean} true if two code points are a valid escape
  308. */
  309. const _ifTwoCodePointsAreValidEscape = (input, pos, f, s) => {
  310. // This section describes how to check if two code points are a valid escape.
  311. // The algorithm described here can be called explicitly with two code points, or can be called with the input stream itself.
  312. // In the latter case, the two code points in question are the current input code point and the next input code point, in that order.
  313. // Note: This algorithm will not consume any additional code point.
  314. const first = f || input.charCodeAt(pos - 1);
  315. const second = s || input.charCodeAt(pos);
  316. // If the first code point is not U+005C REVERSE SOLIDUS (\), return false.
  317. if (first !== CC_REVERSE_SOLIDUS) return false;
  318. // Otherwise, if the second code point is a newline, return false.
  319. if (_isNewline(second)) return false;
  320. // Otherwise, return true.
  321. return true;
  322. };
  323. /**
  324. * @param {string} input input
  325. * @param {number} pos position
  326. * @param {number=} f first
  327. * @param {number=} s second
  328. * @param {number=} t third
  329. * @returns {boolean} true, if input at pos starts an identifier
  330. */
  331. const _ifThreeCodePointsWouldStartAnIdentSequence = (input, pos, f, s, t) => {
  332. // This section describes how to check if three code points would start an ident sequence.
  333. // The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself.
  334. // In the latter case, the three code points in question are the current input code point and the next two input code points, in that order.
  335. // Note: This algorithm will not consume any additional code points.
  336. const first = f || input.charCodeAt(pos - 1);
  337. const second = s || input.charCodeAt(pos);
  338. const third = t || input.charCodeAt(pos + 1);
  339. // Look at the first code point:
  340. // U+002D HYPHEN-MINUS
  341. if (first === CC_HYPHEN_MINUS) {
  342. // If the second code point is an ident-start code point or a U+002D HYPHEN-MINUS
  343. // or a U+002D HYPHEN-MINUS, or the second and third code points are a valid escape, return true.
  344. if (
  345. _isIdentStartCodePoint(second, pos) ||
  346. second === CC_HYPHEN_MINUS ||
  347. _ifTwoCodePointsAreValidEscape(input, pos, second, third)
  348. ) {
  349. return true;
  350. }
  351. return false;
  352. }
  353. // ident-start code point
  354. else if (_isIdentStartCodePoint(first, pos - 1)) {
  355. return true;
  356. }
  357. // U+005C REVERSE SOLIDUS (\)
  358. // If the first and second code points are a valid escape, return true. Otherwise, return false.
  359. else if (first === CC_REVERSE_SOLIDUS) {
  360. if (_ifTwoCodePointsAreValidEscape(input, pos, first, second)) {
  361. return true;
  362. }
  363. return false;
  364. }
  365. // anything else
  366. // Return false.
  367. return false;
  368. };
  369. /**
  370. * @param {string} input input
  371. * @param {number} pos position
  372. * @param {number=} f first
  373. * @param {number=} s second
  374. * @param {number=} t third
  375. * @returns {boolean} true, if input at pos starts an identifier
  376. */
  377. const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => {
  378. // This section describes how to check if three code points would start a number.
  379. // The algorithm described here can be called explicitly with three code points, or can be called with the input stream itself.
  380. // In the latter case, the three code points in question are the current input code point and the next two input code points, in that order.
  381. // Note: This algorithm will not consume any additional code points.
  382. const first = f || input.charCodeAt(pos - 1);
  383. const second = s || input.charCodeAt(pos);
  384. const third = t || input.charCodeAt(pos);
  385. // Look at the first code point:
  386. // U+002B PLUS SIGN (+)
  387. // U+002D HYPHEN-MINUS (-)
  388. //
  389. // If the second code point is a digit, return true.
  390. // Otherwise, if the second code point is a U+002E FULL STOP (.) and the third code point is a digit, return true.
  391. // Otherwise, return false.
  392. if (first === CC_PLUS_SIGN || first === CC_HYPHEN_MINUS) {
  393. if (_isDigit(second)) {
  394. return true;
  395. } else if (second === CC_FULL_STOP && _isDigit(third)) {
  396. return true;
  397. }
  398. return false;
  399. }
  400. // U+002E FULL STOP (.)
  401. // If the second code point is a digit, return true. Otherwise, return false.
  402. else if (first === CC_FULL_STOP) {
  403. if (_isDigit(second)) {
  404. return true;
  405. }
  406. return false;
  407. }
  408. // digit
  409. // Return true.
  410. else if (_isDigit(first)) {
  411. return true;
  412. }
  413. // anything else
  414. // Return false.
  415. return false;
  416. };
  417. /** @type {CharHandler} */
  418. const consumeNumberSign = (input, pos, callbacks) => {
  419. // If the next input code point is an ident code point or the next two input code points are a valid escape, then:
  420. // - Create a <hash-token>.
  421. // - If the next 3 input code points would start an ident sequence, set the <hash-token>’s type flag to "id".
  422. // - Consume an ident sequence, and set the <hash-token>’s value to the returned string.
  423. // - Return the <hash-token>.
  424. const start = pos - 1;
  425. const first = input.charCodeAt(pos);
  426. const second = input.charCodeAt(pos + 1);
  427. if (
  428. _isIdentCodePoint(first, pos - 1) ||
  429. _ifTwoCodePointsAreValidEscape(input, pos, first, second)
  430. ) {
  431. const third = input.charCodeAt(pos + 2);
  432. let isId = false;
  433. if (
  434. _ifThreeCodePointsWouldStartAnIdentSequence(
  435. input,
  436. pos,
  437. first,
  438. second,
  439. third
  440. )
  441. ) {
  442. isId = true;
  443. }
  444. pos = _consumeAnIdentSequence(input, pos, callbacks);
  445. if (callbacks.hash !== undefined) {
  446. return callbacks.hash(input, start, pos, isId);
  447. }
  448. return pos;
  449. }
  450. // Otherwise, return a <delim-token> with its value set to the current input code point.
  451. return pos;
  452. };
  453. /** @type {CharHandler} */
  454. const consumeHyphenMinus = (input, pos, callbacks) => {
  455. // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
  456. if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
  457. pos--;
  458. return consumeANumericToken(input, pos, callbacks);
  459. }
  460. // Otherwise, if the next 2 input code points are U+002D HYPHEN-MINUS U+003E GREATER-THAN SIGN (->), consume them and return a <CDC-token>.
  461. else if (
  462. input.charCodeAt(pos) === CC_HYPHEN_MINUS &&
  463. input.charCodeAt(pos + 1) === CC_GREATER_THAN_SIGN
  464. ) {
  465. return pos + 2;
  466. }
  467. // Otherwise, if the input stream starts with an ident sequence, reconsume the current input code point, consume an ident-like token, and return it.
  468. else if (_ifThreeCodePointsWouldStartAnIdentSequence(input, pos)) {
  469. pos--;
  470. return consumeAnIdentLikeToken(input, pos, callbacks);
  471. }
  472. // Otherwise, return a <delim-token> with its value set to the current input code point.
  473. return pos;
  474. };
  475. /** @type {CharHandler} */
  476. const consumeFullStop = (input, pos, callbacks) => {
  477. const start = pos - 1;
  478. // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
  479. if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
  480. pos--;
  481. return consumeANumericToken(input, pos, callbacks);
  482. }
  483. // Otherwise, return a <delim-token> with its value set to the current input code point.
  484. if (callbacks.delim !== undefined) {
  485. return callbacks.delim(input, start, pos);
  486. }
  487. return pos;
  488. };
  489. /** @type {CharHandler} */
  490. const consumePlusSign = (input, pos, callbacks) => {
  491. // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
  492. if (_ifThreeCodePointsWouldStartANumber(input, pos)) {
  493. pos--;
  494. return consumeANumericToken(input, pos, callbacks);
  495. }
  496. // Otherwise, return a <delim-token> with its value set to the current input code point.
  497. return pos;
  498. };
  499. /** @type {CharHandler} */
  500. const _consumeANumber = (input, pos) => {
  501. // This section describes how to consume a number from a stream of code points.
  502. // It returns a numeric value, and a type which is either "integer" or "number".
  503. // Execute the following steps in order:
  504. // Initially set type to "integer". Let repr be the empty string.
  505. // If the next input code point is U+002B PLUS SIGN (+) or U+002D HYPHEN-MINUS (-), consume it and append it to repr.
  506. if (
  507. input.charCodeAt(pos) === CC_HYPHEN_MINUS ||
  508. input.charCodeAt(pos) === CC_PLUS_SIGN
  509. ) {
  510. pos++;
  511. }
  512. // While the next input code point is a digit, consume it and append it to repr.
  513. while (_isDigit(input.charCodeAt(pos))) {
  514. pos++;
  515. }
  516. // If the next 2 input code points are U+002E FULL STOP (.) followed by a digit, then:
  517. // 1. Consume the next input code point and append it to number part.
  518. // 2. While the next input code point is a digit, consume it and append it to number part.
  519. // 3. Set type to "number".
  520. if (
  521. input.charCodeAt(pos) === CC_FULL_STOP &&
  522. _isDigit(input.charCodeAt(pos + 1))
  523. ) {
  524. pos++;
  525. while (_isDigit(input.charCodeAt(pos))) {
  526. pos++;
  527. }
  528. }
  529. // If the next 2 or 3 input code points are U+0045 LATIN CAPITAL LETTER E (E) or U+0065 LATIN SMALL LETTER E (e), optionally followed by U+002D HYPHEN-MINUS (-) or U+002B PLUS SIGN (+), followed by a digit, then:
  530. // 1. Consume the next input code point.
  531. // 2. If the next input code point is "+" or "-", consume it and append it to exponent part.
  532. // 3. While the next input code point is a digit, consume it and append it to exponent part.
  533. // 4. Set type to "number".
  534. if (
  535. (input.charCodeAt(pos) === CC_LOWER_E ||
  536. input.charCodeAt(pos) === CC_UPPER_E) &&
  537. (((input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS ||
  538. input.charCodeAt(pos + 1) === CC_PLUS_SIGN) &&
  539. _isDigit(input.charCodeAt(pos + 2))) ||
  540. _isDigit(input.charCodeAt(pos + 1)))
  541. ) {
  542. pos++;
  543. if (
  544. input.charCodeAt(pos) === CC_PLUS_SIGN ||
  545. input.charCodeAt(pos) === CC_HYPHEN_MINUS
  546. ) {
  547. pos++;
  548. }
  549. while (_isDigit(input.charCodeAt(pos))) {
  550. pos++;
  551. }
  552. }
  553. // Let value be the result of interpreting number part as a base-10 number.
  554. // If exponent part is non-empty, interpret it as a base-10 integer, then raise 10 to the power of the result, multiply it by value, and set value to that result.
  555. // Return value and type.
  556. return pos;
  557. };
  558. /** @type {CharHandler} */
  559. const consumeANumericToken = (input, pos, callbacks) => {
  560. // This section describes how to consume a numeric token from a stream of code points.
  561. // It returns either a <number-token>, <percentage-token>, or <dimension-token>.
  562. // Consume a number and let number be the result.
  563. pos = _consumeANumber(input, pos, callbacks);
  564. // If the next 3 input code points would start an ident sequence, then:
  565. //
  566. // - Create a <dimension-token> with the same value and type flag as number, and a unit set initially to the empty string.
  567. // - Consume an ident sequence. Set the <dimension-token>’s unit to the returned value.
  568. // - Return the <dimension-token>.
  569. const first = input.charCodeAt(pos);
  570. const second = input.charCodeAt(pos + 1);
  571. const third = input.charCodeAt(pos + 2);
  572. if (
  573. _ifThreeCodePointsWouldStartAnIdentSequence(
  574. input,
  575. pos,
  576. first,
  577. second,
  578. third
  579. )
  580. ) {
  581. return _consumeAnIdentSequence(input, pos, callbacks);
  582. }
  583. // Otherwise, if the next input code point is U+0025 PERCENTAGE SIGN (%), consume it.
  584. // Create a <percentage-token> with the same value as number, and return it.
  585. else if (first === CC_PERCENTAGE) {
  586. return pos + 1;
  587. }
  588. // Otherwise, create a <number-token> with the same value and type flag as number, and return it.
  589. return pos;
  590. };
  591. /** @type {CharHandler} */
  592. const consumeColon = (input, pos, callbacks) => {
  593. // Return a <colon-token>.
  594. if (callbacks.colon !== undefined) {
  595. return callbacks.colon(input, pos - 1, pos);
  596. }
  597. return pos;
  598. };
  599. /** @type {CharHandler} */
  600. const consumeLeftParenthesis = (input, pos, callbacks) => {
  601. // Return a <(-token>.
  602. if (callbacks.leftParenthesis !== undefined) {
  603. return callbacks.leftParenthesis(input, pos - 1, pos);
  604. }
  605. return pos;
  606. };
  607. /** @type {CharHandler} */
  608. const consumeRightParenthesis = (input, pos, callbacks) => {
  609. // Return a <)-token>.
  610. if (callbacks.rightParenthesis !== undefined) {
  611. return callbacks.rightParenthesis(input, pos - 1, pos);
  612. }
  613. return pos;
  614. };
  615. /** @type {CharHandler} */
  616. const consumeLeftSquareBracket = (input, pos, _callbacks) =>
  617. // Return a <]-token>.
  618. pos;
  619. /** @type {CharHandler} */
  620. const consumeRightSquareBracket = (input, pos, _callbacks) =>
  621. // Return a <]-token>.
  622. pos;
  623. /** @type {CharHandler} */
  624. const consumeLeftCurlyBracket = (input, pos, callbacks) => {
  625. // Return a <{-token>.
  626. if (callbacks.leftCurlyBracket !== undefined) {
  627. return callbacks.leftCurlyBracket(input, pos - 1, pos);
  628. }
  629. return pos;
  630. };
  631. /** @type {CharHandler} */
  632. const consumeRightCurlyBracket = (input, pos, callbacks) => {
  633. // Return a <}-token>.
  634. if (callbacks.rightCurlyBracket !== undefined) {
  635. return callbacks.rightCurlyBracket(input, pos - 1, pos);
  636. }
  637. return pos;
  638. };
  639. /** @type {CharHandler} */
  640. const consumeSemicolon = (input, pos, callbacks) => {
  641. // Return a <semicolon-token>.
  642. if (callbacks.semicolon !== undefined) {
  643. return callbacks.semicolon(input, pos - 1, pos);
  644. }
  645. return pos;
  646. };
  647. /** @type {CharHandler} */
  648. const consumeComma = (input, pos, callbacks) => {
  649. // Return a <comma-token>.
  650. if (callbacks.comma !== undefined) {
  651. return callbacks.comma(input, pos - 1, pos);
  652. }
  653. return pos;
  654. };
  655. /** @type {CharHandler} */
  656. const _consumeAnIdentSequence = (input, pos) => {
  657. // This section describes how to consume an ident sequence from a stream of code points.
  658. // It returns a string containing the largest name that can be formed from adjacent code points in the stream, starting from the first.
  659. // Note: This algorithm does not do the verification of the first few code points that are necessary to ensure the returned code points would constitute an <ident-token>.
  660. // If that is the intended use, ensure that the stream starts with an ident sequence before calling this algorithm.
  661. // Let result initially be an empty string.
  662. // Repeatedly consume the next input code point from the stream:
  663. for (;;) {
  664. const cc = input.charCodeAt(pos);
  665. pos++;
  666. // ident code point
  667. // Append the code point to result.
  668. if (_isIdentCodePoint(cc, pos - 1)) {
  669. // Nothing
  670. }
  671. // the stream starts with a valid escape
  672. // Consume an escaped code point. Append the returned code point to result.
  673. else if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  674. pos = _consumeAnEscapedCodePoint(input, pos);
  675. }
  676. // anything else
  677. // Reconsume the current input code point. Return result.
  678. else {
  679. return pos - 1;
  680. }
  681. }
  682. };
  683. /**
  684. * @param {number} cc char code
  685. * @returns {boolean} true, when cc is the non-printable code point, otherwise false
  686. */
  687. const _isNonPrintableCodePoint = cc =>
  688. (cc >= 0x00 && cc <= 0x08) ||
  689. cc === 0x0b ||
  690. (cc >= 0x0e && cc <= 0x1f) ||
  691. cc === 0x7f;
  692. /**
  693. * @param {string} input input
  694. * @param {number} pos position
  695. * @returns {number} position
  696. */
  697. const consumeTheRemnantsOfABadUrl = (input, pos) => {
  698. // This section describes how to consume the remnants of a bad url from a stream of code points,
  699. // "cleaning up" after the tokenizer realizes that it’s in the middle of a <bad-url-token> rather than a <url-token>.
  700. // It returns nothing; its sole use is to consume enough of the input stream to reach a recovery point where normal tokenizing can resume.
  701. // Repeatedly consume the next input code point from the stream:
  702. for (;;) {
  703. // EOF
  704. // Return.
  705. if (pos === input.length) {
  706. return pos;
  707. }
  708. const cc = input.charCodeAt(pos);
  709. pos++;
  710. // U+0029 RIGHT PARENTHESIS ())
  711. // Return.
  712. if (cc === CC_RIGHT_PARENTHESIS) {
  713. return pos;
  714. }
  715. // the input stream starts with a valid escape
  716. // Consume an escaped code point.
  717. // This allows an escaped right parenthesis ("\)") to be encountered without ending the <bad-url-token>.
  718. // This is otherwise identical to the "anything else" clause.
  719. else if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  720. pos = _consumeAnEscapedCodePoint(input, pos);
  721. }
  722. // anything else
  723. // Do nothing.
  724. else {
  725. // Do nothing.
  726. }
  727. }
  728. };
  729. /**
  730. * @param {string} input input
  731. * @param {number} pos position
  732. * @param {number} fnStart start
  733. * @param {CssTokenCallbacks} callbacks callbacks
  734. * @returns {pos} pos
  735. */
  736. const consumeAUrlToken = (input, pos, fnStart, callbacks) => {
  737. // This section describes how to consume a url token from a stream of code points.
  738. // It returns either a <url-token> or a <bad-url-token>.
  739. // Note: This algorithm assumes that the initial "url(" has already been consumed.
  740. // This algorithm also assumes that it’s being called to consume an "unquoted" value, like url(foo).
  741. // A quoted value, like url("foo"), is parsed as a <function-token>.
  742. // Consume an ident-like token automatically handles this distinction; this algorithm shouldn’t be called directly otherwise.
  743. // Initially create a <url-token> with its value set to the empty string.
  744. // Consume as much whitespace as possible.
  745. while (_isWhiteSpace(input.charCodeAt(pos))) {
  746. pos++;
  747. }
  748. const contentStart = pos;
  749. // Repeatedly consume the next input code point from the stream:
  750. for (;;) {
  751. // EOF
  752. // This is a parse error. Return the <url-token>.
  753. if (pos === input.length) {
  754. if (callbacks.url !== undefined) {
  755. return callbacks.url(input, fnStart, pos, contentStart, pos - 1);
  756. }
  757. return pos;
  758. }
  759. const cc = input.charCodeAt(pos);
  760. pos++;
  761. // U+0029 RIGHT PARENTHESIS ())
  762. // Return the <url-token>.
  763. if (cc === CC_RIGHT_PARENTHESIS) {
  764. if (callbacks.url !== undefined) {
  765. return callbacks.url(input, fnStart, pos, contentStart, pos - 1);
  766. }
  767. return pos;
  768. }
  769. // whitespace
  770. // Consume as much whitespace as possible.
  771. // If the next input code point is U+0029 RIGHT PARENTHESIS ()) or EOF, consume it and return the <url-token>
  772. // (if EOF was encountered, this is a parse error); otherwise, consume the remnants of a bad url, create a <bad-url-token>, and return it.
  773. else if (_isWhiteSpace(cc)) {
  774. const end = pos - 1;
  775. while (_isWhiteSpace(input.charCodeAt(pos))) {
  776. pos++;
  777. }
  778. if (pos === input.length) {
  779. if (callbacks.url !== undefined) {
  780. return callbacks.url(input, fnStart, pos, contentStart, end);
  781. }
  782. return pos;
  783. }
  784. if (input.charCodeAt(pos) === CC_RIGHT_PARENTHESIS) {
  785. pos++;
  786. if (callbacks.url !== undefined) {
  787. return callbacks.url(input, fnStart, pos, contentStart, end);
  788. }
  789. return pos;
  790. }
  791. // Don't handle bad urls
  792. return consumeTheRemnantsOfABadUrl(input, pos);
  793. }
  794. // U+0022 QUOTATION MARK (")
  795. // U+0027 APOSTROPHE (')
  796. // U+0028 LEFT PARENTHESIS (()
  797. // non-printable code point
  798. // This is a parse error. Consume the remnants of a bad url, create a <bad-url-token>, and return it.
  799. else if (
  800. cc === CC_QUOTATION_MARK ||
  801. cc === CC_APOSTROPHE ||
  802. cc === CC_LEFT_PARENTHESIS ||
  803. _isNonPrintableCodePoint(cc)
  804. ) {
  805. // Don't handle bad urls
  806. return consumeTheRemnantsOfABadUrl(input, pos);
  807. }
  808. // // U+005C REVERSE SOLIDUS (\)
  809. // // If the stream starts with a valid escape, consume an escaped code point and append the returned code point to the <url-token>’s value.
  810. // // Otherwise, this is a parse error. Consume the remnants of a bad url, create a <bad-url-token>, and return it.
  811. else if (cc === CC_REVERSE_SOLIDUS) {
  812. if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  813. pos = _consumeAnEscapedCodePoint(input, pos);
  814. } else {
  815. // Don't handle bad urls
  816. return consumeTheRemnantsOfABadUrl(input, pos);
  817. }
  818. }
  819. // anything else
  820. // Append the current input code point to the <url-token>’s value.
  821. else {
  822. // Nothing
  823. }
  824. }
  825. };
  826. /** @type {CharHandler} */
  827. const consumeAnIdentLikeToken = (input, pos, callbacks) => {
  828. const start = pos;
  829. // This section describes how to consume an ident-like token from a stream of code points.
  830. // It returns an <ident-token>, <function-token>, <url-token>, or <bad-url-token>.
  831. pos = _consumeAnIdentSequence(input, pos, callbacks);
  832. // If string’s value is an ASCII case-insensitive match for "url", and the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.
  833. // While the next two input code points are whitespace, consume the next input code point.
  834. // If the next one or two input code points are U+0022 QUOTATION MARK ("), U+0027 APOSTROPHE ('), or whitespace followed by U+0022 QUOTATION MARK (") or U+0027 APOSTROPHE ('), then create a <function-token> with its value set to string and return it.
  835. // Otherwise, consume a url token, and return it.
  836. if (
  837. input.slice(start, pos).toLowerCase() === "url" &&
  838. input.charCodeAt(pos) === CC_LEFT_PARENTHESIS
  839. ) {
  840. pos++;
  841. const end = pos;
  842. while (
  843. _isWhiteSpace(input.charCodeAt(pos)) &&
  844. _isWhiteSpace(input.charCodeAt(pos + 1))
  845. ) {
  846. pos++;
  847. }
  848. if (
  849. input.charCodeAt(pos) === CC_QUOTATION_MARK ||
  850. input.charCodeAt(pos) === CC_APOSTROPHE ||
  851. (_isWhiteSpace(input.charCodeAt(pos)) &&
  852. (input.charCodeAt(pos + 1) === CC_QUOTATION_MARK ||
  853. input.charCodeAt(pos + 1) === CC_APOSTROPHE))
  854. ) {
  855. if (callbacks.function !== undefined) {
  856. return callbacks.function(input, start, end);
  857. }
  858. return pos;
  859. }
  860. return consumeAUrlToken(input, pos, start, callbacks);
  861. }
  862. // Otherwise, if the next input code point is U+0028 LEFT PARENTHESIS ((), consume it.
  863. // Create a <function-token> with its value set to string and return it.
  864. if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) {
  865. pos++;
  866. if (callbacks.function !== undefined) {
  867. return callbacks.function(input, start, pos);
  868. }
  869. return pos;
  870. }
  871. // Otherwise, create an <ident-token> with its value set to string and return it.
  872. if (callbacks.identifier !== undefined) {
  873. return callbacks.identifier(input, start, pos);
  874. }
  875. return pos;
  876. };
  877. /** @type {CharHandler} */
  878. const consumeLessThan = (input, pos, _callbacks) => {
  879. // If the next 3 input code points are U+0021 EXCLAMATION MARK U+002D HYPHEN-MINUS U+002D HYPHEN-MINUS (!--), consume them and return a <CDO-token>.
  880. if (input.slice(pos, pos + 3) === "!--") {
  881. return pos + 3;
  882. }
  883. // Otherwise, return a <delim-token> with its value set to the current input code point.
  884. return pos;
  885. };
  886. /** @type {CharHandler} */
  887. const consumeCommercialAt = (input, pos, callbacks) => {
  888. const start = pos - 1;
  889. // If the next 3 input code points would start an ident sequence, consume an ident sequence, create an <at-keyword-token> with its value set to the returned value, and return it.
  890. if (
  891. _ifThreeCodePointsWouldStartAnIdentSequence(
  892. input,
  893. pos,
  894. input.charCodeAt(pos),
  895. input.charCodeAt(pos + 1),
  896. input.charCodeAt(pos + 2)
  897. )
  898. ) {
  899. pos = _consumeAnIdentSequence(input, pos, callbacks);
  900. if (callbacks.atKeyword !== undefined) {
  901. pos = callbacks.atKeyword(input, start, pos);
  902. }
  903. return pos;
  904. }
  905. // Otherwise, return a <delim-token> with its value set to the current input code point.
  906. return pos;
  907. };
  908. /** @type {CharHandler} */
  909. const consumeReverseSolidus = (input, pos, callbacks) => {
  910. // If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it.
  911. if (_ifTwoCodePointsAreValidEscape(input, pos)) {
  912. pos--;
  913. return consumeAnIdentLikeToken(input, pos, callbacks);
  914. }
  915. // Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point.
  916. return pos;
  917. };
  918. /** @type {CharHandler} */
  919. const consumeAToken = (input, pos, callbacks) => {
  920. const cc = input.charCodeAt(pos - 1);
  921. // https://drafts.csswg.org/css-syntax/#consume-token
  922. switch (cc) {
  923. // whitespace
  924. case CC_LINE_FEED:
  925. case CC_CARRIAGE_RETURN:
  926. case CC_FORM_FEED:
  927. case CC_TAB:
  928. case CC_SPACE:
  929. return consumeSpace(input, pos, callbacks);
  930. // U+0022 QUOTATION MARK (")
  931. case CC_QUOTATION_MARK:
  932. return consumeAStringToken(input, pos, callbacks);
  933. // U+0023 NUMBER SIGN (#)
  934. case CC_NUMBER_SIGN:
  935. return consumeNumberSign(input, pos, callbacks);
  936. // U+0027 APOSTROPHE (')
  937. case CC_APOSTROPHE:
  938. return consumeAStringToken(input, pos, callbacks);
  939. // U+0028 LEFT PARENTHESIS (()
  940. case CC_LEFT_PARENTHESIS:
  941. return consumeLeftParenthesis(input, pos, callbacks);
  942. // U+0029 RIGHT PARENTHESIS ())
  943. case CC_RIGHT_PARENTHESIS:
  944. return consumeRightParenthesis(input, pos, callbacks);
  945. // U+002B PLUS SIGN (+)
  946. case CC_PLUS_SIGN:
  947. return consumePlusSign(input, pos, callbacks);
  948. // U+002C COMMA (,)
  949. case CC_COMMA:
  950. return consumeComma(input, pos, callbacks);
  951. // U+002D HYPHEN-MINUS (-)
  952. case CC_HYPHEN_MINUS:
  953. return consumeHyphenMinus(input, pos, callbacks);
  954. // U+002E FULL STOP (.)
  955. case CC_FULL_STOP:
  956. return consumeFullStop(input, pos, callbacks);
  957. // U+003A COLON (:)
  958. case CC_COLON:
  959. return consumeColon(input, pos, callbacks);
  960. // U+003B SEMICOLON (;)
  961. case CC_SEMICOLON:
  962. return consumeSemicolon(input, pos, callbacks);
  963. // U+003C LESS-THAN SIGN (<)
  964. case CC_LESS_THAN_SIGN:
  965. return consumeLessThan(input, pos, callbacks);
  966. // U+0040 COMMERCIAL AT (@)
  967. case CC_AT_SIGN:
  968. return consumeCommercialAt(input, pos, callbacks);
  969. // U+005B LEFT SQUARE BRACKET ([)
  970. case CC_LEFT_SQUARE:
  971. return consumeLeftSquareBracket(input, pos, callbacks);
  972. // U+005C REVERSE SOLIDUS (\)
  973. case CC_REVERSE_SOLIDUS:
  974. return consumeReverseSolidus(input, pos, callbacks);
  975. // U+005D RIGHT SQUARE BRACKET (])
  976. case CC_RIGHT_SQUARE:
  977. return consumeRightSquareBracket(input, pos, callbacks);
  978. // U+007B LEFT CURLY BRACKET ({)
  979. case CC_LEFT_CURLY:
  980. return consumeLeftCurlyBracket(input, pos, callbacks);
  981. // U+007D RIGHT CURLY BRACKET (})
  982. case CC_RIGHT_CURLY:
  983. return consumeRightCurlyBracket(input, pos, callbacks);
  984. default:
  985. // digit
  986. // Reconsume the current input code point, consume a numeric token, and return it.
  987. if (_isDigit(cc)) {
  988. pos--;
  989. return consumeANumericToken(input, pos, callbacks);
  990. } else if (cc === CC_LOWER_U || cc === CC_UPPER_U) {
  991. // If unicode ranges allowed is true and the input stream would start a unicode-range,
  992. // reconsume the current input code point, consume a unicode-range token, and return it.
  993. // Skip now
  994. // if (_ifThreeCodePointsWouldStartAUnicodeRange(input, pos)) {
  995. // pos--;
  996. // return consumeAUnicodeRangeToken(input, pos, callbacks);
  997. // }
  998. // Otherwise, reconsume the current input code point, consume an ident-like token, and return it.
  999. pos--;
  1000. return consumeAnIdentLikeToken(input, pos, callbacks);
  1001. }
  1002. // ident-start code point
  1003. // Reconsume the current input code point, consume an ident-like token, and return it.
  1004. else if (isIdentStartCodePoint(cc)) {
  1005. pos--;
  1006. return consumeAnIdentLikeToken(input, pos, callbacks);
  1007. }
  1008. // EOF, but we don't have it
  1009. // anything else
  1010. // Return a <delim-token> with its value set to the current input code point.
  1011. return consumeDelimToken(input, pos, callbacks);
  1012. }
  1013. };
  1014. /**
  1015. * @param {string} input input css
  1016. * @param {number=} pos pos
  1017. * @param {CssTokenCallbacks=} callbacks callbacks
  1018. * @returns {number} pos
  1019. */
  1020. module.exports = (input, pos = 0, callbacks = {}) => {
  1021. // This section describes how to consume a token from a stream of code points. It will return a single token of any type.
  1022. while (pos < input.length) {
  1023. // Consume comments.
  1024. pos = consumeComments(input, pos, callbacks);
  1025. // Consume the next input code point.
  1026. pos++;
  1027. pos = consumeAToken(input, pos, callbacks);
  1028. if (callbacks.needTerminate && callbacks.needTerminate()) {
  1029. break;
  1030. }
  1031. }
  1032. return pos;
  1033. };
  1034. /**
  1035. * @param {string} input input
  1036. * @param {number} pos position
  1037. * @returns {number} position after comments
  1038. */
  1039. const eatComments = (input, pos) => {
  1040. for (;;) {
  1041. const originalPos = pos;
  1042. pos = consumeComments(input, pos, {});
  1043. if (originalPos === pos) {
  1044. break;
  1045. }
  1046. }
  1047. return pos;
  1048. };
  1049. /**
  1050. * @param {string} input input
  1051. * @param {number} pos position
  1052. * @returns {number} position after whitespace
  1053. */
  1054. const eatWhitespace = (input, pos) => {
  1055. while (_isWhiteSpace(input.charCodeAt(pos))) {
  1056. pos++;
  1057. }
  1058. return pos;
  1059. };
  1060. /**
  1061. * @param {string} input input
  1062. * @param {number} pos position
  1063. * @returns {number} position after whitespace and comments
  1064. */
  1065. const eatWhitespaceAndComments = (input, pos) => {
  1066. for (;;) {
  1067. const originalPos = pos;
  1068. pos = consumeComments(input, pos, {});
  1069. while (_isWhiteSpace(input.charCodeAt(pos))) {
  1070. pos++;
  1071. }
  1072. if (originalPos === pos) {
  1073. break;
  1074. }
  1075. }
  1076. return pos;
  1077. };
  1078. /**
  1079. * @param {string} input input
  1080. * @param {number} pos position
  1081. * @returns {number} position after whitespace
  1082. */
  1083. const eatWhiteLine = (input, pos) => {
  1084. for (;;) {
  1085. const cc = input.charCodeAt(pos);
  1086. if (_isSpace(cc)) {
  1087. pos++;
  1088. continue;
  1089. }
  1090. if (_isNewline(cc)) pos++;
  1091. pos = consumeExtraNewline(cc, input, pos);
  1092. break;
  1093. }
  1094. return pos;
  1095. };
  1096. /**
  1097. * @param {string} input input
  1098. * @param {number} pos position
  1099. * @returns {[number, number] | undefined} positions of ident sequence
  1100. */
  1101. const skipCommentsAndEatIdentSequence = (input, pos) => {
  1102. pos = eatComments(input, pos);
  1103. const start = pos;
  1104. if (
  1105. _ifThreeCodePointsWouldStartAnIdentSequence(
  1106. input,
  1107. pos,
  1108. input.charCodeAt(pos),
  1109. input.charCodeAt(pos + 1),
  1110. input.charCodeAt(pos + 2)
  1111. )
  1112. ) {
  1113. return [start, _consumeAnIdentSequence(input, pos, {})];
  1114. }
  1115. return undefined;
  1116. };
  1117. /**
  1118. * @param {string} input input
  1119. * @param {number} pos position
  1120. * @returns {[number, number] | undefined} positions of ident sequence
  1121. */
  1122. const eatString = (input, pos) => {
  1123. pos = eatWhitespaceAndComments(input, pos);
  1124. const start = pos;
  1125. if (
  1126. input.charCodeAt(pos) === CC_QUOTATION_MARK ||
  1127. input.charCodeAt(pos) === CC_APOSTROPHE
  1128. ) {
  1129. return [start, consumeAStringToken(input, pos + 1, {})];
  1130. }
  1131. return undefined;
  1132. };
  1133. /**
  1134. * @param {string} input input
  1135. * @param {number} pos position
  1136. * @param {CssTokenCallbacks} cbs callbacks
  1137. * @returns {[number, number][]} positions of ident sequence
  1138. */
  1139. const eatImageSetStrings = (input, pos, cbs) => {
  1140. /** @type {[number, number][]} */
  1141. const result = [];
  1142. let isFirst = true;
  1143. let needStop = false;
  1144. // We already in `func(` token
  1145. let balanced = 1;
  1146. /** @type {CssTokenCallbacks} */
  1147. const callbacks = {
  1148. ...cbs,
  1149. string: (_input, start, end) => {
  1150. if (isFirst && balanced === 1) {
  1151. result.push([start, end]);
  1152. isFirst = false;
  1153. }
  1154. return end;
  1155. },
  1156. comma: (_input, _start, end) => {
  1157. if (balanced === 1) {
  1158. isFirst = true;
  1159. }
  1160. return end;
  1161. },
  1162. leftParenthesis: (input, start, end) => {
  1163. balanced++;
  1164. return end;
  1165. },
  1166. function: (_input, start, end) => {
  1167. balanced++;
  1168. return end;
  1169. },
  1170. rightParenthesis: (_input, _start, end) => {
  1171. balanced--;
  1172. if (balanced === 0) {
  1173. needStop = true;
  1174. }
  1175. return end;
  1176. }
  1177. };
  1178. while (pos < input.length) {
  1179. // Consume comments.
  1180. pos = consumeComments(input, pos, callbacks);
  1181. // Consume the next input code point.
  1182. pos++;
  1183. pos = consumeAToken(input, pos, callbacks);
  1184. if (needStop) {
  1185. break;
  1186. }
  1187. }
  1188. return result;
  1189. };
  1190. /**
  1191. * @param {string} input input
  1192. * @param {number} pos position
  1193. * @param {CssTokenCallbacks} cbs callbacks
  1194. * @returns {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} positions of top level tokens
  1195. */
  1196. const eatImportTokens = (input, pos, cbs) => {
  1197. const result =
  1198. /** @type {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} */
  1199. (Array.from({ length: 4 }));
  1200. /** @type {0 | 1 | 2 | undefined} */
  1201. let scope;
  1202. let needStop = false;
  1203. let balanced = 0;
  1204. /** @type {CssTokenCallbacks} */
  1205. const callbacks = {
  1206. ...cbs,
  1207. url: (_input, start, end, contentStart, contentEnd) => {
  1208. if (
  1209. result[0] === undefined &&
  1210. balanced === 0 &&
  1211. result[1] === undefined &&
  1212. result[2] === undefined &&
  1213. result[3] === undefined
  1214. ) {
  1215. result[0] = [start, end, contentStart, contentEnd];
  1216. scope = undefined;
  1217. }
  1218. return end;
  1219. },
  1220. string: (_input, start, end) => {
  1221. if (
  1222. balanced === 0 &&
  1223. result[0] === undefined &&
  1224. result[1] === undefined &&
  1225. result[2] === undefined &&
  1226. result[3] === undefined
  1227. ) {
  1228. result[0] = [start, end, start + 1, end - 1];
  1229. scope = undefined;
  1230. } else if (result[0] !== undefined && scope === 0) {
  1231. result[0][2] = start + 1;
  1232. result[0][3] = end - 1;
  1233. }
  1234. return end;
  1235. },
  1236. leftParenthesis: (_input, _start, end) => {
  1237. balanced++;
  1238. return end;
  1239. },
  1240. rightParenthesis: (_input, _start, end) => {
  1241. balanced--;
  1242. if (balanced === 0 && scope !== undefined) {
  1243. /** @type {[number, number]} */
  1244. (result[scope])[1] = end;
  1245. scope = undefined;
  1246. }
  1247. return end;
  1248. },
  1249. function: (input, start, end) => {
  1250. if (balanced === 0) {
  1251. const name = input
  1252. .slice(start, end - 1)
  1253. .replace(/\\/g, "")
  1254. .toLowerCase();
  1255. if (
  1256. name === "url" &&
  1257. result[0] === undefined &&
  1258. result[1] === undefined &&
  1259. result[2] === undefined &&
  1260. result[3] === undefined
  1261. ) {
  1262. scope = 0;
  1263. result[scope] = [start, end + 1, end + 1, end + 1];
  1264. } else if (
  1265. name === "layer" &&
  1266. result[1] === undefined &&
  1267. result[2] === undefined
  1268. ) {
  1269. scope = 1;
  1270. result[scope] = [start, end];
  1271. } else if (name === "supports" && result[2] === undefined) {
  1272. scope = 2;
  1273. result[scope] = [start, end];
  1274. } else {
  1275. scope = undefined;
  1276. }
  1277. }
  1278. balanced++;
  1279. return end;
  1280. },
  1281. identifier: (input, start, end) => {
  1282. if (
  1283. balanced === 0 &&
  1284. result[1] === undefined &&
  1285. result[2] === undefined
  1286. ) {
  1287. const name = input.slice(start, end).replace(/\\/g, "").toLowerCase();
  1288. if (name === "layer") {
  1289. result[1] = [start, end];
  1290. scope = undefined;
  1291. }
  1292. }
  1293. return end;
  1294. },
  1295. semicolon: (_input, start, end) => {
  1296. if (balanced === 0) {
  1297. needStop = true;
  1298. result[3] = [start, end];
  1299. }
  1300. return end;
  1301. }
  1302. };
  1303. while (pos < input.length) {
  1304. // Consume comments.
  1305. pos = consumeComments(input, pos, callbacks);
  1306. // Consume the next input code point.
  1307. pos++;
  1308. pos = consumeAToken(input, pos, callbacks);
  1309. if (needStop) {
  1310. break;
  1311. }
  1312. }
  1313. return result;
  1314. };
  1315. /**
  1316. * @param {string} input input
  1317. * @param {number} pos position
  1318. * @returns {[number, number] | undefined} positions of ident sequence
  1319. */
  1320. const eatIdentSequence = (input, pos) => {
  1321. pos = eatWhitespaceAndComments(input, pos);
  1322. const start = pos;
  1323. if (
  1324. _ifThreeCodePointsWouldStartAnIdentSequence(
  1325. input,
  1326. pos,
  1327. input.charCodeAt(pos),
  1328. input.charCodeAt(pos + 1),
  1329. input.charCodeAt(pos + 2)
  1330. )
  1331. ) {
  1332. return [start, _consumeAnIdentSequence(input, pos, {})];
  1333. }
  1334. return undefined;
  1335. };
  1336. /**
  1337. * @param {string} input input
  1338. * @param {number} pos position
  1339. * @returns {[number, number, boolean] | undefined} positions of ident sequence or string
  1340. */
  1341. const eatIdentSequenceOrString = (input, pos) => {
  1342. pos = eatWhitespaceAndComments(input, pos);
  1343. const start = pos;
  1344. if (
  1345. input.charCodeAt(pos) === CC_QUOTATION_MARK ||
  1346. input.charCodeAt(pos) === CC_APOSTROPHE
  1347. ) {
  1348. return [start, consumeAStringToken(input, pos + 1, {}), false];
  1349. } else if (
  1350. _ifThreeCodePointsWouldStartAnIdentSequence(
  1351. input,
  1352. pos,
  1353. input.charCodeAt(pos),
  1354. input.charCodeAt(pos + 1),
  1355. input.charCodeAt(pos + 2)
  1356. )
  1357. ) {
  1358. return [start, _consumeAnIdentSequence(input, pos, {}), true];
  1359. }
  1360. return undefined;
  1361. };
  1362. /**
  1363. * @param {string} chars characters
  1364. * @returns {(input: string, pos: number) => number} function to eat characters
  1365. */
  1366. const eatUntil = chars => {
  1367. const charCodes = Array.from({ length: chars.length }, (_, i) =>
  1368. chars.charCodeAt(i)
  1369. );
  1370. const arr = Array.from(
  1371. { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 },
  1372. () => false
  1373. );
  1374. for (const cc of charCodes) {
  1375. arr[cc] = true;
  1376. }
  1377. return (input, pos) => {
  1378. for (;;) {
  1379. const cc = input.charCodeAt(pos);
  1380. if (cc < arr.length && arr[cc]) {
  1381. return pos;
  1382. }
  1383. pos++;
  1384. if (pos === input.length) return pos;
  1385. }
  1386. };
  1387. };
  1388. module.exports.eatComments = eatComments;
  1389. module.exports.eatIdentSequence = eatIdentSequence;
  1390. module.exports.eatIdentSequenceOrString = eatIdentSequenceOrString;
  1391. module.exports.eatImageSetStrings = eatImageSetStrings;
  1392. module.exports.eatImportTokens = eatImportTokens;
  1393. module.exports.eatString = eatString;
  1394. module.exports.eatUntil = eatUntil;
  1395. module.exports.eatWhiteLine = eatWhiteLine;
  1396. module.exports.eatWhitespace = eatWhitespace;
  1397. module.exports.eatWhitespaceAndComments = eatWhitespaceAndComments;
  1398. module.exports.isIdentStartCodePoint = isIdentStartCodePoint;
  1399. module.exports.skipCommentsAndEatIdentSequence =
  1400. skipCommentsAndEatIdentSequence;