walkCssTokens.js 46 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594159515961597159815991600160116021603160416051606160716081609161016111612161316141615161616171618161916201621162216231624162516261627
  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 {(function(string, number, number, number, number): number)=} url
  9. * @property {(function(string, number, number): number)=} comment
  10. * @property {(function(string, number, number): number)=} string
  11. * @property {(function(string, number, number): number)=} leftParenthesis
  12. * @property {(function(string, number, number): number)=} rightParenthesis
  13. * @property {(function(string, number, number): number)=} function
  14. * @property {(function(string, number, number): number)=} colon
  15. * @property {(function(string, number, number): number)=} atKeyword
  16. * @property {(function(string, number, number): number)=} delim
  17. * @property {(function(string, number, number): number)=} identifier
  18. * @property {(function(string, number, number, boolean): number)=} hash
  19. * @property {(function(string, number, number): number)=} leftCurlyBracket
  20. * @property {(function(string, number, number): number)=} rightCurlyBracket
  21. * @property {(function(string, number, number): number)=} semicolon
  22. * @property {(function(string, number, number): number)=} comma
  23. * @property {(function(): boolean)=} needTerminate
  24. */
  25. /** @typedef {function(string, number, 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. module.exports.isIdentStartCodePoint = isIdentStartCodePoint;
  1035. /**
  1036. * @param {string} input input
  1037. * @param {number} pos position
  1038. * @returns {number} position after comments
  1039. */
  1040. module.exports.eatComments = (input, pos) => {
  1041. for (;;) {
  1042. const originalPos = pos;
  1043. pos = consumeComments(input, pos, {});
  1044. if (originalPos === pos) {
  1045. break;
  1046. }
  1047. }
  1048. return pos;
  1049. };
  1050. /**
  1051. * @param {string} input input
  1052. * @param {number} pos position
  1053. * @returns {number} position after whitespace
  1054. */
  1055. module.exports.eatWhitespace = (input, pos) => {
  1056. while (_isWhiteSpace(input.charCodeAt(pos))) {
  1057. pos++;
  1058. }
  1059. return pos;
  1060. };
  1061. /**
  1062. * @param {string} input input
  1063. * @param {number} pos position
  1064. * @returns {number} position after whitespace and comments
  1065. */
  1066. module.exports.eatWhitespaceAndComments = (input, pos) => {
  1067. for (;;) {
  1068. const originalPos = pos;
  1069. pos = consumeComments(input, pos, {});
  1070. while (_isWhiteSpace(input.charCodeAt(pos))) {
  1071. pos++;
  1072. }
  1073. if (originalPos === pos) {
  1074. break;
  1075. }
  1076. }
  1077. return pos;
  1078. };
  1079. /**
  1080. * @param {string} input input
  1081. * @param {number} pos position
  1082. * @returns {number} position after whitespace and comments
  1083. */
  1084. module.exports.eatComments = (input, pos) => {
  1085. for (;;) {
  1086. const originalPos = pos;
  1087. pos = consumeComments(input, pos, {});
  1088. if (originalPos === pos) {
  1089. break;
  1090. }
  1091. }
  1092. return pos;
  1093. };
  1094. /**
  1095. * @param {string} input input
  1096. * @param {number} pos position
  1097. * @returns {number} position after whitespace
  1098. */
  1099. module.exports.eatWhiteLine = (input, pos) => {
  1100. for (;;) {
  1101. const cc = input.charCodeAt(pos);
  1102. if (_isSpace(cc)) {
  1103. pos++;
  1104. continue;
  1105. }
  1106. if (_isNewline(cc)) pos++;
  1107. pos = consumeExtraNewline(cc, input, pos);
  1108. break;
  1109. }
  1110. return pos;
  1111. };
  1112. /**
  1113. * @param {string} input input
  1114. * @param {number} pos position
  1115. * @returns {[number, number] | undefined} positions of ident sequence
  1116. */
  1117. module.exports.skipCommentsAndEatIdentSequence = (input, pos) => {
  1118. pos = module.exports.eatComments(input, pos);
  1119. const start = pos;
  1120. if (
  1121. _ifThreeCodePointsWouldStartAnIdentSequence(
  1122. input,
  1123. pos,
  1124. input.charCodeAt(pos),
  1125. input.charCodeAt(pos + 1),
  1126. input.charCodeAt(pos + 2)
  1127. )
  1128. ) {
  1129. return [start, _consumeAnIdentSequence(input, pos, {})];
  1130. }
  1131. return undefined;
  1132. };
  1133. /**
  1134. * @param {string} input input
  1135. * @param {number} pos position
  1136. * @returns {[number, number] | undefined} positions of ident sequence
  1137. */
  1138. module.exports.eatString = (input, pos) => {
  1139. pos = module.exports.eatWhitespaceAndComments(input, pos);
  1140. const start = pos;
  1141. if (
  1142. input.charCodeAt(pos) === CC_QUOTATION_MARK ||
  1143. input.charCodeAt(pos) === CC_APOSTROPHE
  1144. ) {
  1145. return [start, consumeAStringToken(input, pos + 1, {})];
  1146. }
  1147. return undefined;
  1148. };
  1149. /**
  1150. * @param {string} input input
  1151. * @param {number} pos position
  1152. * @param {CssTokenCallbacks} cbs callbacks
  1153. * @returns {[number, number][]} positions of ident sequence
  1154. */
  1155. module.exports.eatImageSetStrings = (input, pos, cbs) => {
  1156. /** @type {[number, number][]} */
  1157. const result = [];
  1158. let isFirst = true;
  1159. let needStop = false;
  1160. // We already in `func(` token
  1161. let balanced = 1;
  1162. /** @type {CssTokenCallbacks} */
  1163. const callbacks = {
  1164. ...cbs,
  1165. string: (_input, start, end) => {
  1166. if (isFirst && balanced === 1) {
  1167. result.push([start, end]);
  1168. isFirst = false;
  1169. }
  1170. return end;
  1171. },
  1172. comma: (_input, _start, end) => {
  1173. if (balanced === 1) {
  1174. isFirst = true;
  1175. }
  1176. return end;
  1177. },
  1178. leftParenthesis: (input, start, end) => {
  1179. balanced++;
  1180. return end;
  1181. },
  1182. function: (_input, start, end) => {
  1183. balanced++;
  1184. return end;
  1185. },
  1186. rightParenthesis: (_input, _start, end) => {
  1187. balanced--;
  1188. if (balanced === 0) {
  1189. needStop = true;
  1190. }
  1191. return end;
  1192. }
  1193. };
  1194. while (pos < input.length) {
  1195. // Consume comments.
  1196. pos = consumeComments(input, pos, callbacks);
  1197. // Consume the next input code point.
  1198. pos++;
  1199. pos = consumeAToken(input, pos, callbacks);
  1200. if (needStop) {
  1201. break;
  1202. }
  1203. }
  1204. return result;
  1205. };
  1206. /**
  1207. * @param {string} input input
  1208. * @param {number} pos position
  1209. * @param {CssTokenCallbacks} cbs callbacks
  1210. * @returns {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} positions of top level tokens
  1211. */
  1212. module.exports.eatImportTokens = (input, pos, cbs) => {
  1213. const result =
  1214. /** @type {[[number, number, number, number] | undefined, [number, number] | undefined, [number, number] | undefined, [number, number] | undefined]} */
  1215. (new Array(4));
  1216. /** @type {0 | 1 | 2 | undefined} */
  1217. let scope;
  1218. let needStop = false;
  1219. let balanced = 0;
  1220. /** @type {CssTokenCallbacks} */
  1221. const callbacks = {
  1222. ...cbs,
  1223. url: (_input, start, end, contentStart, contentEnd) => {
  1224. if (
  1225. result[0] === undefined &&
  1226. balanced === 0 &&
  1227. result[1] === undefined &&
  1228. result[2] === undefined &&
  1229. result[3] === undefined
  1230. ) {
  1231. result[0] = [start, end, contentStart, contentEnd];
  1232. scope = undefined;
  1233. }
  1234. return end;
  1235. },
  1236. string: (_input, start, end) => {
  1237. if (
  1238. balanced === 0 &&
  1239. result[0] === undefined &&
  1240. result[1] === undefined &&
  1241. result[2] === undefined &&
  1242. result[3] === undefined
  1243. ) {
  1244. result[0] = [start, end, start + 1, end - 1];
  1245. scope = undefined;
  1246. } else if (result[0] !== undefined && scope === 0) {
  1247. result[0][2] = start + 1;
  1248. result[0][3] = end - 1;
  1249. }
  1250. return end;
  1251. },
  1252. leftParenthesis: (_input, _start, end) => {
  1253. balanced++;
  1254. return end;
  1255. },
  1256. rightParenthesis: (_input, _start, end) => {
  1257. balanced--;
  1258. if (balanced === 0 && scope !== undefined) {
  1259. /** @type {[number, number]} */
  1260. (result[scope])[1] = end;
  1261. scope = undefined;
  1262. }
  1263. return end;
  1264. },
  1265. function: (input, start, end) => {
  1266. if (balanced === 0) {
  1267. const name = input
  1268. .slice(start, end - 1)
  1269. .replace(/\\/g, "")
  1270. .toLowerCase();
  1271. if (
  1272. name === "url" &&
  1273. result[0] === undefined &&
  1274. result[1] === undefined &&
  1275. result[2] === undefined &&
  1276. result[3] === undefined
  1277. ) {
  1278. scope = 0;
  1279. result[scope] = [start, end + 1, end + 1, end + 1];
  1280. } else if (
  1281. name === "layer" &&
  1282. result[1] === undefined &&
  1283. result[2] === undefined
  1284. ) {
  1285. scope = 1;
  1286. result[scope] = [start, end];
  1287. } else if (name === "supports" && result[2] === undefined) {
  1288. scope = 2;
  1289. result[scope] = [start, end];
  1290. } else {
  1291. scope = undefined;
  1292. }
  1293. }
  1294. balanced++;
  1295. return end;
  1296. },
  1297. identifier: (input, start, end) => {
  1298. if (
  1299. balanced === 0 &&
  1300. result[1] === undefined &&
  1301. result[2] === undefined
  1302. ) {
  1303. const name = input.slice(start, end).replace(/\\/g, "").toLowerCase();
  1304. if (name === "layer") {
  1305. result[1] = [start, end];
  1306. scope = undefined;
  1307. }
  1308. }
  1309. return end;
  1310. },
  1311. semicolon: (_input, start, end) => {
  1312. if (balanced === 0) {
  1313. needStop = true;
  1314. result[3] = [start, end];
  1315. }
  1316. return end;
  1317. }
  1318. };
  1319. while (pos < input.length) {
  1320. // Consume comments.
  1321. pos = consumeComments(input, pos, callbacks);
  1322. // Consume the next input code point.
  1323. pos++;
  1324. pos = consumeAToken(input, pos, callbacks);
  1325. if (needStop) {
  1326. break;
  1327. }
  1328. }
  1329. return result;
  1330. };
  1331. /**
  1332. * @param {string} input input
  1333. * @param {number} pos position
  1334. * @returns {[number, number] | undefined} positions of ident sequence
  1335. */
  1336. module.exports.eatIdentSequence = (input, pos) => {
  1337. pos = module.exports.eatWhitespaceAndComments(input, pos);
  1338. const start = pos;
  1339. if (
  1340. _ifThreeCodePointsWouldStartAnIdentSequence(
  1341. input,
  1342. pos,
  1343. input.charCodeAt(pos),
  1344. input.charCodeAt(pos + 1),
  1345. input.charCodeAt(pos + 2)
  1346. )
  1347. ) {
  1348. return [start, _consumeAnIdentSequence(input, pos, {})];
  1349. }
  1350. return undefined;
  1351. };
  1352. /**
  1353. * @param {string} input input
  1354. * @param {number} pos position
  1355. * @returns {[number, number, boolean] | undefined} positions of ident sequence or string
  1356. */
  1357. module.exports.eatIdentSequenceOrString = (input, pos) => {
  1358. pos = module.exports.eatWhitespaceAndComments(input, pos);
  1359. const start = pos;
  1360. if (
  1361. input.charCodeAt(pos) === CC_QUOTATION_MARK ||
  1362. input.charCodeAt(pos) === CC_APOSTROPHE
  1363. ) {
  1364. return [start, consumeAStringToken(input, pos + 1, {}), false];
  1365. } else if (
  1366. _ifThreeCodePointsWouldStartAnIdentSequence(
  1367. input,
  1368. pos,
  1369. input.charCodeAt(pos),
  1370. input.charCodeAt(pos + 1),
  1371. input.charCodeAt(pos + 2)
  1372. )
  1373. ) {
  1374. return [start, _consumeAnIdentSequence(input, pos, {}), true];
  1375. }
  1376. return undefined;
  1377. };
  1378. /**
  1379. * @param {string} chars characters
  1380. * @returns {(input: string, pos: number) => number} function to eat characters
  1381. */
  1382. module.exports.eatUntil = chars => {
  1383. const charCodes = Array.from({ length: chars.length }, (_, i) =>
  1384. chars.charCodeAt(i)
  1385. );
  1386. const arr = Array.from(
  1387. { length: charCodes.reduce((a, b) => Math.max(a, b), 0) + 1 },
  1388. () => false
  1389. );
  1390. for (const cc of charCodes) {
  1391. arr[cc] = true;
  1392. }
  1393. return (input, pos) => {
  1394. for (;;) {
  1395. const cc = input.charCodeAt(pos);
  1396. if (cc < arr.length && arr[cc]) {
  1397. return pos;
  1398. }
  1399. pos++;
  1400. if (pos === input.length) return pos;
  1401. }
  1402. };
  1403. };