'use strict'; Object.defineProperty(exports, '__esModule', { value: true }); var Scanner = require('@emmetio/scanner'); exports.OperatorType = void 0; (function (OperatorType) { OperatorType["Sibling"] = "+"; OperatorType["Important"] = "!"; OperatorType["ArgumentDelimiter"] = ","; OperatorType["ValueDelimiter"] = "-"; OperatorType["PropertyDelimiter"] = ":"; })(exports.OperatorType || (exports.OperatorType = {})); var Chars; (function (Chars) { /** `#` character */ Chars[Chars["Hash"] = 35] = "Hash"; /** `$` character */ Chars[Chars["Dollar"] = 36] = "Dollar"; /** `-` character */ Chars[Chars["Dash"] = 45] = "Dash"; /** `.` character */ Chars[Chars["Dot"] = 46] = "Dot"; /** `:` character */ Chars[Chars["Colon"] = 58] = "Colon"; /** `,` character */ Chars[Chars["Comma"] = 44] = "Comma"; /** `!` character */ Chars[Chars["Excl"] = 33] = "Excl"; /** `@` character */ Chars[Chars["At"] = 64] = "At"; /** `%` character */ Chars[Chars["Percent"] = 37] = "Percent"; /** `_` character */ Chars[Chars["Underscore"] = 95] = "Underscore"; /** `(` character */ Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen"; /** `)` character */ Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose"; /** `{` character */ Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen"; /** `}` character */ Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose"; /** `+` character */ Chars[Chars["Sibling"] = 43] = "Sibling"; /** `'` character */ Chars[Chars["SingleQuote"] = 39] = "SingleQuote"; /** `"` character */ Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote"; /** `t` character */ Chars[Chars["Transparent"] = 116] = "Transparent"; /** `/` character */ Chars[Chars["Slash"] = 47] = "Slash"; })(Chars || (Chars = {})); function tokenize(abbr, isValue) { let brackets = 0; let token; const scanner = new Scanner(abbr); const tokens = []; while (!scanner.eof()) { token = getToken(scanner, brackets === 0 && !isValue); if (!token) { throw scanner.error('Unexpected character'); } if (token.type === 'Bracket') { if (!brackets && token.open) { mergeTokens(scanner, tokens); } brackets += token.open ? 1 : -1; if (brackets < 0) { throw scanner.error('Unexpected bracket', token.start); } } tokens.push(token); // Forcibly consume next operator after unit-less numeric value or color: // next dash `-` must be used as value delimiter if (shouldConsumeDashAfter(token) && (token = operator(scanner))) { tokens.push(token); } } return tokens; } /** * Returns next token from given scanner, if possible */ function getToken(scanner, short) { return field(scanner) || customProperty(scanner) || numberValue(scanner) || colorValue(scanner) || stringValue(scanner) || bracket(scanner) || operator(scanner) || whiteSpace(scanner) || literal(scanner, short); } function field(scanner) { const start = scanner.pos; if (scanner.eat(Chars.Dollar) && scanner.eat(Chars.CurlyBracketOpen)) { scanner.start = scanner.pos; let index; let name = ''; if (scanner.eatWhile(Scanner.isNumber)) { // It’s a field index = Number(scanner.current()); name = scanner.eat(Chars.Colon) ? consumePlaceholder(scanner) : ''; } else if (Scanner.isAlpha(scanner.peek())) { // It’s a variable name = consumePlaceholder(scanner); } if (scanner.eat(Chars.CurlyBracketClose)) { return { type: 'Field', index, name, start, end: scanner.pos }; } throw scanner.error('Expecting }'); } // If we reached here then there’s no valid field here, revert // back to starting position scanner.pos = start; } /** * Consumes a placeholder: value right after `:` in field. Could be empty */ function consumePlaceholder(stream) { const stack = []; stream.start = stream.pos; while (!stream.eof()) { if (stream.eat(Chars.CurlyBracketOpen)) { stack.push(stream.pos); } else if (stream.eat(Chars.CurlyBracketClose)) { if (!stack.length) { stream.pos--; break; } stack.pop(); } else { stream.pos++; } } if (stack.length) { stream.pos = stack.pop(); throw stream.error(`Expecting }`); } return stream.current(); } /** * Consumes literal from given scanner * @param short Use short notation for consuming value. * The difference between “short” and “full” notation is that first one uses * alpha characters only and used for extracting keywords from abbreviation, * while “full” notation also supports numbers and dashes */ function literal(scanner, short) { const start = scanner.pos; if (scanner.eat(isIdentPrefix)) { // SCSS or LESS variable // NB a bit dirty hack: if abbreviation starts with identifier prefix, // consume alpha characters only to allow embedded variables scanner.eatWhile(start ? isKeyword : isLiteral$1); } else if (scanner.eat(Scanner.isAlphaWord)) { scanner.eatWhile(short ? isLiteral$1 : isKeyword); } else { // Allow dots only at the beginning of literal scanner.eat(Chars.Dot); scanner.eatWhile(isLiteral$1); } if (start !== scanner.pos) { scanner.start = start; return createLiteral(scanner, scanner.start = start); } } function createLiteral(scanner, start = scanner.start, end = scanner.pos) { return { type: 'Literal', value: scanner.substring(start, end), start, end }; } /** * Consumes numeric CSS value (number with optional unit) from current stream, * if possible */ function numberValue(scanner) { const start = scanner.pos; if (consumeNumber(scanner)) { scanner.start = start; const rawValue = scanner.current(); // eat unit, which can be a % or alpha word scanner.start = scanner.pos; scanner.eat(Chars.Percent) || scanner.eatWhile(Scanner.isAlphaWord); return { type: 'NumberValue', value: Number(rawValue), rawValue, unit: scanner.current(), start, end: scanner.pos }; } } /** * Consumes quoted string value from given scanner */ function stringValue(scanner) { const ch = scanner.peek(); const start = scanner.pos; let finished = false; if (Scanner.isQuote(ch)) { scanner.pos++; while (!scanner.eof()) { // Do not throw error on malformed string if (scanner.eat(ch)) { finished = true; break; } else { scanner.pos++; } } scanner.start = start; return { type: 'StringValue', value: scanner.substring(start + 1, scanner.pos - (finished ? 1 : 0)), quote: ch === Chars.SingleQuote ? 'single' : 'double', start, end: scanner.pos }; } } /** * Consumes a color token from given string */ function colorValue(scanner) { // supported color variations: // #abc → #aabbccc // #0 → #000000 // #fff.5 → rgba(255, 255, 255, 0.5) // #t → transparent const start = scanner.pos; if (scanner.eat(Chars.Hash)) { const valueStart = scanner.pos; let color = ''; let alpha = ''; if (scanner.eatWhile(isHex)) { color = scanner.substring(valueStart, scanner.pos); alpha = colorAlpha(scanner); } else if (scanner.eat(Chars.Transparent)) { color = '0'; alpha = colorAlpha(scanner) || '0'; } else { alpha = colorAlpha(scanner); } if (color || alpha || scanner.eof()) { const { r, g, b, a } = parseColor(color, alpha); return { type: 'ColorValue', r, g, b, a, raw: scanner.substring(start + 1, scanner.pos), start, end: scanner.pos }; } else { // Consumed # but no actual value: invalid color value, treat it as literal return createLiteral(scanner, start); } } scanner.pos = start; } /** * Consumes alpha value of color: `.1` */ function colorAlpha(scanner) { const start = scanner.pos; if (scanner.eat(Chars.Dot)) { scanner.start = start; if (scanner.eatWhile(Scanner.isNumber)) { return scanner.current(); } return '1'; } return ''; } /** * Consumes white space characters as string literal from given scanner */ function whiteSpace(scanner) { const start = scanner.pos; if (scanner.eatWhile(Scanner.isSpace)) { return { type: 'WhiteSpace', start, end: scanner.pos }; } } /** * Consumes custom CSS property: --foo-bar */ function customProperty(scanner) { const start = scanner.pos; if (scanner.eat(Chars.Dash) && scanner.eat(Chars.Dash)) { scanner.start = start; scanner.eatWhile(isKeyword); return { type: 'CustomProperty', value: scanner.current(), start, end: scanner.pos }; } scanner.pos = start; } /** * Consumes bracket from given scanner */ function bracket(scanner) { const ch = scanner.peek(); if (isBracket$1(ch)) { return { type: 'Bracket', open: ch === Chars.RoundBracketOpen, start: scanner.pos++, end: scanner.pos }; } } /** * Consumes operator from given scanner */ function operator(scanner) { const op = operatorType(scanner.peek()); if (op) { return { type: 'Operator', operator: op, start: scanner.pos++, end: scanner.pos }; } } /** * Eats number value from given stream * @return Returns `true` if number was consumed */ function consumeNumber(stream) { const start = stream.pos; stream.eat(Chars.Dash); const afterNegative = stream.pos; const hasDecimal = stream.eatWhile(Scanner.isNumber); const prevPos = stream.pos; if (stream.eat(Chars.Dot)) { // It’s perfectly valid to have numbers like `1.`, which enforces // value to float unit type const hasFloat = stream.eatWhile(Scanner.isNumber); if (!hasDecimal && !hasFloat) { // Lone dot stream.pos = prevPos; } } // Edge case: consumed dash only: not a number, bail-out if (stream.pos === afterNegative) { stream.pos = start; } return stream.pos !== start; } function isIdentPrefix(code) { return code === Chars.At || code === Chars.Dollar; } /** * If given character is an operator, returns it’s type */ function operatorType(ch) { return (ch === Chars.Sibling && exports.OperatorType.Sibling) || (ch === Chars.Excl && exports.OperatorType.Important) || (ch === Chars.Comma && exports.OperatorType.ArgumentDelimiter) || (ch === Chars.Colon && exports.OperatorType.PropertyDelimiter) || (ch === Chars.Dash && exports.OperatorType.ValueDelimiter) || void 0; } /** * Check if given code is a hex value (/0-9a-f/) */ function isHex(code) { return Scanner.isNumber(code) || Scanner.isAlpha(code, 65, 70); // A-F } function isKeyword(code) { return Scanner.isAlphaNumericWord(code) || code === Chars.Dash; } function isBracket$1(code) { return code === Chars.RoundBracketOpen || code === Chars.RoundBracketClose; } function isLiteral$1(code) { return Scanner.isAlphaWord(code) || code === Chars.Percent || code === Chars.Slash; } /** * Parses given color value from abbreviation into RGBA format */ function parseColor(value, alpha) { let r = '0'; let g = '0'; let b = '0'; let a = Number(alpha != null && alpha !== '' ? alpha : 1); if (value === 't') { a = 0; } else { switch (value.length) { case 0: break; case 1: r = g = b = value + value; break; case 2: r = g = b = value; break; case 3: r = value[0] + value[0]; g = value[1] + value[1]; b = value[2] + value[2]; break; default: value += value; r = value.slice(0, 2); g = value.slice(2, 4); b = value.slice(4, 6); } } return { r: parseInt(r, 16), g: parseInt(g, 16), b: parseInt(b, 16), a }; } /** * Check if scanner reader must consume dash after given token. * Used in cases where user must explicitly separate numeric values */ function shouldConsumeDashAfter(token) { return token.type === 'ColorValue' || (token.type === 'NumberValue' && !token.unit); } /** * Merges last adjacent tokens into a single literal. * This function is used to overcome edge case when function name was parsed * as a list of separate tokens. For example, a `scale3d()` value will be * parsed as literal and number tokens (`scale` and `3d`) which is a perfectly * valid abbreviation but undesired result. This function will detect last adjacent * literal and number values and combine them into single literal */ function mergeTokens(scanner, tokens) { let start = 0; let end = 0; while (tokens.length) { const token = last(tokens); if (token.type === 'Literal' || token.type === 'NumberValue') { start = token.start; if (!end) { end = token.end; } tokens.pop(); } else { break; } } if (start !== end) { tokens.push(createLiteral(scanner, start, end)); } } function last(arr) { return arr[arr.length - 1]; } function tokenScanner(tokens) { return { tokens, start: 0, pos: 0, size: tokens.length }; } function peek(scanner) { return scanner.tokens[scanner.pos]; } function readable(scanner) { return scanner.pos < scanner.size; } function consume(scanner, test) { if (test(peek(scanner))) { scanner.pos++; return true; } return false; } function error(scanner, message, token = peek(scanner)) { if (token && token.start != null) { message += ` at ${token.start}`; } const err = new Error(message); err['pos'] = token && token.start; return err; } function parser(tokens, options = {}) { const scanner = tokenScanner(tokens); const result = []; let property; while (readable(scanner)) { if (property = consumeProperty(scanner, options)) { result.push(property); } else if (!consume(scanner, isSiblingOperator)) { throw error(scanner, 'Unexpected token'); } } return result; } /** * Consumes single CSS property */ function consumeProperty(scanner, options) { let name; let important = false; let valueFragment; const value = []; const token = peek(scanner); const valueMode = !!options.value; if (!valueMode && isLiteral(token) && !isFunctionStart(scanner)) { scanner.pos++; name = token.value; // Consume any following value delimiter after property name consume(scanner, isValueDelimiter); } // Skip whitespace right after property name, if any if (valueMode) { consume(scanner, isWhiteSpace); } while (readable(scanner)) { if (consume(scanner, isImportant)) { important = true; } else if (valueFragment = consumeValue(scanner, valueMode)) { value.push(valueFragment); } else if (!consume(scanner, isFragmentDelimiter)) { break; } } if (name || value.length || important) { return { name, value, important }; } } /** * Consumes single value fragment, e.g. all value tokens before comma */ function consumeValue(scanner, inArgument) { const result = []; let token; let args; while (readable(scanner)) { token = peek(scanner); if (isValue(token)) { scanner.pos++; if (isLiteral(token) && (args = consumeArguments(scanner))) { result.push({ type: 'FunctionCall', name: token.value, arguments: args }); } else { result.push(token); } } else if (isValueDelimiter(token) || (inArgument && isWhiteSpace(token))) { scanner.pos++; } else { break; } } return result.length ? { type: 'CSSValue', value: result } : void 0; } function consumeArguments(scanner) { const start = scanner.pos; if (consume(scanner, isOpenBracket)) { const args = []; let value; while (readable(scanner) && !consume(scanner, isCloseBracket)) { if (value = consumeValue(scanner, true)) { args.push(value); } else if (!consume(scanner, isWhiteSpace) && !consume(scanner, isArgumentDelimiter)) { throw error(scanner, 'Unexpected token'); } } scanner.start = start; return args; } } function isLiteral(token) { return token && token.type === 'Literal'; } function isBracket(token, open) { return token && token.type === 'Bracket' && (open == null || token.open === open); } function isOpenBracket(token) { return isBracket(token, true); } function isCloseBracket(token) { return isBracket(token, false); } function isWhiteSpace(token) { return token && token.type === 'WhiteSpace'; } function isOperator(token, operator) { return token && token.type === 'Operator' && (!operator || token.operator === operator); } function isSiblingOperator(token) { return isOperator(token, exports.OperatorType.Sibling); } function isArgumentDelimiter(token) { return isOperator(token, exports.OperatorType.ArgumentDelimiter); } function isFragmentDelimiter(token) { return isArgumentDelimiter(token); } function isImportant(token) { return isOperator(token, exports.OperatorType.Important); } function isValue(token) { return token.type === 'StringValue' || token.type === 'ColorValue' || token.type === 'NumberValue' || token.type === 'Literal' || token.type === 'Field' || token.type === 'CustomProperty'; } function isValueDelimiter(token) { return isOperator(token, exports.OperatorType.PropertyDelimiter) || isOperator(token, exports.OperatorType.ValueDelimiter); } function isFunctionStart(scanner) { const t1 = scanner.tokens[scanner.pos]; const t2 = scanner.tokens[scanner.pos + 1]; return t1 && t2 && isLiteral(t1) && t2.type === 'Bracket'; } /** * Parses given abbreviation into property set */ function parse(abbr, options) { try { const tokens = typeof abbr === 'string' ? tokenize(abbr, options && options.value) : abbr; return parser(tokens, options); } catch (err) { if (err instanceof Scanner.ScannerError && typeof abbr === 'string') { err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`; } throw err; } } exports.default = parse; exports.getToken = getToken; exports.parser = parser; exports.tokenize = tokenize; //# sourceMappingURL=index.cjs.map