walkCssTokens.js 49 KB

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