UNPKG

webpack

Version:

Packs ECMAScript/CommonJs/AMD modules for the browser. Allows you to split your codebase into multiple bundles, which can be loaded on demand. Supports loaders to preprocess files, i.e. json, jsx, es7, css, less, ... and your custom stuff.

1,380 lines (1,310 loc) 158 kB
/* MIT License http://www.opensource.org/licenses/mit-license.php Author Tobias Koppers @sokra */ "use strict"; const LocConverter = require("../util/LocConverter"); const GenericSourceProcessor = require("../util/SourceProcessor"); const { makeCacheable } = require("../util/identifier"); // spec: https://drafts.csswg.org/css-syntax/ /** * @typedef {object} CssWhitespaceToken * @property {number} type * @property {number} start byte offset of the first whitespace code point * @property {number} end byte offset just past the last whitespace code point */ /** * @typedef {object} CssCommentToken * @property {number} type * @property {number} start byte offset of the opening `/` * @property {number} end byte offset just past the closing `/` */ /** * @typedef {object} CssStringToken * @property {number} type * @property {number} start byte offset of the opening quote * @property {number} end byte offset just past the closing quote (or EOF for unterminated strings) */ /** * @typedef {object} CssBadStringToken * @property {number} type * @property {number} start byte offset of the opening quote * @property {number} end byte offset where parsing gave up (typically the newline that broke the string) */ /** * @typedef {object} CssLeftCurlyBracketToken * @property {number} type * @property {number} start byte offset of `{` * @property {number} end `start + 1` */ /** * @typedef {object} CssRightCurlyBracketToken * @property {number} type * @property {number} start byte offset of `}` * @property {number} end `start + 1` */ /** * @typedef {object} CssLeftSquareBracketToken * @property {number} type * @property {number} start byte offset of `[` * @property {number} end `start + 1` */ /** * @typedef {object} CssRightSquareBracketToken * @property {number} type * @property {number} start byte offset of `]` * @property {number} end `start + 1` */ /** * @typedef {object} CssLeftParenthesisToken * @property {number} type * @property {number} start byte offset of `(` * @property {number} end `start + 1` */ /** * @typedef {object} CssRightParenthesisToken * @property {number} type * @property {number} start byte offset of `)` * @property {number} end `start + 1` */ /** * @typedef {object} CssFunctionToken * @property {number} type * @property {number} start byte offset of the function name's first code point * @property {number} end byte offset just past the `(` that closes the function token */ /** * @typedef {object} CssUrlToken * @property {number} type * @property {number} start byte offset of the `url(` keyword (i.e. the `u`) * @property {number} end byte offset just past the closing `)` (or EOF) * @property {number} contentStart byte offset of the first code point of the unquoted URL content (post leading whitespace) * @property {number} contentEnd byte offset just past the last code point of the unquoted URL content (pre trailing whitespace / `)` / EOF) */ /** * @typedef {object} CssBadUrlToken * @property {number} type * @property {number} start byte offset of the `url(` keyword * @property {number} end byte offset where parsing gave up (past the recovery `)` or EOF) */ /** * @typedef {object} CssColonToken * @property {number} type * @property {number} start byte offset of `:` * @property {number} end `start + 1` */ /** * @typedef {object} CssAtKeywordToken * @property {number} type * @property {number} start byte offset of `@` * @property {number} end byte offset just past the last ident-sequence code point */ /** * @typedef {object} CssDelimToken * @property {number} type * @property {number} start byte offset of the delim code point * @property {number} end `start + 1` */ /** * @typedef {object} CssIdentToken * @property {number} type * @property {number} start byte offset of the first ident code point * @property {number} end byte offset just past the last ident-sequence code point */ /** * @typedef {object} CssPercentageToken * @property {number} type * @property {number} start byte offset of the first numeric code point * @property {number} end byte offset just past the `%` */ /** * @typedef {object} CssNumberToken * @property {number} type * @property {number} start byte offset of the first numeric code point * @property {number} end byte offset just past the last numeric code point */ /** * @typedef {object} CssDimensionToken * @property {number} type * @property {number} start byte offset of the first numeric code point * @property {number} end byte offset just past the last unit ident code point * @property {number} unitStart byte offset of the first unit-ident code point (== end of the numeric run) */ /** * @typedef {object} CssHashToken * @property {number} type * @property {number} start byte offset of `#` * @property {number} end byte offset just past the last ident-sequence code point * @property {boolean} isId true when the hash starts an ident sequence (`#foo`), false for non-ident hashes (`#1abc`) */ /** * @typedef {object} CssSemicolonToken * @property {number} type * @property {number} start byte offset of `;` * @property {number} end `start + 1` */ /** * @typedef {object} CssCommaToken * @property {number} type * @property {number} start byte offset of `,` * @property {number} end `start + 1` */ /** * @typedef {object} CssCdoToken * @property {number} type * @property {number} start byte offset of `<` * @property {number} end byte offset just past `<!--` */ /** * @typedef {object} CssCdcToken * @property {number} type * @property {number} start byte offset of `-` * @property {number} end byte offset just past `-->` */ /** * @typedef {CssWhitespaceToken | CssCommentToken | CssStringToken | CssBadStringToken | CssLeftCurlyBracketToken | CssRightCurlyBracketToken | CssLeftSquareBracketToken | CssRightSquareBracketToken | CssLeftParenthesisToken | CssRightParenthesisToken | CssFunctionToken | CssUrlToken | CssBadUrlToken | CssColonToken | CssAtKeywordToken | CssDelimToken | CssIdentToken | CssPercentageToken | CssNumberToken | CssDimensionToken | CssHashToken | CssSemicolonToken | CssCommaToken | CssCdoToken | CssCdcToken} CssToken */ const CC_LINE_FEED = "\n".charCodeAt(0); const CC_CARRIAGE_RETURN = "\r".charCodeAt(0); const CC_FORM_FEED = "\f".charCodeAt(0); const CC_TAB = "\t".charCodeAt(0); const CC_SPACE = " ".charCodeAt(0); const CC_SOLIDUS = "/".charCodeAt(0); const CC_REVERSE_SOLIDUS = "\\".charCodeAt(0); const CC_ASTERISK = "*".charCodeAt(0); const CC_LEFT_PARENTHESIS = "(".charCodeAt(0); const CC_RIGHT_PARENTHESIS = ")".charCodeAt(0); const CC_LEFT_CURLY = "{".charCodeAt(0); const CC_RIGHT_CURLY = "}".charCodeAt(0); const CC_LEFT_SQUARE = "[".charCodeAt(0); const CC_RIGHT_SQUARE = "]".charCodeAt(0); const CC_QUOTATION_MARK = '"'.charCodeAt(0); const CC_APOSTROPHE = "'".charCodeAt(0); const CC_FULL_STOP = ".".charCodeAt(0); const CC_COLON = ":".charCodeAt(0); const CC_SEMICOLON = ";".charCodeAt(0); const CC_COMMA = ",".charCodeAt(0); const CC_PERCENTAGE = "%".charCodeAt(0); const CC_AT_SIGN = "@".charCodeAt(0); const CC_LOW_LINE = "_".charCodeAt(0); const CC_LOWER_A = "a".charCodeAt(0); const CC_LOWER_D = "d".charCodeAt(0); const CC_LOWER_F = "f".charCodeAt(0); const CC_LOWER_E = "e".charCodeAt(0); const CC_LOWER_U = "u".charCodeAt(0); const CC_LOWER_R = "r".charCodeAt(0); const CC_LOWER_L = "l".charCodeAt(0); const CC_LOWER_Z = "z".charCodeAt(0); const CC_EXCLAMATION = "!".charCodeAt(0); const CC_UPPER_A = "A".charCodeAt(0); const CC_UPPER_F = "F".charCodeAt(0); const CC_UPPER_E = "E".charCodeAt(0); const CC_UPPER_Z = "Z".charCodeAt(0); const CC_0 = "0".charCodeAt(0); const CC_9 = "9".charCodeAt(0); const CC_NUMBER_SIGN = "#".charCodeAt(0); const CC_PLUS_SIGN = "+".charCodeAt(0); const CC_HYPHEN_MINUS = "-".charCodeAt(0); const CC_LESS_THAN_SIGN = "<".charCodeAt(0); const CC_GREATER_THAN_SIGN = ">".charCodeAt(0); // Lexer token types (CSS Syntax Level 3 §4) plus the `<eof-token>`. Numeric so // the per-token `type` slot stays compact and `next` / `consume` / the consume // algorithms dispatch on integer `===` instead of string comparison. Exported // alongside `readToken` (the per-token lexer primitive) for the unit test. const TT_COMMENT = 1; const TT_WHITESPACE = 2; const TT_STRING = 3; const TT_BAD_STRING_TOKEN = 4; const TT_HASH = 5; const TT_DELIM = 6; // The three opening brackets are kept contiguous (7..9) so "is this an opening // bracket?" is a single range check (`>= TT_LEFT_PARENTHESIS && <= TT_LEFT_CURLY_BRACKET`). const TT_LEFT_PARENTHESIS = 7; const TT_LEFT_SQUARE_BRACKET = 8; const TT_LEFT_CURLY_BRACKET = 9; const TT_RIGHT_PARENTHESIS = 10; const TT_RIGHT_SQUARE_BRACKET = 11; const TT_RIGHT_CURLY_BRACKET = 12; const TT_COMMA = 13; const TT_COLON = 14; const TT_SEMICOLON = 15; const TT_AT_KEYWORD = 16; const TT_FUNCTION = 17; const TT_URL = 18; const TT_BAD_URL_TOKEN = 19; const TT_IDENTIFIER = 20; const TT_NUMBER = 21; const TT_PERCENTAGE = 22; const TT_DIMENSION = 23; const TT_CDO = 24; const TT_CDC = 25; const TT_EOF = 26; // The opening bracket types (7..9) and their mirror closers (10..12) are laid // out so a closer is always `opener + 3`; `consumeASimpleBlock` uses that // directly. The associated block char is a dense array indexed by the opener's // offset from `TT_LEFT_PARENTHESIS` — a plain element load instead of a numeric // object-key lookup. /** @type {SimpleBlockToken[]} */ const BLOCK_TOKEN_CHAR = ["(", "[", "{"]; /** * @param {number} cc char code * @returns {boolean} true, if cc is a newline (per the spec: LF, CR, or FF) */ const _isNewline = (cc) => cc === CC_LINE_FEED || cc === CC_CARRIAGE_RETURN || cc === CC_FORM_FEED; /** * If the source had a CR followed by an LF, advance past the LF — * the spec normalises CRLF to LF during preprocessing. * @param {number} cc char code already consumed (the CR) * @param {string} input input * @param {number} pos position just past `cc` * @returns {number} position past the CRLF pair (or unchanged for bare CR) */ const consumeExtraNewline = (cc, input, pos) => { if (cc === CC_CARRIAGE_RETURN && input.charCodeAt(pos) === CC_LINE_FEED) { pos++; } return pos; }; /** * @param {number} cc char code * @returns {boolean} true, if cc is space or tab */ const _isSpace = (cc) => cc === CC_SPACE || cc === CC_TAB; /** * @param {number} cc char code * @returns {boolean} true, if cc is whitespace (space/tab/newline) */ // Space-first: U+0020 is the common case, so it short-circuits before the // rarer tab / newline tests. const _isWhiteSpace = (cc) => _isSpace(cc) || _isNewline(cc); // Whitespace membership table for the run-consumption loop — one load instead // of up to five compares per char. EOF (NaN) / non-ASCII index to undefined. const _wsTable = new Uint8Array(128); _wsTable[CC_SPACE] = 1; _wsTable[CC_TAB] = 1; _wsTable[CC_LINE_FEED] = 1; _wsTable[CC_CARRIAGE_RETURN] = 1; _wsTable[CC_FORM_FEED] = 1; /** * @param {number} cc char code * @returns {boolean} true, if cc is a digit */ const _isDigit = (cc) => cc >= CC_0 && cc <= CC_9; /** * @param {number} cc char code * @returns {boolean} true, if cc is a hex digit */ const _isHexDigit = (cc) => _isDigit(cc) || (cc >= CC_UPPER_A && cc <= CC_UPPER_F) || (cc >= CC_LOWER_A && cc <= CC_LOWER_F); /** * @param {number} cc char code * @returns {boolean} is letter (a-z / A-Z) */ const _isLetter = (cc) => (cc >= CC_LOWER_A && cc <= CC_LOWER_Z) || (cc >= CC_UPPER_A && cc <= CC_UPPER_Z); /** * Spec: ident-start = letter / non-ASCII / `_`. Internal helper that * accepts an explicit char code (lookahead). * @param {number} cc char code * @returns {boolean} true, if cc is an ident-start code point */ const _isIdentStartCodePointCC = (cc) => _isLetter(cc) || cc >= 0x80 || cc === CC_LOW_LINE; /** * Spec: ident-code = ident-start / digit / hyphen-minus. */ // Full `charCodeAt` range (0..0xFFFF) so the per-code-point ident test is one // table load with no `cc < 128` branch — `_consumeAnIdentSequence` runs this on // every character of every ident / class / property name (the tokenizer's // hottest loop). Every non-ASCII code unit (>= 0x80) is an ident code point per // spec, so those default to 1; only the ASCII rows carry real classification. // Callers must index with `cc | 0`: EOF (`charCodeAt` → NaN) becomes 0 (NUL, // not an ident) — a raw NaN index is an out-of-range access that permanently // degrades the load site's IC. const _identCharTable = new Uint8Array(0x10000).fill(1); for (let i = 0; i < 128; i++) { _identCharTable[i] = _isLetter(i) || i === CC_LOW_LINE || _isDigit(i) || i === CC_HYPHEN_MINUS ? 1 : 0; } /** * @param {number} cc char code * @returns {boolean} true, if cc is an ident-sequence code point */ const _isIdentCodePoint = (cc) => _identCharTable[cc | 0] === 1; /** * ASCII case-insensitive equality against a lowercase literal — avoids the * `toLowerCase()` allocation and matches CSS's ASCII case-insensitive keyword * matching. `lit` must be lowercase ASCII. * @param {string} s string to test * @param {string} lit lowercase ASCII literal to match * @returns {boolean} true, if `s` equals `lit` ignoring ASCII case */ const equalsLowerCase = (s, lit) => { if (s.length !== lit.length) return false; for (let i = 0; i < lit.length; i++) { let c = s.charCodeAt(i); if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20; if (c !== lit.charCodeAt(i)) return false; } return true; }; /** * Case-sensitive equality of a source range against a literal — no slice. * @param {string} input source * @param {number} start range start * @param {number} end range end (exclusive) * @param {string} lit literal to match * @returns {boolean} true when the range equals `lit` */ const rangeEquals = (input, start, end, lit) => end - start === lit.length && input.startsWith(lit, start); /** * ASCII case-insensitive equality of a source range against a lowercase ASCII literal — no slice. * @param {string} input source * @param {number} start range start * @param {number} end range end (exclusive) * @param {string} lit lowercase ASCII literal to match * @returns {boolean} true when the range equals `lit` ignoring ASCII case */ const rangeEqualsLowerCase = (input, start, end, lit) => { if (end - start !== lit.length) return false; for (let i = 0; i < lit.length; i++) { let c = input.charCodeAt(start + i); if (c >= CC_UPPER_A && c <= CC_UPPER_Z) c |= 0x20; if (c !== lit.charCodeAt(i)) return false; } return true; }; /** * `s.toLowerCase()` that returns `s` itself (no allocation) when it can't * change — no ASCII uppercase and no non-ASCII (whose Unicode case mapping is * left to the real `toLowerCase`). * @param {string} s string * @returns {string} lowercased string */ const toLowerCaseIfNeeded = (s) => { for (let i = 0; i < s.length; i++) { const c = s.charCodeAt(i); if ((c >= CC_UPPER_A && c <= CC_UPPER_Z) || c > 127) return s.toLowerCase(); } return s; }; /** * A custom property name (`<dashed-ident>`): a `--`-prefixed identifier other than bare `--`. * @param {string} identifier identifier * @returns {boolean} true when identifier is dashed, otherwise false */ const isDashedIdentifier = (identifier) => identifier.startsWith("--") && identifier.length >= 3; /** * Consume an escaped code point. * @param {string} input input * @param {number} pos position just past the `\` * @returns {number} position past the escape sequence */ const _consumeAnEscapedCodePoint = (input, pos) => { // Caller has verified the `\` and the next code point form a valid // escape. Hex digits: consume up to 6 hex digits, then one optional // whitespace. Non-hex: consume one code point. // `\` at EOF: nothing to consume; return pos so callers don't overrun. if (pos >= input.length) return pos; const cc = input.charCodeAt(pos); pos++; if (pos === input.length) return pos; if (_isHexDigit(cc)) { for (let i = 0; i < 5; i++) { if (!_isHexDigit(input.charCodeAt(pos))) break; pos++; } const trail = input.charCodeAt(pos); if (_isWhiteSpace(trail)) { pos++; pos = consumeExtraNewline(trail, input, pos); } } return pos; }; /** * Spec: "two code points are a valid escape" — first is `\`, second is * not a newline. * @param {string} input input * @param {number} pos position of the second code point * @param {number=} f first code point (defaults to `input.charCodeAt(pos - 1)`) * @param {number=} s second code point (defaults to `input.charCodeAt(pos)`) * @returns {boolean} true, if the two code points form a valid escape */ const _ifTwoCodePointsAreValidEscape = (input, pos, f, s) => { const first = f || input.charCodeAt(pos - 1); const second = s || input.charCodeAt(pos); if (first !== CC_REVERSE_SOLIDUS) return false; if (_isNewline(second)) return false; return true; }; /** * Spec: "three code points would start an ident sequence". * @param {string} input input * @param {number} pos position * @param {number=} f first code point (defaults to `input.charCodeAt(pos - 1)`) * @param {number=} s second code point (defaults to `input.charCodeAt(pos)`) * @param {number=} t third code point (defaults to `input.charCodeAt(pos + 1)`) * @returns {boolean} true, if the three code points start an ident sequence */ const _ifThreeCodePointsWouldStartAnIdentSequence = (input, pos, f, s, t) => { const first = f || input.charCodeAt(pos - 1); const second = s || input.charCodeAt(pos); const third = t || input.charCodeAt(pos + 1); if (first === CC_HYPHEN_MINUS) { return ( _isIdentStartCodePointCC(second) || second === CC_HYPHEN_MINUS || _ifTwoCodePointsAreValidEscape(input, pos, second, third) ); } if (_isIdentStartCodePointCC(first)) return true; if (first === CC_REVERSE_SOLIDUS) { return _ifTwoCodePointsAreValidEscape(input, pos, first, second); } return false; }; /** * Spec: "three code points would start a number". * @param {string} input input * @param {number} pos position * @param {number=} f first code point * @param {number=} s second code point * @param {number=} t third code point * @returns {boolean} true, if the three code points start a number */ const _ifThreeCodePointsWouldStartANumber = (input, pos, f, s, t) => { const first = f || input.charCodeAt(pos - 1); const second = s || input.charCodeAt(pos); const third = t || input.charCodeAt(pos + 1); if (first === CC_PLUS_SIGN || first === CC_HYPHEN_MINUS) { if (_isDigit(second)) return true; return second === CC_FULL_STOP && _isDigit(third); } if (first === CC_FULL_STOP) return _isDigit(second); /* istanbul ignore next -- @preserve: spec-general; every caller passes `pos` just past a +/-/. so `first` is never a bare digit here */ return _isDigit(first); }; /** * Consume an ident sequence (no validation of the first code points). * @param {string} input input * @param {number} pos position * @returns {number} position just past the last ident-sequence code point */ const _consumeAnIdentSequence = (input, pos) => { // Hot loop (every ident, at-keyword, hash, function name, unit). Both checks // are inlined from `_isIdentCodePoint` / `_ifTwoCodePointsAreValidEscape`: the // ident test is a single full-range table load (no `cc < 128` branch), and the // escape test reads the following code point only when `cc` is a `\` (rare) // instead of eagerly. for (;;) { const cc = input.charCodeAt(pos) | 0; pos++; if (_identCharTable[cc] === 1) { continue; } if (cc === CC_REVERSE_SOLIDUS && !_isNewline(input.charCodeAt(pos))) { pos = _consumeAnEscapedCodePoint(input, pos); continue; } return pos - 1; } }; /** * @param {number} cc char code * @returns {boolean} true, if cc is a non-printable code point */ const _isNonPrintableCodePoint = (cc) => (cc >= 0x00 && cc <= 0x08) || cc === 0x0b || (cc >= 0x0e && cc <= 0x1f) || cc === 0x7f; /** * Consume the body of a number per the spec (does not classify integer * vs number — caller / token type handles that). * @param {string} input input * @param {number} pos position at the first numeric / sign code point * @returns {number} position just past the number */ const _consumeANumber = (input, pos) => { let cc = input.charCodeAt(pos); if (cc === CC_HYPHEN_MINUS || cc === CC_PLUS_SIGN) { pos++; } while (_isDigit(input.charCodeAt(pos))) pos++; if ( input.charCodeAt(pos) === CC_FULL_STOP && _isDigit(input.charCodeAt(pos + 1)) ) { pos++; while (_isDigit(input.charCodeAt(pos))) pos++; } cc = input.charCodeAt(pos); if ( (cc === CC_LOWER_E || cc === CC_UPPER_E) && (((input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS || input.charCodeAt(pos + 1) === CC_PLUS_SIGN) && _isDigit(input.charCodeAt(pos + 2))) || _isDigit(input.charCodeAt(pos + 1))) ) { pos++; cc = input.charCodeAt(pos); if (cc === CC_PLUS_SIGN || cc === CC_HYPHEN_MINUS) { pos++; } while (_isDigit(input.charCodeAt(pos))) pos++; } return pos; }; /** * Spec recovery: when the tokenizer realises it's mid-bad-url, consume * until `)` or EOF. * @param {string} input input * @param {number} pos position * @returns {number} position past the recovery `)` or EOF */ const _consumeTheRemnantsOfABadUrl = (input, pos) => { for (;;) { if (pos === input.length) return pos; const cc = input.charCodeAt(pos); pos++; if (cc === CC_RIGHT_PARENTHESIS) return pos; if (_ifTwoCodePointsAreValidEscape(input, pos)) { pos = _consumeAnEscapedCodePoint(input, pos); } } }; /** * A mutable lexer token. The `next` / `consume` hot path reuses a single * instance per `TokenStream` (the lexer writes into it instead of allocating * one object per token), which also keeps the parser's `t.type` reads * monomorphic. All fields are present from construction so the shape never * transitions; type-specific fields (`isId` / `contentStart` / `contentEnd` / * `unitStart`) carry stale values for unrelated token types and are only read * by `tokenToNode` for the matching type. Pass a fresh one per `readToken` call * to collect the raw token list (e.g. tests). * @typedef {object} MutableToken * @property {number} type one of the `TT_*` constants * @property {number} start byte offset of the token's first code point * @property {number} end byte offset just past the token's last code point * @property {boolean} isId hash tokens: starts an ident sequence * @property {number} contentStart url tokens: first content code point * @property {number} contentEnd url tokens: just past the last content code point * @property {number} unitStart dimension tokens: first unit-ident code point */ /** * @returns {MutableToken} a fresh lexer token with the canonical shape */ const createToken = () => ({ type: TT_EOF, start: 0, end: 0, isId: false, contentStart: 0, contentEnd: 0, unitStart: 0 }); /** * Populate `out`'s common fields and return it — the lexer functions' return * statement (kept tiny so V8 can inline it). * @param {MutableToken} out token to populate * @param {number} type one of the `TT_*` constants * @param {number} start byte offset of the token's first code point * @param {number} end byte offset just past the token's last code point * @returns {MutableToken} `out` */ const fill = (out, type, start, end) => { out.type = type; out.start = start; out.end = end; return out; }; /** * Whitespace token. Caller advances past the leading code point so * `start = pos - 1`. * @param {string} input input * @param {number} pos position just past the first whitespace code point * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeSpace(input, pos, out) { const start = pos - 1; while (_wsTable[input.charCodeAt(pos)] === 1) pos++; return fill(out, TT_WHITESPACE, start, pos); } // Sticky fast-forward classes: a native run-skip over the ordinary characters of // a string / url token, so long values (data: URIs, base64) don't cost one JS // char read each. The negated classes match exactly the per-char terminators the // loops below handle (quotes / backslash / newlines for strings; plus `(`, `)`, // whitespace and non-printable code points for urls). const _STRING_SAFE = /[^"'\\\n\r\f]+/y; // eslint-disable-next-line no-control-regex -- url terminators include the control range and DEL (they make a bad-url) const _URL_SAFE = /[^\u0000-\u0020\u007F"'()\\]+/y; /** * Consume a string token. Caller advanced past the opening quote so * `pos - 1` holds the ending code point and `pos - 1` is the start. * @param {string} input input * @param {number} pos position just past the opening quote * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeAStringToken(input, pos, out) { const start = pos - 1; const endingCodePoint = input.charCodeAt(pos - 1); for (;;) { _STRING_SAFE.lastIndex = pos; if (_STRING_SAFE.test(input)) pos = _STRING_SAFE.lastIndex; if (pos === input.length) { return fill(out, TT_STRING, start, pos); } const cc = input.charCodeAt(pos); pos++; if (cc === endingCodePoint) { return fill(out, TT_STRING, start, pos); } if (_isNewline(cc)) { pos--; return fill(out, TT_BAD_STRING_TOKEN, start, pos); } if (cc === CC_REVERSE_SOLIDUS) { // `\` at EOF: string ends here; emit the token so ranges cover all input. if (pos === input.length) return fill(out, TT_STRING, start, pos); if (_isNewline(input.charCodeAt(pos))) { const ccNl = input.charCodeAt(pos); pos++; pos = consumeExtraNewline(ccNl, input, pos); } else if (_ifTwoCodePointsAreValidEscape(input, pos)) { pos = _consumeAnEscapedCodePoint(input, pos); } } } } /** * `#` — hash or delim. * @param {string} input input * @param {number} pos position just past `#` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeNumberSign(input, pos, out) { const start = pos - 1; const first = input.charCodeAt(pos); const second = input.charCodeAt(pos + 1); if ( _isIdentCodePoint(first) || _ifTwoCodePointsAreValidEscape(input, pos, first, second) ) { const third = input.charCodeAt(pos + 2); out.isId = _ifThreeCodePointsWouldStartAnIdentSequence( input, pos, first, second, third ); pos = _consumeAnIdentSequence(input, pos); return fill(out, TT_HASH, start, pos); } return fill(out, TT_DELIM, start, pos); } /** * `-` — number / cdc / ident / delim. * @param {string} input input * @param {number} pos position just past `-` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeHyphenMinus(input, pos, out) { // Read the two lookahead code points once; the lead is the known `-`. const second = input.charCodeAt(pos); const third = input.charCodeAt(pos + 1); if ( _ifThreeCodePointsWouldStartANumber( input, pos, CC_HYPHEN_MINUS, second, third ) ) { pos--; return consumeANumericToken(input, pos, out); } if (second === CC_HYPHEN_MINUS && third === CC_GREATER_THAN_SIGN) { return fill(out, TT_CDC, pos - 1, pos + 2); } if ( _ifThreeCodePointsWouldStartAnIdentSequence( input, pos, CC_HYPHEN_MINUS, second, third ) ) { pos--; return consumeAnIdentLikeToken(input, pos, out); } return fill(out, TT_DELIM, pos - 1, pos); } /** * `.` — number or delim. * @param {string} input input * @param {number} pos position just past `.` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeFullStop(input, pos, out) { const start = pos - 1; if (_ifThreeCodePointsWouldStartANumber(input, pos)) { pos--; return consumeANumericToken(input, pos, out); } return fill(out, TT_DELIM, start, pos); } /** * `+` — number or delim. * @param {string} input input * @param {number} pos position just past `+` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumePlusSign(input, pos, out) { const start = pos - 1; if (_ifThreeCodePointsWouldStartANumber(input, pos)) { pos--; return consumeANumericToken(input, pos, out); } return fill(out, TT_DELIM, start, pos); } /** * Numeric token: number / percentage / dimension. * @param {string} input input * @param {number} pos position at the first numeric/sign code point * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeANumericToken(input, pos, out) { const start = pos; pos = _consumeANumber(input, pos); const first = input.charCodeAt(pos); // A unit can only begin with `-`, `\`, or an ident-start code point — exactly // the cases where the §4 "would start an ident sequence" check can be true. For // a plain number (next char is whitespace / `;` / `,` / `)` / EOF, the common // case) skip the two lookahead reads and the call entirely. if ( (first === CC_HYPHEN_MINUS || first === CC_REVERSE_SOLIDUS || _isIdentStartCodePointCC(first)) && _ifThreeCodePointsWouldStartAnIdentSequence( input, pos, first, input.charCodeAt(pos + 1), input.charCodeAt(pos + 2) ) ) { out.unitStart = pos; pos = _consumeAnIdentSequence(input, pos); return fill(out, TT_DIMENSION, start, pos); } if (first === CC_PERCENTAGE) { return fill(out, TT_PERCENTAGE, start, pos + 1); } return fill(out, TT_NUMBER, start, pos); } /** * Consume an unquoted url token. Caller has already eaten `url(` and * any leading whitespace. * @param {string} input input * @param {number} pos position at the first content code point * @param {number} fnStart byte offset of the `u` in `url(` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeAUrlToken(input, pos, fnStart, out) { while (_isWhiteSpace(input.charCodeAt(pos))) pos++; const contentStart = pos; out.contentStart = contentStart; for (;;) { _URL_SAFE.lastIndex = pos; if (_URL_SAFE.test(input)) pos = _URL_SAFE.lastIndex; if (pos === input.length) { out.contentEnd = pos; return fill(out, TT_URL, fnStart, pos); } const cc = input.charCodeAt(pos); pos++; if (cc === CC_RIGHT_PARENTHESIS) { out.contentEnd = pos - 1; return fill(out, TT_URL, fnStart, pos); } if (_isWhiteSpace(cc)) { const end = pos - 1; while (_isWhiteSpace(input.charCodeAt(pos))) pos++; if (pos === input.length) { out.contentEnd = end; return fill(out, TT_URL, fnStart, pos); } if (input.charCodeAt(pos) === CC_RIGHT_PARENTHESIS) { pos++; out.contentEnd = end; return fill(out, TT_URL, fnStart, pos); } pos = _consumeTheRemnantsOfABadUrl(input, pos); return fill(out, TT_BAD_URL_TOKEN, fnStart, pos); } if ( cc === CC_QUOTATION_MARK || cc === CC_APOSTROPHE || cc === CC_LEFT_PARENTHESIS || _isNonPrintableCodePoint(cc) ) { pos = _consumeTheRemnantsOfABadUrl(input, pos); return fill(out, TT_BAD_URL_TOKEN, fnStart, pos); } if (cc === CC_REVERSE_SOLIDUS) { if (_ifTwoCodePointsAreValidEscape(input, pos)) { pos = _consumeAnEscapedCodePoint(input, pos); } else { pos = _consumeTheRemnantsOfABadUrl(input, pos); return fill(out, TT_BAD_URL_TOKEN, fnStart, pos); } } } } /** * Consume an ident-like token: ident / function / url / bad-url. * @param {string} input input * @param {number} pos position at the first ident-start code point * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeAnIdentLikeToken(input, pos, out) { const start = pos; pos = _consumeAnIdentSequence(input, pos); // `url` case-insensitively (ASCII lower via `| 0x20`) without a // `slice().toLowerCase()` allocation per identifier; an escaped ident can't // be exactly 3 raw chars, so the length gate keeps this equivalent. if ( pos - start === 3 && (input.charCodeAt(start) | 0x20) === CC_LOWER_U && (input.charCodeAt(start + 1) | 0x20) === CC_LOWER_R && (input.charCodeAt(start + 2) | 0x20) === CC_LOWER_L && input.charCodeAt(pos) === CC_LEFT_PARENTHESIS ) { pos++; const end = pos; while ( _isWhiteSpace(input.charCodeAt(pos)) && _isWhiteSpace(input.charCodeAt(pos + 1)) ) { pos++; } if ( input.charCodeAt(pos) === CC_QUOTATION_MARK || input.charCodeAt(pos) === CC_APOSTROPHE || (_isWhiteSpace(input.charCodeAt(pos)) && (input.charCodeAt(pos + 1) === CC_QUOTATION_MARK || input.charCodeAt(pos + 1) === CC_APOSTROPHE)) ) { // End at `end` (the `(`'s closer position), not `pos` — the // lookahead-eaten whitespace must be re-tokenized as a whitespace // token rather than swallowed silently. The reader resumes at // `token.end`, so returning `end` here does that. return fill(out, TT_FUNCTION, start, end); } return consumeAUrlToken(input, pos, start, out); } if (input.charCodeAt(pos) === CC_LEFT_PARENTHESIS) { pos++; return fill(out, TT_FUNCTION, start, pos); } return fill(out, TT_IDENTIFIER, start, pos); } /** * `<` — CDO or delim. * @param {string} input input * @param {number} pos position just past `<` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeLessThan(input, pos, out) { if ( input.charCodeAt(pos) === CC_EXCLAMATION && input.charCodeAt(pos + 1) === CC_HYPHEN_MINUS && input.charCodeAt(pos + 2) === CC_HYPHEN_MINUS ) { return fill(out, TT_CDO, pos - 1, pos + 3); } return fill(out, TT_DELIM, pos - 1, pos); } /** * `@` — at-keyword or delim. * @param {string} input input * @param {number} pos position just past `@` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeCommercialAt(input, pos, out) { const start = pos - 1; if ( _ifThreeCodePointsWouldStartAnIdentSequence( input, pos, input.charCodeAt(pos), input.charCodeAt(pos + 1), input.charCodeAt(pos + 2) ) ) { pos = _consumeAnIdentSequence(input, pos); return fill(out, TT_AT_KEYWORD, start, pos); } return fill(out, TT_DELIM, start, pos); } /** * `\` — escape starts an ident-like token, otherwise it's a delim. * @param {string} input input * @param {number} pos position just past `\` * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeReverseSolidus(input, pos, out) { if (_ifTwoCodePointsAreValidEscape(input, pos)) { pos--; return consumeAnIdentLikeToken(input, pos, out); } return fill(out, TT_DELIM, pos - 1, pos); } // `consumeAToken` dispatch: the §4 token rules keyed by the lead code point are // === Tokenizer lead-character dispatch (CSS Syntax Level 3 §4 "consume a token") === // // `consumeAToken` selects a sub-routine from the first ("lead") code point of each // token. The §4 rules are keyed on specific code points (`"` `#` `(` digit // ident-start …) that sit SPARSELY across the ASCII range, so a plain `switch (cc)` // compiles to a jump table spanning U+0009..U+007D in which the most common lead — // an ident-start letter — is not a case and reaches its handler only after the // digit/whitespace tests miss. `_charClass` precomputes, for every ASCII code // point, a dense handler id (`HC_*`, 0..12) so `consumeAToken` is one array load + // a compact 13-entry jump table and idents dispatch directly. Non-ASCII // (cc >= 128) is always ident-start per §4, so it skips the table. // // Extending for a spec change: repoint the code point in the build loop below; if // it needs a new sub-routine, add an `HC_*` id, a `case` in `consumeAToken`, and a // row here. This list is the authoritative "which lead code point dispatches // where" map (§4 "consume a token", step by lead code point): // // HC_WHITESPACE whitespace U+0009 TAB U+000A LF U+000C FF U+000D CR U+0020 SPACE // HC_STRING string start U+0022 " U+0027 ' // HC_SINGLE one-char token ( ) , : ; [ ] { } (its token type comes from `_singleTT`) // HC_NUMBER_SIGN hash / delim U+0023 # // HC_PLUS_SIGN number / delim U+002B + // HC_HYPHEN_MINUS number / CDC / ident / delim U+002D - // HC_FULL_STOP number / delim U+002E . // HC_LESS_THAN CDO / delim U+003C < // HC_AT_SIGN at-keyword / delim U+0040 @ // HC_REVERSE_SOLIDUS escape / delim U+005C \ // HC_DIGIT number U+0030..U+0039 0-9 // HC_IDENT ident-like U+0041..U+005A A-Z U+0061..U+007A a-z U+005F _ (plus cc >= 128) // HC_DELIM anything else -> a single <delim-token> // // `_singleTT[cc]` is the token type for the HC_SINGLE code points (a second table // so they share one handler instead of one `case` each). The default class 0 is // the delim handler (anything not matched below), so it needs no named constant. const HC_WHITESPACE = 1; const HC_STRING = 2; const HC_SINGLE = 3; const HC_NUMBER_SIGN = 4; const HC_PLUS_SIGN = 5; const HC_HYPHEN_MINUS = 6; const HC_FULL_STOP = 7; const HC_LESS_THAN = 8; const HC_AT_SIGN = 9; const HC_REVERSE_SOLIDUS = 10; const HC_DIGIT = 11; const HC_IDENT = 12; // Full `charCodeAt` range so `consumeAToken` dispatches with one table load and // no `cc < 128` branch. Every non-ASCII code point (>= 0x80) is an ident-start // lead per §4, so those rows are seeded to `HC_IDENT`; the ASCII rows below // overwrite 0..127 with their real class. const _charClass = new Uint8Array(0x10000).fill(HC_IDENT, 128); const _singleTT = new Uint8Array(128); _singleTT[CC_LEFT_PARENTHESIS] = TT_LEFT_PARENTHESIS; _singleTT[CC_RIGHT_PARENTHESIS] = TT_RIGHT_PARENTHESIS; _singleTT[CC_COMMA] = TT_COMMA; _singleTT[CC_COLON] = TT_COLON; _singleTT[CC_SEMICOLON] = TT_SEMICOLON; _singleTT[CC_LEFT_SQUARE] = TT_LEFT_SQUARE_BRACKET; _singleTT[CC_RIGHT_SQUARE] = TT_RIGHT_SQUARE_BRACKET; _singleTT[CC_LEFT_CURLY] = TT_LEFT_CURLY_BRACKET; _singleTT[CC_RIGHT_CURLY] = TT_RIGHT_CURLY_BRACKET; // Each ASCII code point belongs to exactly one class; HC_SINGLE is seeded from // `_singleTT` above, the rest follow §4's lead-code-point rules, and everything // unmatched stays the delim class (0). Keep this in sync with the table above. for (let i = 0; i < 128; i++) { if (_singleTT[i] !== 0) { _charClass[i] = HC_SINGLE; } else if (_isWhiteSpace(i)) { _charClass[i] = HC_WHITESPACE; } else if (i === CC_QUOTATION_MARK || i === CC_APOSTROPHE) { _charClass[i] = HC_STRING; } else if (i === CC_NUMBER_SIGN) { _charClass[i] = HC_NUMBER_SIGN; } else if (i === CC_PLUS_SIGN) { _charClass[i] = HC_PLUS_SIGN; } else if (i === CC_HYPHEN_MINUS) { _charClass[i] = HC_HYPHEN_MINUS; } else if (i === CC_FULL_STOP) { _charClass[i] = HC_FULL_STOP; } else if (i === CC_LESS_THAN_SIGN) { _charClass[i] = HC_LESS_THAN; } else if (i === CC_AT_SIGN) { _charClass[i] = HC_AT_SIGN; } else if (i === CC_REVERSE_SOLIDUS) { _charClass[i] = HC_REVERSE_SOLIDUS; } else if (_isDigit(i)) { _charClass[i] = HC_DIGIT; } else if (_isIdentStartCodePointCC(i)) { _charClass[i] = HC_IDENT; } // else stays the delim class (0) } /** * Per-character dispatcher. The outer loop has already advanced past * the lead code point (`pos - 1` is the lead). * @param {string} input input * @param {number} pos position just past the lead code point * @param {number} cc the lead code point (`input.charCodeAt(pos - 1)`, already read by the caller) * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the resulting token, or undefined at EOF */ function consumeAToken(input, pos, cc, out) { // `u` / `U` would start a unicode-range token in the spec; those are not // produced, so they map to HC_IDENT and fall through to ident-like. switch (_charClass[cc]) { // Run of whitespace → one <whitespace-token>. case HC_WHITESPACE: return consumeSpace(input, pos, out); // `"` / `'` → <string-token> (or <bad-string-token> on a raw newline). case HC_STRING: return consumeAStringToken(input, pos, out); // One-code-point token: its type is looked up in `_singleTT` (the `(` `)` // `,` `:` `;` `[` `]` `{` `}` set), so all of them share this arm. case HC_SINGLE: return fill(out, _singleTT[cc], pos - 1, pos); // `#` → <hash-token> if an ident/escape follows, else a <delim-token>. case HC_NUMBER_SIGN: return consumeNumberSign(input, pos, out); // `+` → <number-token> if it starts a number, else a <delim-token>. case HC_PLUS_SIGN: return consumePlusSign(input, pos, out); // `-` → number / <CDC-token> (`-->`) / ident / <delim-token>. case HC_HYPHEN_MINUS: return consumeHyphenMinus(input, pos, out); // `.` → <number-token> if a digit follows, else a <delim-token>. case HC_FULL_STOP: return consumeFullStop(input, pos, out); // `<` → <CDO-token> (`<!--`), else a <delim-token>. case HC_LESS_THAN: return consumeLessThan(input, pos, out); // `@` → <at-keyword-token> if an ident follows, else a <delim-token>. case HC_AT_SIGN: return consumeCommercialAt(input, pos, out); // `\` → ident-like token if it's a valid escape, else a <delim-token>. case HC_REVERSE_SOLIDUS: return consumeReverseSolidus(input, pos, out); // Digit → numeric token; `pos - 1` re-includes the digit the caller passed. case HC_DIGIT: return consumeANumericToken(input, pos - 1, out); // Ident-start (letter / `_` / non-ASCII, incl. `u`/`U`) → ident / function / // url token; `pos - 1` re-includes the lead code point. case HC_IDENT: return consumeAnIdentLikeToken(input, pos - 1, out); default: // HC_DELIM. EOF is impossible here (caller guarded with the outer // loop's `pos < input.length` check). Anything else: a <delim-token>. return fill(out, TT_DELIM, pos - 1, pos); } } /** * Read one raw token (comment / whitespace / value token) starting at byte * `pos`, writing it into the caller-supplied `out` and returning `out`. The * token's `end` is the next read position. Returns `undefined` at end-of-input — * `pos >= length`, an unterminated comment, or a string ending on a trailing * escape. This is the shared lexer core: `next` reuses one `out` across calls so * the parse hot path allocates no per-token object; loop over it with a fresh * `out` per call to collect the raw token list (e.g. tests). Comment tokens are * returned here; `next` filters them. * @param {string} input input * @param {number} pos byte offset to read from * @param {MutableToken} out token to populate * @returns {MutableToken | undefined} the token, or undefined at EOF */ function readToken(input, pos, out) { if (pos >= input.length) return undefined; const cc = input.charCodeAt(pos); // Comment: `/*…*/` is yielded as a token (filtered by `next`). if (cc === CC_SOLIDUS && input.charCodeAt(pos + 1) === CC_ASTERISK) { const start = pos; // Jump to the closing `*/` in one native scan instead of a per-character // loop — comment bodies (license banners, source comments) can be long. // No close: unterminated comment runs to EOF so ranges cover all input. const close = input.indexOf("*/", pos + 2); return fill( out, TT_COMMENT, start, close === -1 ? input.length : close + 2 ); } // `consumeAToken` dispatches on the lead code point at `pos` (it expects the // position just past the lead and the already-read lead code point). return consumeAToken(input, pos + 1, cc, out); } // AST shape mirrors tabatkins/parse-css (the CSS Syntax Level 3 reference), with two deviations: nodes carry a `range` byte offset pair + a lazy `loc` getter, and have no methods beyond it. /** * AST node / leaf-token `type` discriminators (spec name where it has one, else * parse-css's PascalCase). Numeric for the same reasons as the `TT_*` token * constants: a compact `Node#type` slot and integer `===` / `Map` keys on the * visitor hot path. Kept as a `NodeType` namespace (not bare constants) because * consumers reference members as `NodeType.AtRule`; exported so visitor maps * (`SourceProcessor#use`) and `CssParser` name nodes instead of a string * literal. A lexer token type never reaches a `Node#type`. * @enum {number} */ const NodeType = { Ident: 1, Function: 2, AtKeyword: 3, Hash: 4, String: 5, BadString: 6, Url: 7, BadUrl: 8, Delim: 9, Number: 10, Percentage: 11, Dimension: 12, Whitespace: 13, Colon: 14, Semicolon: 15, Comma: 16, // Preserved tokens for stray closers / CDO / CDC (kept as component values per §5.4.8 "consume a token and return it"). RightParenthesis: 17, RightSquareBracket: 18, RightCurlyBracket: 19, CDO: 20, CDC: 21, SimpleBlock: 22, Declaration: 23, AtRule: 24, QualifiedRule: 25, Stylesheet: 26, // Comments are never tree nodes; this type exists only so a `NodeType.Comment` // visitor can be registered (fired during tokenization — see `grammar`). Comment: 27 }; const { Ident: T_IDENT, Function: T_FUNCTION, AtKeyword: T_AT_KEYWORD, Hash: T_HASH, String: T_STRING, BadString: T_BAD_STRING, Url: T_URL, BadUrl: T_BAD_URL, Delim: T_DELIM, Number: T_NUMBER, Percentage: T_PERCENTAGE, Dimension: T_DIMENSION, Whitespace: T_WHITESPACE, Colon: T_COLON, Semicolon: T_SEMICOLON, Comma: T_COMMA, RightParenthesis: T_RIGHT_PARENTHESIS, RightSquareBracket: T_RIGHT_SQUARE_BRACKET, RightCurlyBracket: T_RIGHT_CURLY_BRACKET, CDO: T_CDO, CDC: T_CDC, SimpleBlock: T_SIMPLE_BLOCK, Declaration: T_DECLARATION, AtRule: T_AT_RULE, QualifiedRule: T_QUALIFIED_RULE, Stylesheet: T_STYLESHEET, Comment: T_COMMENT } = NodeType; /** * Base AST node — the property-accessor view the `parseA*` entry points return * (see `_makeReader`). Every concrete node carries the `[start, end)` byte * `range` of the source slice it covers; `loc` is computed on demand from the * shared `LocConverter`, so line/column conversion is only paid when a consumer * needs it. The concrete node typedefs below extend this via `&`. * * Inside the parser a node ref is an integer id into the columns; the reader * exposes this property shape over a retained snapshot of those columns. * @typedef {object} Node * @property {number} type node-type discriminator * @property {number} start byte offset of the node's first code point * @property {number} end byte offset just past the node's last code point * @property {[number, number]} range the `[start, end)` byte range * @property {{ start: { line: number, column: number }, end: { line: number, column: number } }} loc source location (1-based line, 0-based column) * @property {() => string} toString source slice for this node * @property {string} unescapedName name with CSS escapes resolved (name-bearing nodes only) */ /** * @param {string} s numeric text * @returns {"+" | "-" | ""} the spec sign ("" when unsigned) */ const _signOf = (s) => { const c = s.charCodeAt(0); return c === CC_PLUS_SIGN ? "+" : c === CC_HYPHEN_MINUS ? "-" : ""; }; /** * @param {string} s numeric text (no unit / `%`) * @returns {"integer" | "number"} the spec type flag */ const _typeFlagOf = (s) => s.includes(".") || s.includes("e") || s.includes("E") ? "number" : "integer"; /** * Leaf token node (property-accessor view) — `value` is the raw source slice * (identifier text, quoted string including quotes, a dimension's full `123px`, * …; hash / at-keyword drop their `#` / `@` prefix, url uses its content range). * The `NumberToken` / `HashToken` / `UrlToken` / `DimensionToken` typedefs below * narrow the value accessors. `numericValue` / `typeFlag` / `sign` / `unit` are * derived from the source on read and are only meaningful on the matching token * type; `contentStart` / `contentEnd` mark a url token's inner content range. * @typedef {Node & { value: string, unescaped: string, numericValue: number, typeFlag: "integer" | "number" | "id" | "unrestricted", sign: "+" | "-" | "", unit: string, contentStart: number, contentEnd: number }} Token */ /** * Number token (`123`, `-1.5`, `+2e3`). `value` is the raw source slice (the spec's "value"); `numericValue` / `typeFlag` / `sign` are lazy getters derived from it (see `Token`). * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "" }} NumberToken */ /** * Percentage token (`50%`). `value` is the raw slice including `%`; `numericValue` (without `%`) and `sign` are lazy getters. * @typedef {Token & { numericValue: number, sign: "+" | "-" | "" }} PercentageToken */ /** * Dimension token (`100px`, `1.5em`). `value` is the raw slice (number + unit); `numericValue` / `typeFlag` / `sign` (of the numeric part) and `unit` (lower-cased) are lazy getters. * @typedef {Token & { numericValue: number, typeFlag: "integer" | "number", sign: "+" | "-" | "", unit: string }} DimensionToken */ // Spec "Assert: …" preconditions are comments only (callers satisfy them); a future `strict` option could reinstate them as throws. /** * Hash token (`#foo`). `value` is the name without the leading `#`; `typeFlag` is the spec type flag ("id" when the name forms a valid `<id>` selector, "unrestricted" otherwise). * @typedef {Token & { typeFlag: "id" | "unrestricted" }} HashToken */ /** * Old-style unquoted URL token (`url(unquoted)`). `value` is the unquoted body; * `contentStart` / `contentEnd` mark the inner content range in the source. * @typedef {Token & { contentStart: number, contentEnd: number }} UrlToken */ /** * Function node: `name(component-values...)`. `name` is the raw source slice * before the `(` (callers