walkCssTokens.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. /*
  2. MIT License http://www.opensource.org/licenses/mit-license.php
  3. Author Tobias Koppers @sokra
  4. */
  5. "use strict";
  6. /**
  7. * @typedef {object} CssTokenCallbacks
  8. * @property {function(string, number): boolean=} isSelector
  9. * @property {function(string, number, number, number, number): number=} url
  10. * @property {function(string, number, number): number=} string
  11. * @property {function(string, number, number): number=} leftParenthesis
  12. * @property {function(string, number, number): number=} rightParenthesis
  13. * @property {function(string, number, number): number=} pseudoFunction
  14. * @property {function(string, number, number): number=} function
  15. * @property {function(string, number, number): number=} pseudoClass
  16. * @property {function(string, number, number): number=} atKeyword
  17. * @property {function(string, number, number): number=} class
  18. * @property {function(string, number, number): number=} identifier
  19. * @property {function(string, number, number): number=} id
  20. * @property {function(string, number, number): number=} leftCurlyBracket
  21. * @property {function(string, number, number): number=} rightCurlyBracket
  22. * @property {function(string, number, number): number=} semicolon
  23. * @property {function(string, number, number): number=} comma
  24. */
  25. /** @typedef {function(string, number, CssTokenCallbacks): number} CharHandler */
  26. // spec: https://drafts.csswg.org/css-syntax/
  27. const CC_LINE_FEED = "\n".charCodeAt(0);
  28. const CC_CARRIAGE_RETURN = "\r".charCodeAt(0);
  29. const CC_FORM_FEED = "\f".charCodeAt(0);
  30. const CC_TAB = "\t".charCodeAt(0);
  31. const CC_SPACE = " ".charCodeAt(0);
  32. const CC_SOLIDUS = "/".charCodeAt(0);
  33. const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0);
  34. const CC_ASTERISK = "*".charCodeAt(0);
  35. const CC_LEFT_PARENTHESIS = "(".charCodeAt(0);
  36. const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0);
  37. const CC_LEFT_CURLY = "{".charCodeAt(0);
  38. const CC_RIGHT_CURLY = "}".charCodeAt(0);
  39. const CC_LEFT_SQUARE = "[".charCodeAt(0);
  40. const CC_RIGHT_SQUARE = "]".charCodeAt(0);
  41. const CC_QUOTATION_MARK = '"'.charCodeAt(0);
  42. const CC_APOSTROPHE = "'".charCodeAt(0);
  43. const CC_FULL_STOP = ".".charCodeAt(0);
  44. const CC_COLON = ":".charCodeAt(0);
  45. const CC_SEMICOLON = ";".charCodeAt(0);
  46. const CC_COMMA = ",".charCodeAt(0);
  47. const CC_PERCENTAGE = "%".charCodeAt(0);
  48. const CC_AT_SIGN = "@".charCodeAt(0);
  49. const CC_LOW_LINE = "_".charCodeAt(0);
  50. const CC_LOWER_A = "a".charCodeAt(0);
  51. const CC_LOWER_U = "u".charCodeAt(0);
  52. const CC_LOWER_E = "e".charCodeAt(0);
  53. const CC_LOWER_Z = "z".charCodeAt(0);
  54. const CC_UPPER_A = "A".charCodeAt(0);
  55. const CC_UPPER_E = "E".charCodeAt(0);
  56. const CC_UPPER_U = "U".charCodeAt(0);
  57. const CC_UPPER_Z = "Z".charCodeAt(0);
  58. const CC_0 = "0".charCodeAt(0);
  59. const CC_9 = "9".charCodeAt(0);
  60. const CC_NUMBER_SIGN = "#".charCodeAt(0);
  61. const CC_PLUS_SIGN = "+".charCodeAt(0);
  62. const CC_HYPHEN_MINUS = "-".charCodeAt(0);
  63. const CC_LESS_THAN_SIGN = "<".charCodeAt(0);
  64. const CC_GREATER_THAN_SIGN = ">".charCodeAt(0);
  65. /**
  66. * @param {number} cc char code
  67. * @returns {boolean} true, if cc is a newline
  68. */
  69. const _isNewLine = cc => {
  70. return (
  71. cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED
  72. );
  73. };
  74. /** @type {CharHandler} */
  75. const consumeSpace = (input, pos, callbacks) => {
  76. /** @type {number} */
  77. let cc;
  78. do {
  79. pos++;
  80. cc = input.charCodeAt(pos);
  81. } while (_isWhiteSpace(cc));
  82. return pos;
  83. };
  84. /**
  85. * @param {number} cc char code
  86. * @returns {boolean} true, if cc is a newline
  87. */
  88. const _isNewline = cc => {
  89. return (
  90. cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED
  91. );
  92. };
  93. /**
  94. * @param {number} cc char code
  95. * @returns {boolean} true, if cc is a space (U+0009 CHARACTER TABULATION or U+0020 SPACE)
  96. */
  97. const _isSpace = cc => {
  98. return cc === CC_TAB || cc === CC_SPACE;
  99. };
  100. /**
  101. * @param {number} cc char code
  102. * @returns {boolean} true, if cc is a whitespace
  103. */
  104. const _isWhiteSpace = cc => {
  105. return _isNewline(cc) || _isSpace(cc);
  106. };
  107. /**
  108. * ident-start code point
  109. *
  110. * A letter, a non-ASCII code point, or U+005F LOW LINE (_).
  111. *
  112. * @param {number} cc char code
  113. * @returns {boolean} true, if cc is a start code point of an identifier
  114. */
  115. const isIdentStartCodePoint = cc => {
  116. return (
  117. (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
  118. (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
  119. cc === CC_LOW_LINE ||
  120. cc >= 0x80
  121. );
  122. };
  123. /** @type {CharHandler} */
  124. const consumeDelimToken = (input, pos, callbacks) => {
  125. return pos + 1;
  126. };
  127. /** @type {CharHandler} */
  128. const consumeComments = (input, pos, callbacks) => {
  129. // If the next two input code point are U+002F SOLIDUS (/) followed by a U+002A
  130. // ASTERISK (*), consume them and all following code points up to and including
  131. // the first U+002A ASTERISK (*) followed by a U+002F SOLIDUS (/), or up to an
  132. // EOF code point. Return to the start of this step.
  133. //
  134. // If the preceding paragraph ended by consuming an EOF code point, this is a parse error.
  135. // But we are silent on errors.
  136. if (
  137. input.charCodeAt(pos) === CC_SOLIDUS &&
  138. input.charCodeAt(pos + 1) === CC_ASTERISK
  139. ) {
  140. pos += 1;
  141. while (pos < input.length) {
  142. if (
  143. input.charCodeAt(pos) === CC_ASTERISK &&
  144. input.charCodeAt(pos + 1) === CC_SOLIDUS
  145. ) {
  146. pos += 2;
  147. break;
  148. }
  149. pos++;
  150. }
  151. }
  152. return pos;
  153. };
  154. /** @type {function(number): CharHandler} */
  155. const consumeString = quote_cc => (input, pos, callbacks) => {
  156. const start = pos;
  157. pos = _consumeString(input, pos, quote_cc);
  158. if (callbacks.string !== undefined) {
  159. pos = callbacks.string(input, start, pos);
  160. }
  161. return pos;
  162. };
  163. /**
  164. * @param {string} input input
  165. * @param {number} pos position
  166. * @param {number} quote_cc quote char code
  167. * @returns {number} new position
  168. */
  169. const _consumeString = (input, pos, quote_cc) => {
  170. pos++;
  171. for (;;) {
  172. if (pos === input.length) return pos;
  173. const cc = input.charCodeAt(pos);
  174. if (cc === quote_cc) return pos + 1;
  175. if (_isNewLine(cc)) {
  176. // bad string
  177. return pos;
  178. }
  179. if (cc === CC_REVERSE_SOLIDUS) {
  180. // we don't need to fully parse the escaped code point
  181. // just skip over a potential new line
  182. pos++;
  183. if (pos === input.length) return pos;
  184. pos++;
  185. } else {
  186. pos++;
  187. }
  188. }
  189. };
  190. /**
  191. * @param {number} cc char code
  192. * @returns {boolean} is identifier start code
  193. */
  194. const _isIdentifierStartCode = cc => {
  195. return (
  196. cc === CC_LOW_LINE ||
  197. (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) ||
  198. (cc >= CC_UPPER_A && cc <= CC_UPPER_Z) ||
  199. cc > 0x80
  200. );
  201. };
  202. /**
  203. * @param {number} first first code point
  204. * @param {number} second second code point
  205. * @returns {boolean} true if two code points are a valid escape
  206. */
  207. const _isTwoCodePointsAreValidEscape = (first, second) => {
  208. if (first !== CC_REVERSE_SOLIDUS) return false;
  209. if (_isNewLine(second)) return false;
  210. return true;
  211. };
  212. /**
  213. * @param {number} cc char code
  214. * @returns {boolean} is digit
  215. */
  216. const _isDigit = cc => {
  217. return cc >= CC_0 && cc <= CC_9;
  218. };
  219. /**
  220. * @param {string} input input
  221. * @param {number} pos position
  222. * @returns {boolean} true, if input at pos starts an identifier
  223. */
  224. const _startsIdentifier = (input, pos) => {
  225. const cc = input.charCodeAt(pos);
  226. if (cc === CC_HYPHEN_MINUS) {
  227. if (pos === input.length) return false;
  228. const cc = input.charCodeAt(pos + 1);
  229. if (cc === CC_HYPHEN_MINUS) return true;
  230. if (cc === CC_REVERSE_SOLIDUS) {
  231. const cc = input.charCodeAt(pos + 2);
  232. return !_isNewLine(cc);
  233. }
  234. return _isIdentifierStartCode(cc);
  235. }
  236. if (cc === CC_REVERSE_SOLIDUS) {
  237. const cc = input.charCodeAt(pos + 1);
  238. return !_isNewLine(cc);
  239. }
  240. return _isIdentifierStartCode(cc);
  241. };
  242. /** @type {CharHandler} */
  243. const consumeNumberSign = (input, pos, callbacks) => {
  244. const start = pos;
  245. pos++;
  246. if (pos === input.length) return pos;
  247. if (callbacks.isSelector(input, pos) && _startsIdentifier(input, pos)) {
  248. pos = _consumeIdentifier(input, pos, callbacks);
  249. if (callbacks.id !== undefined) {
  250. return callbacks.id(input, start, pos);
  251. }
  252. }
  253. return pos;
  254. };
  255. /** @type {CharHandler} */
  256. const consumeMinus = (input, pos, callbacks) => {
  257. const start = pos;
  258. pos++;
  259. if (pos === input.length) return pos;
  260. const cc = input.charCodeAt(pos);
  261. // If the input stream starts with a number, reconsume the current input code point, consume a numeric token, and return it.
  262. if (cc === CC_FULL_STOP || _isDigit(cc)) {
  263. return consumeNumericToken(input, pos, callbacks);
  264. } else if (cc === CC_HYPHEN_MINUS) {
  265. pos++;
  266. if (pos === input.length) return pos;
  267. const cc = input.charCodeAt(pos);
  268. if (cc === CC_GREATER_THAN_SIGN) {
  269. return pos + 1;
  270. } else {
  271. pos = _consumeIdentifier(input, pos, callbacks);
  272. if (callbacks.identifier !== undefined) {
  273. return callbacks.identifier(input, start, pos);
  274. }
  275. }
  276. } else if (cc === CC_REVERSE_SOLIDUS) {
  277. if (pos + 1 === input.length) return pos;
  278. const cc = input.charCodeAt(pos + 1);
  279. if (_isNewLine(cc)) return pos;
  280. pos = _consumeIdentifier(input, pos, callbacks);
  281. if (callbacks.identifier !== undefined) {
  282. return callbacks.identifier(input, start, pos);
  283. }
  284. } else if (_isIdentifierStartCode(cc)) {
  285. pos = consumeOtherIdentifier(input, pos - 1, callbacks);
  286. }
  287. return pos;
  288. };
  289. /** @type {CharHandler} */
  290. const consumeDot = (input, pos, callbacks) => {
  291. const start = pos;
  292. pos++;
  293. if (pos === input.length) return pos;
  294. const cc = input.charCodeAt(pos);
  295. if (_isDigit(cc)) return consumeNumericToken(input, pos - 2, callbacks);
  296. if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
  297. return pos;
  298. pos = _consumeIdentifier(input, pos, callbacks);
  299. if (callbacks.class !== undefined) return callbacks.class(input, start, pos);
  300. return pos;
  301. };
  302. /** @type {CharHandler} */
  303. const consumeNumericToken = (input, pos, callbacks) => {
  304. pos = _consumeNumber(input, pos, callbacks);
  305. if (pos === input.length) return pos;
  306. if (_startsIdentifier(input, pos))
  307. return _consumeIdentifier(input, pos, callbacks);
  308. const cc = input.charCodeAt(pos);
  309. if (cc === CC_PERCENTAGE) return pos + 1;
  310. return pos;
  311. };
  312. /** @type {CharHandler} */
  313. const consumeOtherIdentifier = (input, pos, callbacks) => {
  314. const start = pos;
  315. pos = _consumeIdentifier(input, pos, callbacks);
  316. if (pos !== input.length && input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) {
  317. pos++;
  318. if (callbacks.function !== undefined) {
  319. return callbacks.function(input, start, pos);
  320. }
  321. } else {
  322. if (callbacks.identifier !== undefined) {
  323. return callbacks.identifier(input, start, pos);
  324. }
  325. }
  326. return pos;
  327. };
  328. /** @type {CharHandler} */
  329. const consumePotentialUrl = (input, pos, callbacks) => {
  330. const start = pos;
  331. pos = _consumeIdentifier(input, pos, callbacks);
  332. const nextPos = pos + 1;
  333. if (
  334. pos === start + 3 &&
  335. input.slice(start, nextPos).toLowerCase() === "url("
  336. ) {
  337. pos++;
  338. let cc = input.charCodeAt(pos);
  339. while (_isWhiteSpace(cc)) {
  340. pos++;
  341. if (pos === input.length) return pos;
  342. cc = input.charCodeAt(pos);
  343. }
  344. if (cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE) {
  345. if (callbacks.function !== undefined) {
  346. return callbacks.function(input, start, nextPos);
  347. }
  348. return nextPos;
  349. } else {
  350. const contentStart = pos;
  351. /** @type {number} */
  352. let contentEnd;
  353. for (;;) {
  354. if (cc === CC_REVERSE_SOLIDUS) {
  355. pos++;
  356. if (pos === input.length) return pos;
  357. pos++;
  358. } else if (_isWhiteSpace(cc)) {
  359. contentEnd = pos;
  360. do {
  361. pos++;
  362. if (pos === input.length) return pos;
  363. cc = input.charCodeAt(pos);
  364. } while (_isWhiteSpace(cc));
  365. if (cc !== CC_RIGHT_PARENTHESIS) return pos;
  366. pos++;
  367. if (callbacks.url !== undefined) {
  368. return callbacks.url(input, start, pos, contentStart, contentEnd);
  369. }
  370. return pos;
  371. } else if (cc === CC_RIGHT_PARENTHESIS) {
  372. contentEnd = pos;
  373. pos++;
  374. if (callbacks.url !== undefined) {
  375. return callbacks.url(input, start, pos, contentStart, contentEnd);
  376. }
  377. return pos;
  378. } else if (cc === CC_LEFT_PARENTHESIS) {
  379. return pos;
  380. } else {
  381. pos++;
  382. }
  383. if (pos === input.length) return pos;
  384. cc = input.charCodeAt(pos);
  385. }
  386. }
  387. } else {
  388. if (callbacks.identifier !== undefined) {
  389. return callbacks.identifier(input, start, pos);
  390. }
  391. return pos;
  392. }
  393. };
  394. /** @type {CharHandler} */
  395. const consumePotentialPseudo = (input, pos, callbacks) => {
  396. const start = pos;
  397. pos++;
  398. if (!callbacks.isSelector(input, pos) || !_startsIdentifier(input, pos))
  399. return pos;
  400. pos = _consumeIdentifier(input, pos, callbacks);
  401. let cc = input.charCodeAt(pos);
  402. if (cc === CC_LEFT_PARENTHESIS) {
  403. pos++;
  404. if (callbacks.pseudoFunction !== undefined) {
  405. return callbacks.pseudoFunction(input, start, pos);
  406. }
  407. return pos;
  408. }
  409. if (callbacks.pseudoClass !== undefined) {
  410. return callbacks.pseudoClass(input, start, pos);
  411. }
  412. return pos;
  413. };
  414. /** @type {CharHandler} */
  415. const consumeLeftParenthesis = (input, pos, callbacks) => {
  416. pos++;
  417. if (callbacks.leftParenthesis !== undefined) {
  418. return callbacks.leftParenthesis(input, pos - 1, pos);
  419. }
  420. return pos;
  421. };
  422. /** @type {CharHandler} */
  423. const consumeRightParenthesis = (input, pos, callbacks) => {
  424. pos++;
  425. if (callbacks.rightParenthesis !== undefined) {
  426. return callbacks.rightParenthesis(input, pos - 1, pos);
  427. }
  428. return pos;
  429. };
  430. /** @type {CharHandler} */
  431. const consumeLeftCurlyBracket = (input, pos, callbacks) => {
  432. pos++;
  433. if (callbacks.leftCurlyBracket !== undefined) {
  434. return callbacks.leftCurlyBracket(input, pos - 1, pos);
  435. }
  436. return pos;
  437. };
  438. /** @type {CharHandler} */
  439. const consumeRightCurlyBracket = (input, pos, callbacks) => {
  440. pos++;
  441. if (callbacks.rightCurlyBracket !== undefined) {
  442. return callbacks.rightCurlyBracket(input, pos - 1, pos);
  443. }
  444. return pos;
  445. };
  446. /** @type {CharHandler} */
  447. const consumeSemicolon = (input, pos, callbacks) => {
  448. pos++;
  449. if (callbacks.semicolon !== undefined) {
  450. return callbacks.semicolon(input, pos - 1, pos);
  451. }
  452. return pos;
  453. };
  454. /** @type {CharHandler} */
  455. const consumeComma = (input, pos, callbacks) => {
  456. pos++;
  457. if (callbacks.comma !== undefined) {
  458. return callbacks.comma(input, pos - 1, pos);
  459. }
  460. return pos;
  461. };
  462. /** @type {CharHandler} */
  463. const _consumeIdentifier = (input, pos) => {
  464. for (;;) {
  465. const cc = input.charCodeAt(pos);
  466. if (cc === CC_REVERSE_SOLIDUS) {
  467. pos++;
  468. if (pos === input.length) return pos;
  469. pos++;
  470. } else if (
  471. _isIdentifierStartCode(cc) ||
  472. _isDigit(cc) ||
  473. cc === CC_HYPHEN_MINUS
  474. ) {
  475. pos++;
  476. } else {
  477. return pos;
  478. }
  479. }
  480. };
  481. /** @type {CharHandler} */
  482. const _consumeNumber = (input, pos) => {
  483. pos++;
  484. if (pos === input.length) return pos;
  485. let cc = input.charCodeAt(pos);
  486. while (_isDigit(cc)) {
  487. pos++;
  488. if (pos === input.length) return pos;
  489. cc = input.charCodeAt(pos);
  490. }
  491. if (cc === CC_FULL_STOP && pos + 1 !== input.length) {
  492. const next = input.charCodeAt(pos + 1);
  493. if (_isDigit(next)) {
  494. pos += 2;
  495. cc = input.charCodeAt(pos);
  496. while (_isDigit(cc)) {
  497. pos++;
  498. if (pos === input.length) return pos;
  499. cc = input.charCodeAt(pos);
  500. }
  501. }
  502. }
  503. if (cc === CC_LOWER_E || cc === CC_UPPER_E) {
  504. if (pos + 1 !== input.length) {
  505. const next = input.charCodeAt(pos + 2);
  506. if (_isDigit(next)) {
  507. pos += 2;
  508. } else if (
  509. (next === CC_HYPHEN_MINUS || next === CC_PLUS_SIGN) &&
  510. pos + 2 !== input.length
  511. ) {
  512. const next = input.charCodeAt(pos + 2);
  513. if (_isDigit(next)) {
  514. pos += 3;
  515. } else {
  516. return pos;
  517. }
  518. } else {
  519. return pos;
  520. }
  521. }
  522. } else {
  523. return pos;
  524. }
  525. cc = input.charCodeAt(pos);
  526. while (_isDigit(cc)) {
  527. pos++;
  528. if (pos === input.length) return pos;
  529. cc = input.charCodeAt(pos);
  530. }
  531. return pos;
  532. };
  533. /** @type {CharHandler} */
  534. const consumeLessThan = (input, pos, callbacks) => {
  535. if (input.slice(pos + 1, pos + 4) === "!--") return pos + 4;
  536. return pos + 1;
  537. };
  538. /** @type {CharHandler} */
  539. const consumeAt = (input, pos, callbacks) => {
  540. const start = pos;
  541. pos++;
  542. if (pos === input.length) return pos;
  543. if (_startsIdentifier(input, pos)) {
  544. pos = _consumeIdentifier(input, pos, callbacks);
  545. if (callbacks.atKeyword !== undefined) {
  546. pos = callbacks.atKeyword(input, start, pos);
  547. }
  548. }
  549. return pos;
  550. };
  551. /** @type {CharHandler} */
  552. const consumeReverseSolidus = (input, pos, callbacks) => {
  553. const start = pos;
  554. pos++;
  555. if (pos === input.length) return pos;
  556. // If the input stream starts with a valid escape, reconsume the current input code point, consume an ident-like token, and return it.
  557. if (
  558. _isTwoCodePointsAreValidEscape(
  559. input.charCodeAt(start),
  560. input.charCodeAt(pos)
  561. )
  562. ) {
  563. return consumeOtherIdentifier(input, pos - 1, callbacks);
  564. }
  565. // Otherwise, this is a parse error. Return a <delim-token> with its value set to the current input code point.
  566. return pos;
  567. };
  568. const CHAR_MAP = Array.from({ length: 0x80 }, (_, cc) => {
  569. // https://drafts.csswg.org/css-syntax/#consume-token
  570. switch (cc) {
  571. // whitespace
  572. case CC_LINE_FEED:
  573. case CC_CARRIAGE_RETURN:
  574. case CC_FORM_FEED:
  575. case CC_TAB:
  576. case CC_SPACE:
  577. return consumeSpace;
  578. // U+0022 QUOTATION MARK (")
  579. case CC_QUOTATION_MARK:
  580. return consumeString(cc);
  581. // U+0023 NUMBER SIGN (#)
  582. case CC_NUMBER_SIGN:
  583. return consumeNumberSign;
  584. // U+0027 APOSTROPHE (')
  585. case CC_APOSTROPHE:
  586. return consumeString(cc);
  587. // U+0028 LEFT PARENTHESIS (()
  588. case CC_LEFT_PARENTHESIS:
  589. return consumeLeftParenthesis;
  590. // U+0029 RIGHT PARENTHESIS ())
  591. case CC_RIGHT_PARENTHESIS:
  592. return consumeRightParenthesis;
  593. // U+002B PLUS SIGN (+)
  594. case CC_PLUS_SIGN:
  595. return consumeNumericToken;
  596. // U+002C COMMA (,)
  597. case CC_COMMA:
  598. return consumeComma;
  599. // U+002D HYPHEN-MINUS (-)
  600. case CC_HYPHEN_MINUS:
  601. return consumeMinus;
  602. // U+002E FULL STOP (.)
  603. case CC_FULL_STOP:
  604. return consumeDot;
  605. // U+003A COLON (:)
  606. case CC_COLON:
  607. return consumePotentialPseudo;
  608. // U+003B SEMICOLON (;)
  609. case CC_SEMICOLON:
  610. return consumeSemicolon;
  611. // U+003C LESS-THAN SIGN (<)
  612. case CC_LESS_THAN_SIGN:
  613. return consumeLessThan;
  614. // U+0040 COMMERCIAL AT (@)
  615. case CC_AT_SIGN:
  616. return consumeAt;
  617. // U+005B LEFT SQUARE BRACKET ([)
  618. case CC_LEFT_SQUARE:
  619. return consumeDelimToken;
  620. // U+005C REVERSE SOLIDUS (\)
  621. case CC_REVERSE_SOLIDUS:
  622. return consumeReverseSolidus;
  623. // U+005D RIGHT SQUARE BRACKET (])
  624. case CC_RIGHT_SQUARE:
  625. return consumeDelimToken;
  626. // U+007B LEFT CURLY BRACKET ({)
  627. case CC_LEFT_CURLY:
  628. return consumeLeftCurlyBracket;
  629. // U+007D RIGHT CURLY BRACKET (})
  630. case CC_RIGHT_CURLY:
  631. return consumeRightCurlyBracket;
  632. // Optimization
  633. case CC_LOWER_U:
  634. case CC_UPPER_U:
  635. return consumePotentialUrl;
  636. default:
  637. // digit
  638. if (_isDigit(cc)) return consumeNumericToken;
  639. // ident-start code point
  640. if (isIdentStartCodePoint(cc)) {
  641. return consumeOtherIdentifier;
  642. }
  643. // EOF, but we don't have it
  644. // anything else
  645. return consumeDelimToken;
  646. }
  647. });
  648. /**
  649. * @param {string} input input css
  650. * @param {CssTokenCallbacks} callbacks callbacks
  651. * @returns {void}
  652. */
  653. module.exports = (input, callbacks) => {
  654. // This section describes how to consume a token from a stream of code points. It will return a single token of any type.
  655. let pos = 0;
  656. while (pos < input.length) {
  657. // Consume comments.
  658. pos = consumeComments(input, pos, callbacks);
  659. const cc = input.charCodeAt(pos);
  660. // Consume the next input code point.
  661. if (cc < 0x80) {
  662. pos = CHAR_MAP[cc](input, pos, callbacks);
  663. } else {
  664. pos++;
  665. }
  666. }
  667. };
  668. module.exports.isIdentStartCodePoint = isIdentStartCodePoint;
  669. /**
  670. * @param {string} input input
  671. * @param {number} pos position
  672. * @returns {number} position after comments
  673. */
  674. module.exports.eatComments = (input, pos) => {
  675. for (;;) {
  676. let originalPos = pos;
  677. pos = consumeComments(input, pos, {});
  678. if (originalPos === pos) {
  679. break;
  680. }
  681. }
  682. return pos;
  683. };
  684. /**
  685. * @param {string} input input
  686. * @param {number} pos position
  687. * @returns {number} position after whitespace
  688. */
  689. module.exports.eatWhitespace = (input, pos) => {
  690. while (_isWhiteSpace(input.charCodeAt(pos))) {
  691. pos++;
  692. }
  693. return pos;
  694. };
  695. /**
  696. * @param {string} input input
  697. * @param {number} pos position
  698. * @returns {number} position after whitespace and comments
  699. */
  700. module.exports.eatWhitespaceAndComments = (input, pos) => {
  701. for (;;) {
  702. let originalPos = pos;
  703. pos = consumeComments(input, pos, {});
  704. while (_isWhiteSpace(input.charCodeAt(pos))) {
  705. pos++;
  706. }
  707. if (originalPos === pos) {
  708. break;
  709. }
  710. }
  711. return pos;
  712. };
  713. /**
  714. * @param {string} input input
  715. * @param {number} pos position
  716. * @returns {number} position after whitespace
  717. */
  718. module.exports.eatWhiteLine = (input, pos) => {
  719. for (;;) {
  720. const cc = input.charCodeAt(pos);
  721. if (_isSpace(cc)) {
  722. pos++;
  723. continue;
  724. }
  725. if (_isNewLine(cc)) pos++;
  726. // For `\r\n`
  727. if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos + 1) === CC_LINE_FEED)
  728. pos++;
  729. break;
  730. }
  731. return pos;
  732. };