source: frontend/node_modules/webpack/lib/css/walkCssTokens.js

Last change on this file was 9af201e, checked in by MBK <marija.karapandzova@…>, 11 days ago

Fix frontend appearance

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