walkCssTokens.js 53 KB

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