astro-ghostcms/.pnpm-store/v3/files/af/bcfdf39dadbae76fd981d07b9d1...

5357 lines
170 KiB
Plaintext
Raw Normal View History

2024-02-14 14:10:47 +00:00
/**
* Check if given code is a number
*/
function isNumber$1(code) {
return code > 47 && code < 58;
}
/**
* Check if given character code is alpha code (letter through A to Z)
*/
function isAlpha$1(code, from, to) {
from = from || 65; // A
to = to || 90; // Z
code &= ~32; // quick hack to convert any char code to uppercase char code
return code >= from && code <= to;
}
function isAlphaNumericWord(code) {
return isNumber$1(code) || isAlphaWord(code);
}
function isAlphaWord(code) {
return code === 95 /* _ */ || isAlpha$1(code);
}
/**
* Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü
*/
function isUmlaut(code) {
return code === 196
|| code == 214
|| code === 220
|| code === 228
|| code === 246
|| code === 252;
}
/**
* Check if given character code is a white-space character: a space character
* or line breaks
*/
function isWhiteSpace$3(code) {
return code === 32 /* space */
|| code === 9 /* tab */
|| code === 160; /* non-breaking space */
}
/**
* Check if given character code is a space character
*/
function isSpace(code) {
return isWhiteSpace$3(code)
|| code === 10 /* LF */
|| code === 13; /* CR */
}
/**
* Check if given character code is a quote character
*/
function isQuote$2(code) {
return code === 39 /* ' */ || code === 34 /* " */;
}
/**
* A streaming, character code-based string reader
*/
class Scanner {
constructor(str, start, end) {
if (end == null && typeof str === 'string') {
end = str.length;
}
this.string = str;
this.pos = this.start = start || 0;
this.end = end || 0;
}
/**
* Returns true only if the stream is at the end of the file.
*/
eof() {
return this.pos >= this.end;
}
/**
* Creates a new stream instance which is limited to given `start` and `end`
* range. E.g. its `eof()` method will look at `end` property, not actual
* stream end
*/
limit(start, end) {
return new Scanner(this.string, start, end);
}
/**
* Returns the next character code in the stream without advancing it.
* Will return NaN at the end of the file.
*/
peek() {
return this.string.charCodeAt(this.pos);
}
/**
* Returns the next character in the stream and advances it.
* Also returns <code>undefined</code> when no more characters are available.
*/
next() {
if (this.pos < this.string.length) {
return this.string.charCodeAt(this.pos++);
}
}
/**
* `match` can be a character code or a function that takes a character code
* and returns a boolean. If the next character in the stream 'matches'
* the given argument, it is consumed and returned.
* Otherwise, `false` is returned.
*/
eat(match) {
const ch = this.peek();
const ok = typeof match === 'function' ? match(ch) : ch === match;
if (ok) {
this.next();
}
return ok;
}
/**
* Repeatedly calls <code>eat</code> with the given argument, until it
* fails. Returns <code>true</code> if any characters were eaten.
*/
eatWhile(match) {
const start = this.pos;
while (!this.eof() && this.eat(match)) { /* */ }
return this.pos !== start;
}
/**
* Backs up the stream n characters. Backing it up further than the
* start of the current token will cause things to break, so be careful.
*/
backUp(n) {
this.pos -= (n || 1);
}
/**
* Get the string between the start of the current token and the
* current stream position.
*/
current() {
return this.substring(this.start, this.pos);
}
/**
* Returns substring for given range
*/
substring(start, end) {
return this.string.slice(start, end);
}
/**
* Creates error object with current stream state
*/
error(message, pos = this.pos) {
return new ScannerError(`${message} at ${pos + 1}`, pos, this.string);
}
}
class ScannerError extends Error {
constructor(message, pos, str) {
super(message);
this.pos = pos;
this.string = str;
}
}
function tokenScanner$1(tokens) {
return {
tokens,
start: 0,
pos: 0,
size: tokens.length
};
}
function peek$3(scanner) {
return scanner.tokens[scanner.pos];
}
function next(scanner) {
return scanner.tokens[scanner.pos++];
}
function slice(scanner, from = scanner.start, to = scanner.pos) {
return scanner.tokens.slice(from, to);
}
function readable$1(scanner) {
return scanner.pos < scanner.size;
}
function consume$2(scanner, test) {
const token = peek$3(scanner);
if (token && test(token)) {
scanner.pos++;
return true;
}
return false;
}
function error$1(scanner, message, token = peek$3(scanner)) {
if (token && token.start != null) {
message += ` at ${token.start}`;
}
const err = new Error(message);
err['pos'] = token && token.start;
return err;
}
function abbreviation(abbr, options = {}) {
const scanner = tokenScanner$1(abbr);
const result = statements(scanner, options);
if (readable$1(scanner)) {
throw error$1(scanner, 'Unexpected character');
}
return result;
}
function statements(scanner, options) {
const result = {
type: 'TokenGroup',
elements: []
};
let ctx = result;
let node;
const stack = [];
while (readable$1(scanner)) {
if (node = element$2(scanner, options) || group(scanner, options)) {
ctx.elements.push(node);
if (consume$2(scanner, isChildOperator)) {
stack.push(ctx);
ctx = node;
}
else if (consume$2(scanner, isSiblingOperator$1)) {
continue;
}
else if (consume$2(scanner, isClimbOperator)) {
do {
if (stack.length) {
ctx = stack.pop();
}
} while (consume$2(scanner, isClimbOperator));
}
}
else {
break;
}
}
return result;
}
/**
* Consumes group from given scanner
*/
function group(scanner, options) {
if (consume$2(scanner, isGroupStart)) {
const result = statements(scanner, options);
const token = next(scanner);
if (isBracket$2(token, 'group', false)) {
result.repeat = repeater$1(scanner);
}
return result;
}
}
/**
* Consumes single element from given scanner
*/
function element$2(scanner, options) {
let attr;
const elem = {
type: 'TokenElement',
name: void 0,
attributes: void 0,
value: void 0,
repeat: void 0,
selfClose: false,
elements: []
};
if (elementName(scanner, options)) {
elem.name = slice(scanner);
}
while (readable$1(scanner)) {
scanner.start = scanner.pos;
if (!elem.repeat && !isEmpty(elem) && consume$2(scanner, isRepeater)) {
elem.repeat = scanner.tokens[scanner.pos - 1];
}
else if (!elem.value && text(scanner)) {
elem.value = getText(scanner);
}
else if (attr = shortAttribute(scanner, 'id', options) || shortAttribute(scanner, 'class', options) || attributeSet(scanner)) {
if (!elem.attributes) {
elem.attributes = Array.isArray(attr) ? attr.slice() : [attr];
}
else {
elem.attributes = elem.attributes.concat(attr);
}
}
else {
if (!isEmpty(elem) && consume$2(scanner, isCloseOperator)) {
elem.selfClose = true;
if (!elem.repeat && consume$2(scanner, isRepeater)) {
elem.repeat = scanner.tokens[scanner.pos - 1];
}
}
break;
}
}
return !isEmpty(elem) ? elem : void 0;
}
/**
* Consumes attribute set from given scanner
*/
function attributeSet(scanner) {
if (consume$2(scanner, isAttributeSetStart)) {
const attributes = [];
let attr;
while (readable$1(scanner)) {
if (attr = attribute(scanner)) {
attributes.push(attr);
}
else if (consume$2(scanner, isAttributeSetEnd)) {
break;
}
else if (!consume$2(scanner, isWhiteSpace$2)) {
throw error$1(scanner, `Unexpected "${peek$3(scanner).type}" token`);
}
}
return attributes;
}
}
/**
* Consumes attribute shorthand (class or id) from given scanner
*/
function shortAttribute(scanner, type, options) {
if (isOperator$1(peek$3(scanner), type)) {
scanner.pos++;
// Consume multiple operators
let count = 1;
while (isOperator$1(peek$3(scanner), type)) {
scanner.pos++;
count++;
}
const attr = {
name: [createLiteral$1(type)]
};
if (count > 1) {
attr.multiple = true;
}
// Consume expression after shorthand start for React-like components
if (options.jsx && text(scanner)) {
attr.value = getText(scanner);
attr.expression = true;
}
else {
attr.value = literal$1$1(scanner) ? slice(scanner) : void 0;
}
return attr;
}
}
/**
* Consumes single attribute from given scanner
*/
function attribute(scanner) {
if (quoted(scanner)) {
// Consumed quoted value: its a value for default attribute
return {
value: slice(scanner)
};
}
if (literal$1$1(scanner, true)) {
const name = slice(scanner);
let value;
if (consume$2(scanner, isEquals)) {
if (quoted(scanner) || literal$1$1(scanner, true)) {
value = slice(scanner);
}
}
return { name, value };
}
}
function repeater$1(scanner) {
return isRepeater(peek$3(scanner))
? scanner.tokens[scanner.pos++]
: void 0;
}
/**
* Consumes quoted value from given scanner, if possible
*/
function quoted(scanner) {
const start = scanner.pos;
const quote = peek$3(scanner);
if (isQuote$1(quote)) {
scanner.pos++;
while (readable$1(scanner)) {
if (isQuote$1(next(scanner), quote.single)) {
scanner.start = start;
return true;
}
}
throw error$1(scanner, 'Unclosed quote', quote);
}
return false;
}
/**
* Consumes literal (unquoted value) from given scanner
*/
function literal$1$1(scanner, allowBrackets) {
const start = scanner.pos;
const brackets = {
attribute: 0,
expression: 0,
group: 0
};
while (readable$1(scanner)) {
const token = peek$3(scanner);
if (brackets.expression) {
// If were inside expression, we should consume all content in it
if (isBracket$2(token, 'expression')) {
brackets[token.context] += token.open ? 1 : -1;
}
}
else if (isQuote$1(token) || isOperator$1(token) || isWhiteSpace$2(token) || isRepeater(token)) {
break;
}
else if (isBracket$2(token)) {
if (!allowBrackets) {
break;
}
if (token.open) {
brackets[token.context]++;
}
else if (!brackets[token.context]) {
// Stop if found unmatched closing brace: it must be handled
// by parent consumer
break;
}
else {
brackets[token.context]--;
}
}
scanner.pos++;
}
if (start !== scanner.pos) {
scanner.start = start;
return true;
}
return false;
}
/**
* Consumes element name from given scanner
*/
function elementName(scanner, options) {
const start = scanner.pos;
if (options.jsx && consume$2(scanner, isCapitalizedLiteral)) {
// Check for edge case: consume immediate capitalized class names
// for React-like components, e.g. `Foo.Bar.Baz`
while (readable$1(scanner)) {
const { pos } = scanner;
if (!consume$2(scanner, isClassNameOperator) || !consume$2(scanner, isCapitalizedLiteral)) {
scanner.pos = pos;
break;
}
}
}
while (readable$1(scanner) && consume$2(scanner, isElementName$1)) {
// empty
}
if (scanner.pos !== start) {
scanner.start = start;
return true;
}
return false;
}
/**
* Consumes text value from given scanner
*/
function text(scanner) {
const start = scanner.pos;
if (consume$2(scanner, isTextStart)) {
let brackets = 0;
while (readable$1(scanner)) {
const token = next(scanner);
if (isBracket$2(token, 'expression')) {
if (token.open) {
brackets++;
}
else if (!brackets) {
break;
}
else {
brackets--;
}
}
}
scanner.start = start;
return true;
}
return false;
}
function getText(scanner) {
let from = scanner.start;
let to = scanner.pos;
if (isBracket$2(scanner.tokens[from], 'expression', true)) {
from++;
}
if (isBracket$2(scanner.tokens[to - 1], 'expression', false)) {
to--;
}
return slice(scanner, from, to);
}
function isBracket$2(token, context, isOpen) {
return Boolean(token && token.type === 'Bracket'
&& (!context || token.context === context)
&& (isOpen == null || token.open === isOpen));
}
function isOperator$1(token, type) {
return Boolean(token && token.type === 'Operator' && (!type || token.operator === type));
}
function isQuote$1(token, isSingle) {
return Boolean(token && token.type === 'Quote' && (isSingle == null || token.single === isSingle));
}
function isWhiteSpace$2(token) {
return Boolean(token && token.type === 'WhiteSpace');
}
function isEquals(token) {
return isOperator$1(token, 'equal');
}
function isRepeater(token) {
return Boolean(token && token.type === 'Repeater');
}
function isLiteral$2(token) {
return token.type === 'Literal';
}
function isCapitalizedLiteral(token) {
if (isLiteral$2(token)) {
const ch = token.value.charCodeAt(0);
return ch >= 65 && ch <= 90;
}
return false;
}
function isElementName$1(token) {
return token.type === 'Literal' || token.type === 'RepeaterNumber' || token.type === 'RepeaterPlaceholder';
}
function isClassNameOperator(token) {
return isOperator$1(token, 'class');
}
function isAttributeSetStart(token) {
return isBracket$2(token, 'attribute', true);
}
function isAttributeSetEnd(token) {
return isBracket$2(token, 'attribute', false);
}
function isTextStart(token) {
return isBracket$2(token, 'expression', true);
}
function isGroupStart(token) {
return isBracket$2(token, 'group', true);
}
function createLiteral$1(value) {
return { type: 'Literal', value };
}
function isEmpty(elem) {
return !elem.name && !elem.value && !elem.attributes;
}
function isChildOperator(token) {
return isOperator$1(token, 'child');
}
function isSiblingOperator$1(token) {
return isOperator$1(token, 'sibling');
}
function isClimbOperator(token) {
return isOperator$1(token, 'climb');
}
function isCloseOperator(token) {
return isOperator$1(token, 'close');
}
var Chars$3;
(function (Chars) {
/** `{` character */
Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
/** `}` character */
Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
/** `\\` character */
Chars[Chars["Escape"] = 92] = "Escape";
/** `=` character */
Chars[Chars["Equals"] = 61] = "Equals";
/** `[` character */
Chars[Chars["SquareBracketOpen"] = 91] = "SquareBracketOpen";
/** `]` character */
Chars[Chars["SquareBracketClose"] = 93] = "SquareBracketClose";
/** `*` character */
Chars[Chars["Asterisk"] = 42] = "Asterisk";
/** `#` 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["Slash"] = 47] = "Slash";
/** `:` character */
Chars[Chars["Colon"] = 58] = "Colon";
/** `!` character */
Chars[Chars["Excl"] = 33] = "Excl";
/** `@` character */
Chars[Chars["At"] = 64] = "At";
/** `_` character */
Chars[Chars["Underscore"] = 95] = "Underscore";
/** `(` character */
Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
/** `)` character */
Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
/** `+` character */
Chars[Chars["Sibling"] = 43] = "Sibling";
/** `>` character */
Chars[Chars["Child"] = 62] = "Child";
/** `^` character */
Chars[Chars["Climb"] = 94] = "Climb";
/** `'` character */
Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
/** `""` character */
Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
})(Chars$3 || (Chars$3 = {}));
/**
* If consumes escape character, sets current stream range to escaped value
*/
function escaped(scanner) {
if (scanner.eat(Chars$3.Escape)) {
scanner.start = scanner.pos;
if (!scanner.eof()) {
scanner.pos++;
}
return true;
}
return false;
}
function tokenize$1(source) {
const scanner = new Scanner(source);
const result = [];
const ctx = {
group: 0,
attribute: 0,
expression: 0,
quote: 0
};
let ch = 0;
let token;
while (!scanner.eof()) {
ch = scanner.peek();
token = getToken$1(scanner, ctx);
if (token) {
result.push(token);
if (token.type === 'Quote') {
ctx.quote = ch === ctx.quote ? 0 : ch;
}
else if (token.type === 'Bracket') {
ctx[token.context] += token.open ? 1 : -1;
}
}
else {
throw scanner.error('Unexpected character');
}
}
return result;
}
/**
* Returns next token from given scanner, if possible
*/
function getToken$1(scanner, ctx) {
return field$2(scanner, ctx)
|| repeaterPlaceholder(scanner)
|| repeaterNumber(scanner)
|| repeater(scanner)
|| whiteSpace$1(scanner)
|| literal$2(scanner, ctx)
|| operator$1(scanner)
|| quote(scanner)
|| bracket$1(scanner);
}
/**
* Consumes literal from given scanner
*/
function literal$2(scanner, ctx) {
const start = scanner.pos;
const expressionStart = ctx.expression;
let value = '';
while (!scanner.eof()) {
// Consume escaped sequence no matter of context
if (escaped(scanner)) {
value += scanner.current();
continue;
}
const ch = scanner.peek();
if (ch === Chars$3.Slash && !ctx.quote && !ctx.expression && !ctx.attribute) {
// Special case for `/` character between numbers in class names
const prev = scanner.string.charCodeAt(scanner.pos - 1);
const next = scanner.string.charCodeAt(scanner.pos + 1);
if (isNumber$1(prev) && isNumber$1(next)) {
value += scanner.string[scanner.pos++];
continue;
}
}
if (ch === ctx.quote || ch === Chars$3.Dollar || isAllowedOperator(ch, ctx)) {
// 1. Found matching quote
// 2. The `$` character has special meaning in every context
// 3. Depending on context, some characters should be treated as operators
break;
}
if (expressionStart) {
// Consume nested expressions, e.g. span{{foo}}
if (ch === Chars$3.CurlyBracketOpen) {
ctx.expression++;
}
else if (ch === Chars$3.CurlyBracketClose) {
if (ctx.expression > expressionStart) {
ctx.expression--;
}
else {
break;
}
}
}
else if (!ctx.quote) {
// Consuming element name
if (!ctx.attribute && !isElementName(ch)) {
break;
}
if (isAllowedSpace(ch, ctx) || isAllowedRepeater(ch, ctx) || isQuote$2(ch) || bracketType(ch)) {
// Stop for characters not allowed in unquoted literal
break;
}
}
value += scanner.string[scanner.pos++];
}
if (start !== scanner.pos) {
scanner.start = start;
return {
type: 'Literal',
value,
start,
end: scanner.pos
};
}
}
/**
* Consumes white space characters as string literal from given scanner
*/
function whiteSpace$1(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(isSpace)) {
return {
type: 'WhiteSpace',
start,
end: scanner.pos,
value: scanner.substring(start, scanner.pos)
};
}
}
/**
* Consumes quote from given scanner
*/
function quote(scanner) {
const ch = scanner.peek();
if (isQuote$2(ch)) {
return {
type: 'Quote',
single: ch === Chars$3.SingleQuote,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes bracket from given scanner
*/
function bracket$1(scanner) {
const ch = scanner.peek();
const context = bracketType(ch);
if (context) {
return {
type: 'Bracket',
open: isOpenBracket$2(ch),
context,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes operator from given scanner
*/
function operator$1(scanner) {
const op = operatorType$1(scanner.peek());
if (op) {
return {
type: 'Operator',
operator: op,
start: scanner.pos++,
end: scanner.pos
};
}
}
/**
* Consumes node repeat token from current stream position and returns its
* parsed value
*/
function repeater(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$3.Asterisk)) {
scanner.start = scanner.pos;
let count = 1;
let implicit = false;
if (scanner.eatWhile(isNumber$1)) {
count = Number(scanner.current());
}
else {
implicit = true;
}
return {
type: 'Repeater',
count,
value: 0,
implicit,
start,
end: scanner.pos
};
}
}
/**
* Consumes repeater placeholder `$#` from given scanner
*/
function repeaterPlaceholder(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$3.Dollar) && scanner.eat(Chars$3.Hash)) {
return {
type: 'RepeaterPlaceholder',
value: void 0,
start,
end: scanner.pos
};
}
scanner.pos = start;
}
/**
* Consumes numbering token like `$` from given scanner state
*/
function repeaterNumber(scanner) {
const start = scanner.pos;
if (scanner.eatWhile(Chars$3.Dollar)) {
const size = scanner.pos - start;
let reverse = false;
let base = 1;
let parent = 0;
if (scanner.eat(Chars$3.At)) {
// Consume numbering modifiers
while (scanner.eat(Chars$3.Climb)) {
parent++;
}
reverse = scanner.eat(Chars$3.Dash);
scanner.start = scanner.pos;
if (scanner.eatWhile(isNumber$1)) {
base = Number(scanner.current());
}
}
scanner.start = start;
return {
type: 'RepeaterNumber',
size,
reverse,
base,
parent,
start,
end: scanner.pos
};
}
}
function field$2(scanner, ctx) {
const start = scanner.pos;
// Fields are allowed inside expressions and attributes
if ((ctx.expression || ctx.attribute) && scanner.eat(Chars$3.Dollar) && scanner.eat(Chars$3.CurlyBracketOpen)) {
scanner.start = scanner.pos;
let index;
let name = '';
if (scanner.eatWhile(isNumber$1)) {
// Its a field
index = Number(scanner.current());
name = scanner.eat(Chars$3.Colon) ? consumePlaceholder$2(scanner) : '';
}
else if (isAlpha$1(scanner.peek())) {
// Its a variable
name = consumePlaceholder$2(scanner);
}
if (scanner.eat(Chars$3.CurlyBracketClose)) {
return {
type: 'Field',
index, name,
start,
end: scanner.pos
};
}
throw scanner.error('Expecting }');
}
// If we reached here then theres 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$2(stream) {
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
if (stream.eat(Chars$3.CurlyBracketOpen)) {
stack.push(stream.pos);
}
else if (stream.eat(Chars$3.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();
}
/**
* Check if given character code is an operator and its allowed in current context
*/
function isAllowedOperator(ch, ctx) {
const op = operatorType$1(ch);
if (!op || ctx.quote || ctx.expression) {
// No operators inside quoted values or expressions
return false;
}
// Inside attributes, only `equals` is allowed
return !ctx.attribute || op === 'equal';
}
/**
* Check if given character is a space character and is allowed to be consumed
* as a space token in current context
*/
function isAllowedSpace(ch, ctx) {
return isSpace(ch) && !ctx.expression;
}
/**
* Check if given character can be consumed as repeater in current context
*/
function isAllowedRepeater(ch, ctx) {
return ch === Chars$3.Asterisk && !ctx.attribute && !ctx.expression;
}
/**
* If given character is a bracket, returns its type
*/
function bracketType(ch) {
if (ch === Chars$3.RoundBracketOpen || ch === Chars$3.RoundBracketClose) {
return 'group';
}
if (ch === Chars$3.SquareBracketOpen || ch === Chars$3.SquareBracketClose) {
return 'attribute';
}
if (ch === Chars$3.CurlyBracketOpen || ch === Chars$3.CurlyBracketClose) {
return 'expression';
}
}
/**
* If given character is an operator, returns its type
*/
function operatorType$1(ch) {
return (ch === Chars$3.Child && 'child')
|| (ch === Chars$3.Sibling && 'sibling')
|| (ch === Chars$3.Climb && 'climb')
|| (ch === Chars$3.Dot && 'class')
|| (ch === Chars$3.Hash && 'id')
|| (ch === Chars$3.Slash && 'close')
|| (ch === Chars$3.Equals && 'equal')
|| void 0;
}
/**
* Check if given character is an open bracket
*/
function isOpenBracket$2(ch) {
return ch === Chars$3.CurlyBracketOpen
|| ch === Chars$3.SquareBracketOpen
|| ch === Chars$3.RoundBracketOpen;
}
/**
* Check if given character is allowed in element name
*/
function isElementName(ch) {
return isAlphaNumericWord(ch)
|| isUmlaut(ch)
|| ch === Chars$3.Dash
|| ch === Chars$3.Colon
|| ch === Chars$3.Excl;
}
const operators = {
child: '>',
class: '.',
climb: '^',
id: '#',
equal: '=',
close: '/',
sibling: '+'
};
const tokenVisitor = {
Literal(token) {
return token.value;
},
Quote(token) {
return token.single ? '\'' : '"';
},
Bracket(token) {
if (token.context === 'attribute') {
return token.open ? '[' : ']';
}
else if (token.context === 'expression') {
return token.open ? '{' : '}';
}
else {
return token.open ? '(' : '}';
}
},
Operator(token) {
return operators[token.operator];
},
Field(token, state) {
if (token.index != null) {
// Its a field: by default, return TextMate-compatible field
return token.name
? `\${${token.index}:${token.name}}`
: `\${${token.index}`;
}
else if (token.name) {
// Its a variable
return state.getVariable(token.name);
}
return '';
},
RepeaterPlaceholder(token, state) {
// Find closest implicit repeater
let repeater;
for (let i = state.repeaters.length - 1; i >= 0; i--) {
if (state.repeaters[i].implicit) {
repeater = state.repeaters[i];
break;
}
}
state.inserted = true;
return state.getText(repeater && repeater.value);
},
RepeaterNumber(token, state) {
let value = 1;
const lastIx = state.repeaters.length - 1;
// const repeaterIx = Math.max(0, state.repeaters.length - 1 - token.parent);
const repeater = state.repeaters[lastIx];
if (repeater) {
value = token.reverse
? token.base + repeater.count - repeater.value - 1
: token.base + repeater.value;
if (token.parent) {
const parentIx = Math.max(0, lastIx - token.parent);
if (parentIx !== lastIx) {
const parentRepeater = state.repeaters[parentIx];
value += repeater.count * parentRepeater.value;
}
}
}
let result = String(value);
while (result.length < token.size) {
result = '0' + result;
}
return result;
},
WhiteSpace(token) {
return token.value;
}
};
/**
* Converts given value token to string
*/
function stringify$1(token, state) {
if (!tokenVisitor[token.type]) {
throw new Error(`Unknown token ${token.type}`);
}
return tokenVisitor[token.type](token, state);
}
const urlRegex = /^((https?:|ftp:|file:)?\/\/|(www|ftp)\.)[^ ]*$/;
const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,5}$/;
/**
* Converts given token-based abbreviation into simplified and unrolled node-based
* abbreviation
*/
function convert(abbr, options = {}) {
let textInserted = false;
let cleanText;
if (options.text) {
if (Array.isArray(options.text)) {
cleanText = options.text.filter(s => s.trim());
}
else {
cleanText = options.text;
}
}
const result = {
type: 'Abbreviation',
children: convertGroup(abbr, {
inserted: false,
repeaters: [],
text: options.text,
cleanText,
repeatGuard: options.maxRepeat || Number.POSITIVE_INFINITY,
getText(pos) {
var _a;
textInserted = true;
let value;
if (Array.isArray(options.text)) {
if (pos !== undefined && pos >= 0 && pos < cleanText.length) {
return cleanText[pos];
}
value = pos !== undefined ? options.text[pos] : options.text.join('\n');
}
else {
value = (_a = options.text) !== null && _a !== void 0 ? _a : '';
}
return value;
},
getVariable(name) {
const varValue = options.variables && options.variables[name];
return varValue != null ? varValue : name;
}
})
};
if (options.text != null && !textInserted) {
// Text given but no implicitly repeated elements: insert it into
// deepest child
const deepest = deepestNode(last$1(result.children));
if (deepest) {
const text = Array.isArray(options.text) ? options.text.join('\n') : options.text;
insertText(deepest, text);
if (deepest.name === 'a' && options.href) {
// Automatically update value of `<a>` element if inserting URL or email
insertHref(deepest, text);
}
}
}
return result;
}
/**
* Converts given statement to abbreviation nodes
*/
function convertStatement(node, state) {
let result = [];
if (node.repeat) {
// Node is repeated: we should create copies of given node
// and supply context token with actual repeater state
const original = node.repeat;
const repeat = Object.assign({}, original);
repeat.count = repeat.implicit && Array.isArray(state.text)
? state.cleanText.length
: (repeat.count || 1);
let items;
state.repeaters.push(repeat);
for (let i = 0; i < repeat.count; i++) {
repeat.value = i;
node.repeat = repeat;
items = isGroup(node)
? convertGroup(node, state)
: convertElement(node, state);
if (repeat.implicit && !state.inserted) {
// Its an implicit repeater but no repeater placeholders found inside,
// we should insert text into deepest node
const target = last$1(items);
const deepest = target && deepestNode(target);
if (deepest) {
insertText(deepest, state.getText(repeat.value));
}
}
result = result.concat(items);
// We should output at least one repeated item even if its reached
// repeat limit
if (--state.repeatGuard <= 0) {
break;
}
}
state.repeaters.pop();
node.repeat = original;
if (repeat.implicit) {
state.inserted = true;
}
}
else {
result = result.concat(isGroup(node) ? convertGroup(node, state) : convertElement(node, state));
}
return result;
}
function convertElement(node, state) {
let children = [];
const elem = {
type: 'AbbreviationNode',
name: node.name && stringifyName(node.name, state),
value: node.value && stringifyValue$1(node.value, state),
attributes: void 0,
children,
repeat: node.repeat && Object.assign({}, node.repeat),
selfClosing: node.selfClose,
};
let result = [elem];
for (const child of node.elements) {
children = children.concat(convertStatement(child, state));
}
if (node.attributes) {
elem.attributes = [];
for (const attr of node.attributes) {
elem.attributes.push(convertAttribute(attr, state));
}
}
// In case if current node is a text-only snippet without fields, we should
// put all children as siblings
if (!elem.name && !elem.attributes && elem.value && !elem.value.some(isField$1)) {
// XXX its unclear that `children` is not bound to `elem`
// due to concat operation
result = result.concat(children);
}
else {
elem.children = children;
}
return result;
}
function convertGroup(node, state) {
let result = [];
for (const child of node.elements) {
result = result.concat(convertStatement(child, state));
}
if (node.repeat) {
result = attachRepeater(result, node.repeat);
}
return result;
}
function convertAttribute(node, state) {
let implied = false;
let isBoolean = false;
let valueType = node.expression ? 'expression' : 'raw';
let value;
const name = node.name && stringifyName(node.name, state);
if (name && name[0] === '!') {
implied = true;
}
if (name && name[name.length - 1] === '.') {
isBoolean = true;
}
if (node.value) {
const tokens = node.value.slice();
if (isQuote$1(tokens[0])) {
// Its a quoted value: remove quotes from output but mark attribute
// value as quoted
const quote = tokens.shift();
if (tokens.length && last$1(tokens).type === quote.type) {
tokens.pop();
}
valueType = quote.single ? 'singleQuote' : 'doubleQuote';
}
else if (isBracket$2(tokens[0], 'expression', true)) {
// Value is expression: remove brackets but mark value type
valueType = 'expression';
tokens.shift();
if (isBracket$2(last$1(tokens), 'expression', false)) {
tokens.pop();
}
}
value = stringifyValue$1(tokens, state);
}
return {
name: isBoolean || implied
? name.slice(implied ? 1 : 0, isBoolean ? -1 : void 0)
: name,
value,
boolean: isBoolean,
implied,
valueType,
multiple: node.multiple
};
}
/**
* Converts given token list to string
*/
function stringifyName(tokens, state) {
let str = '';
for (let i = 0; i < tokens.length; i++) {
str += stringify$1(tokens[i], state);
}
return str;
}
/**
* Converts given token list to value list
*/
function stringifyValue$1(tokens, state) {
const result = [];
let str = '';
for (let i = 0, token; i < tokens.length; i++) {
token = tokens[i];
if (isField$1(token)) {
// We should keep original fields in output since some editors has their
// own syntax for field or doesnt support fields at all so we should
// capture actual field location in output stream
if (str) {
result.push(str);
str = '';
}
result.push(token);
}
else {
str += stringify$1(token, state);
}
}
if (str) {
result.push(str);
}
return result;
}
function isGroup(node) {
return node.type === 'TokenGroup';
}
function isField$1(token) {
return typeof token === 'object' && token.type === 'Field' && token.index != null;
}
function last$1(arr) {
return arr[arr.length - 1];
}
function deepestNode(node) {
return node.children.length ? deepestNode(last$1(node.children)) : node;
}
function insertText(node, text) {
if (node.value) {
const lastToken = last$1(node.value);
if (typeof lastToken === 'string') {
node.value[node.value.length - 1] += text;
}
else {
node.value.push(text);
}
}
else {
node.value = [text];
}
}
function insertHref(node, text) {
var _a;
let href = '';
if (urlRegex.test(text)) {
href = text;
if (!/\w+:/.test(href) && !href.startsWith('//')) {
href = `http://${href}`;
}
}
else if (emailRegex.test(text)) {
href = `mailto:${text}`;
}
const hrefAttribute = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.find(attr => attr.name === 'href');
if (!hrefAttribute) {
if (!node.attributes) {
node.attributes = [];
}
node.attributes.push({ name: 'href', value: [href], valueType: 'doubleQuote' });
}
else if (!hrefAttribute.value) {
hrefAttribute.value = [href];
}
}
function attachRepeater(items, repeater) {
for (const item of items) {
if (!item.repeat) {
item.repeat = Object.assign({}, repeater);
}
}
return items;
}
/**
* Parses given abbreviation into node tree
*/
function parseAbbreviation(abbr, options) {
try {
const tokens = typeof abbr === 'string' ? tokenize$1(abbr) : abbr;
return convert(abbreviation(tokens, options), options);
}
catch (err) {
if (err instanceof ScannerError && typeof abbr === 'string') {
err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
}
throw err;
}
}
var OperatorType;
(function (OperatorType) {
OperatorType["Sibling"] = "+";
OperatorType["Important"] = "!";
OperatorType["ArgumentDelimiter"] = ",";
OperatorType["ValueDelimiter"] = "-";
OperatorType["PropertyDelimiter"] = ":";
})(OperatorType || (OperatorType = {}));
var Chars$2;
(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$2 || (Chars$2 = {}));
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$1(scanner)
|| customProperty(scanner)
|| numberValue(scanner)
|| colorValue(scanner)
|| stringValue(scanner)
|| bracket(scanner)
|| operator(scanner)
|| whiteSpace(scanner)
|| literal$1(scanner, short);
}
function field$1(scanner) {
const start = scanner.pos;
if (scanner.eat(Chars$2.Dollar) && scanner.eat(Chars$2.CurlyBracketOpen)) {
scanner.start = scanner.pos;
let index;
let name = '';
if (scanner.eatWhile(isNumber$1)) {
// Its a field
index = Number(scanner.current());
name = scanner.eat(Chars$2.Colon) ? consumePlaceholder$1(scanner) : '';
}
else if (isAlpha$1(scanner.peek())) {
// Its a variable
name = consumePlaceholder$1(scanner);
}
if (scanner.eat(Chars$2.CurlyBracketClose)) {
return {
type: 'Field',
index, name,
start,
end: scanner.pos
};
}
throw scanner.error('Expecting }');
}
// If we reached here then theres 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$1(stream) {
const stack = [];
stream.start = stream.pos;
while (!stream.eof()) {
if (stream.eat(Chars$2.CurlyBracketOpen)) {
stack.push(stream.pos);
}
else if (stream.eat(Chars$2.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$1(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(isAlphaWord)) {
scanner.eatWhile(short ? isLiteral$1 : isKeyword);
}
else {
// Allow dots only at the beginning of literal
scanner.eat(Chars$2.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$2.Percent) || scanner.eatWhile(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 (isQuote$2(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$2.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$2.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$2.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$2.Dot)) {
scanner.start = start;
if (scanner.eatWhile(isNumber$1)) {
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(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$2.Dash) && scanner.eat(Chars$2.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$2.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$2.Dash);
const afterNegative = stream.pos;
const hasDecimal = stream.eatWhile(isNumber$1);
const prevPos = stream.pos;
if (stream.eat(Chars$2.Dot)) {
// Its perfectly valid to have numbers like `1.`, which enforces
// value to float unit type
const hasFloat = stream.eatWhile(isNumber$1);
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$2.At || code === Chars$2.Dollar;
}
/**
* If given character is an operator, returns its type
*/
function operatorType(ch) {
return (ch === Chars$2.Sibling && OperatorType.Sibling)
|| (ch === Chars$2.Excl && OperatorType.Important)
|| (ch === Chars$2.Comma && OperatorType.ArgumentDelimiter)
|| (ch === Chars$2.Colon && OperatorType.PropertyDelimiter)
|| (ch === Chars$2.Dash && OperatorType.ValueDelimiter)
|| void 0;
}
/**
* Check if given code is a hex value (/0-9a-f/)
*/
function isHex(code) {
return isNumber$1(code) || isAlpha$1(code, 65, 70); // A-F
}
function isKeyword(code) {
return isAlphaNumericWord(code) || code === Chars$2.Dash;
}
function isBracket$1(code) {
return code === Chars$2.RoundBracketOpen || code === Chars$2.RoundBracketClose;
}
function isLiteral$1(code) {
return isAlphaWord(code) || code === Chars$2.Percent || code === Chars$2.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$2(scanner) {
return scanner.tokens[scanner.pos];
}
function readable(scanner) {
return scanner.pos < scanner.size;
}
function consume$1(scanner, test) {
if (test(peek$2(scanner))) {
scanner.pos++;
return true;
}
return false;
}
function error(scanner, message, token = peek$2(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$1(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$2(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$1(scanner, isValueDelimiter);
}
// Skip whitespace right after property name, if any
if (valueMode) {
consume$1(scanner, isWhiteSpace$1);
}
while (readable(scanner)) {
if (consume$1(scanner, isImportant)) {
important = true;
}
else if (valueFragment = consumeValue(scanner, valueMode)) {
value.push(valueFragment);
}
else if (!consume$1(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$2(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$1(token))) {
scanner.pos++;
}
else {
break;
}
}
return result.length
? { type: 'CSSValue', value: result }
: void 0;
}
function consumeArguments(scanner) {
const start = scanner.pos;
if (consume$1(scanner, isOpenBracket$1)) {
const args = [];
let value;
while (readable(scanner) && !consume$1(scanner, isCloseBracket$1)) {
if (value = consumeValue(scanner, true)) {
args.push(value);
}
else if (!consume$1(scanner, isWhiteSpace$1) && !consume$1(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$1(token) {
return isBracket(token, true);
}
function isCloseBracket$1(token) {
return isBracket(token, false);
}
function isWhiteSpace$1(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, OperatorType.Sibling);
}
function isArgumentDelimiter(token) {
return isOperator(token, OperatorType.ArgumentDelimiter);
}
function isFragmentDelimiter(token) {
return isArgumentDelimiter(token);
}
function isImportant(token) {
return isOperator(token, 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, OperatorType.PropertyDelimiter)
|| isOperator(token, 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$2(abbr, options) {
try {
const tokens = typeof abbr === 'string' ? tokenize(abbr, options && options.value) : abbr;
return parser(tokens, options);
}
catch (err) {
if (err instanceof ScannerError && typeof abbr === 'string') {
err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
}
throw err;
}
}
/**
* Merges attributes in current node: de-duplicates attributes with the same name
* and merges class names
*/
function mergeAttributes(node, config) {
if (!node.attributes) {
return;
}
const attributes = [];
const lookup = {};
for (const attr of node.attributes) {
if (attr.name) {
const attrName = attr.name;
if (attrName in lookup) {
const prev = lookup[attrName];
if (attrName === 'class') {
prev.value = mergeValue(prev.value, attr.value, ' ');
}
else {
mergeDeclarations(prev, attr, config);
}
}
else {
// Create new attribute instance so we can safely modify it later
attributes.push(lookup[attrName] = Object.assign({}, attr));
}
}
else {
attributes.push(attr);
}
}
node.attributes = attributes;
}
/**
* Merges two token lists into single list. Adjacent strings are merged together
*/
function mergeValue(prev, next, glue) {
if (prev && next) {
if (prev.length && glue) {
append(prev, glue);
}
for (const t of next) {
append(prev, t);
}
return prev;
}
const result = prev || next;
return result && result.slice();
}
/**
* Merges data from `src` attribute into `dest` and returns it
*/
function mergeDeclarations(dest, src, config) {
dest.name = src.name;
if (!config.options['output.reverseAttributes']) {
dest.value = src.value;
}
// Keep high-priority properties
if (!dest.implied) {
dest.implied = src.implied;
}
if (!dest.boolean) {
dest.boolean = src.boolean;
}
if (dest.valueType !== 'expression') {
dest.valueType = src.valueType;
}
return dest;
}
function append(tokens, value) {
const lastIx = tokens.length - 1;
if (typeof tokens[lastIx] === 'string' && typeof value === 'string') {
tokens[lastIx] += value;
}
else {
tokens.push(value);
}
}
/**
* Walks over each child node of given markup abbreviation AST node (not including
* given one) and invokes `fn` on each node.
* The `fn` callback accepts context node, list of ancestor nodes and optional
* state object
*/
function walk$1(node, fn, state) {
const ancestors = [node];
const callback = (ctx) => {
fn(ctx, ancestors, state);
ancestors.push(ctx);
ctx.children.forEach(callback);
ancestors.pop();
};
node.children.forEach(callback);
}
/**
* Finds node which is the deepest for in current node or node itself.
*/
function findDeepest(node) {
let parent;
while (node.children.length) {
parent = node;
node = node.children[node.children.length - 1];
}
return { parent, node };
}
function isNode(node) {
return node.type === 'AbbreviationNode';
}
/**
* Finds matching snippet from `registry` and resolves it into a parsed abbreviation.
* Resolved node is then updated or replaced with matched abbreviation tree.
*
* A HTML registry basically contains aliases to another Emmet abbreviations,
* e.g. a predefined set of name, attributes and so on, possibly a complex
* abbreviation with multiple elements. So we have to get snippet, parse it
* and recursively resolve it.
*/
function resolveSnippets(abbr, config) {
const stack = [];
const reversed = config.options['output.reverseAttributes'];
const resolve = (child) => {
const snippet = child.name && config.snippets[child.name];
// A snippet in stack means circular reference.
// It can be either a user error or a perfectly valid snippet like
// "img": "img[src alt]/", e.g. an element with predefined shape.
// In any case, simply stop parsing and keep element as is
if (!snippet || stack.includes(snippet)) {
return null;
}
const snippetAbbr = parseAbbreviation(snippet, config);
stack.push(snippet);
walkResolve(snippetAbbr, resolve);
stack.pop();
// Add attributes from current node into every top-level node of parsed abbreviation
for (const topNode of snippetAbbr.children) {
if (child.attributes) {
const from = topNode.attributes || [];
const to = child.attributes || [];
topNode.attributes = reversed ? to.concat(from) : from.concat(to);
}
mergeNodes(child, topNode);
}
return snippetAbbr;
};
walkResolve(abbr, resolve);
return abbr;
}
function walkResolve(node, resolve, config) {
let children = [];
for (const child of node.children) {
const resolved = resolve(child);
if (resolved) {
children = children.concat(resolved.children);
const deepest = findDeepest(resolved);
if (isNode(deepest.node)) {
deepest.node.children = deepest.node.children.concat(walkResolve(child, resolve));
}
}
else {
children.push(child);
child.children = walkResolve(child, resolve);
}
}
return node.children = children;
}
/**
* Adds data from first node into second node
*/
function mergeNodes(from, to) {
if (from.selfClosing) {
to.selfClosing = true;
}
if (from.value != null) {
to.value = from.value;
}
if (from.repeat) {
to.repeat = from.repeat;
}
}
const expressionStart = '{';
const expressionEnd = '}';
function createOutputStream(options, level = 0) {
return {
options,
value: '',
level,
offset: 0,
line: 0,
column: 0
};
}
/**
* Pushes plain string into output stream without newline processing
*/
function push(stream, text) {
const processText = stream.options['output.text'];
_push(stream, processText(text, stream.offset, stream.line, stream.column));
}
/**
* Pushes given string with possible newline formatting into output
*/
function pushString(stream, value) {
// If given value contains newlines, we should push content line-by-line and
// use `pushNewline()` to maintain proper line/column state
const lines = splitByLines$1(value);
for (let i = 0, il = lines.length - 1; i <= il; i++) {
push(stream, lines[i]);
if (i !== il) {
pushNewline(stream, true);
}
}
}
/**
* Pushes new line into given output stream
*/
function pushNewline(stream, indent) {
const baseIndent = stream.options['output.baseIndent'];
const newline = stream.options['output.newline'];
push(stream, newline + baseIndent);
stream.line++;
stream.column = baseIndent.length;
if (indent) {
pushIndent(stream, indent === true ? stream.level : indent);
}
}
/**
* Adds indentation of `size` to current output stream
*/
function pushIndent(stream, size = stream.level) {
const indent = stream.options['output.indent'];
push(stream, indent.repeat(Math.max(size, 0)));
}
/**
* Pushes field/tabstop into output stream
*/
function pushField(stream, index, placeholder) {
const field = stream.options['output.field'];
// NB: use `_push` instead of `push` to skip text processing
_push(stream, field(index, placeholder, stream.offset, stream.line, stream.column));
}
/**
* Returns given tag name formatted according to given config
*/
function tagName(name, config) {
return strCase(name, config.options['output.tagCase']);
}
/**
* Returns given attribute name formatted according to given config
*/
function attrName(name, config) {
return strCase(name, config.options['output.attributeCase']);
}
/**
* Returns character for quoting value of given attribute
*/
function attrQuote(attr, config, isOpen) {
if (attr.valueType === 'expression') {
return isOpen ? expressionStart : expressionEnd;
}
return config.options['output.attributeQuotes'] === 'single' ? '\'' : '"';
}
/**
* Check if given attribute is boolean
*/
function isBooleanAttribute(attr, config) {
return attr.boolean
|| config.options['output.booleanAttributes'].includes((attr.name || '').toLowerCase());
}
/**
* Returns a token for self-closing tag, depending on current options
*/
function selfClose(config) {
switch (config.options['output.selfClosingStyle']) {
case 'xhtml': return ' /';
case 'xml': return '/';
default: return '';
}
}
/**
* Check if given tag name belongs to inline-level element
* @param node Parsed node or tag name
*/
function isInline(node, config) {
if (typeof node === 'string') {
return config.options.inlineElements.includes(node.toLowerCase());
}
// inline node is a node either with inline-level name or text-only node
return node.name ? isInline(node.name, config) : Boolean(node.value && !node.attributes);
}
/**
* Splits given text by lines
*/
function splitByLines$1(text) {
return text.split(/\r\n|\r|\n/g);
}
/**
* Pushes raw string into output stream without any processing
*/
function _push(stream, text) {
stream.value += text;
stream.offset += text.length;
stream.column += text.length;
}
function strCase(str, type) {
if (type) {
return type === 'upper' ? str.toUpperCase() : str.toLowerCase();
}
return str;
}
const elementMap = {
p: 'span',
ul: 'li',
ol: 'li',
table: 'tr',
tr: 'td',
tbody: 'tr',
thead: 'tr',
tfoot: 'tr',
colgroup: 'col',
select: 'option',
optgroup: 'option',
audio: 'source',
video: 'source',
object: 'param',
map: 'area'
};
function implicitTag(node, ancestors, config) {
if (!node.name && node.attributes) {
resolveImplicitTag(node, ancestors, config);
}
}
function resolveImplicitTag(node, ancestors, config) {
const parent = getParentElement(ancestors);
const contextName = config.context ? config.context.name : '';
const parentName = lowercase(parent ? parent.name : contextName);
node.name = elementMap[parentName]
|| (isInline(parentName, config) ? 'span' : 'div');
}
function lowercase(str) {
return (str || '').toLowerCase();
}
/**
* Returns closest element node from given ancestors list
*/
function getParentElement(ancestors) {
for (let i = ancestors.length - 1; i >= 0; i--) {
const elem = ancestors[i];
if (isNode(elem)) {
return elem;
}
}
}
var latin = {
"common": ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipisicing", "elit"],
"words": ["exercitationem", "perferendis", "perspiciatis", "laborum", "eveniet",
"sunt", "iure", "nam", "nobis", "eum", "cum", "officiis", "excepturi",
"odio", "consectetur", "quasi", "aut", "quisquam", "vel", "eligendi",
"itaque", "non", "odit", "tempore", "quaerat", "dignissimos",
"facilis", "neque", "nihil", "expedita", "vitae", "vero", "ipsum",
"nisi", "animi", "cumque", "pariatur", "velit", "modi", "natus",
"iusto", "eaque", "sequi", "illo", "sed", "ex", "et", "voluptatibus",
"tempora", "veritatis", "ratione", "assumenda", "incidunt", "nostrum",
"placeat", "aliquid", "fuga", "provident", "praesentium", "rem",
"necessitatibus", "suscipit", "adipisci", "quidem", "possimus",
"voluptas", "debitis", "sint", "accusantium", "unde", "sapiente",
"voluptate", "qui", "aspernatur", "laudantium", "soluta", "amet",
"quo", "aliquam", "saepe", "culpa", "libero", "ipsa", "dicta",
"reiciendis", "nesciunt", "doloribus", "autem", "impedit", "minima",
"maiores", "repudiandae", "ipsam", "obcaecati", "ullam", "enim",
"totam", "delectus", "ducimus", "quis", "voluptates", "dolores",
"molestiae", "harum", "dolorem", "quia", "voluptatem", "molestias",
"magni", "distinctio", "omnis", "illum", "dolorum", "voluptatum", "ea",
"quas", "quam", "corporis", "quae", "blanditiis", "atque", "deserunt",
"laboriosam", "earum", "consequuntur", "hic", "cupiditate",
"quibusdam", "accusamus", "ut", "rerum", "error", "minus", "eius",
"ab", "ad", "nemo", "fugit", "officia", "at", "in", "id", "quos",
"reprehenderit", "numquam", "iste", "fugiat", "sit", "inventore",
"beatae", "repellendus", "magnam", "recusandae", "quod", "explicabo",
"doloremque", "aperiam", "consequatur", "asperiores", "commodi",
"optio", "dolor", "labore", "temporibus", "repellat", "veniam",
"architecto", "est", "esse", "mollitia", "nulla", "a", "similique",
"eos", "alias", "dolore", "tenetur", "deleniti", "porro", "facere",
"maxime", "corrupti"]
};
var ru = {
"common": ["далеко-далеко", "за", "словесными", "горами", "в стране", "гласных", "и согласных", "живут", "рыбные", "тексты"],
"words": ["вдали", "от всех", "они", "буквенных", "домах", "на берегу", "семантика",
"большого", "языкового", "океана", "маленький", "ручеек", "даль",
"журчит", "по всей", "обеспечивает", "ее","всеми", "необходимыми",
"правилами", "эта", "парадигматическая", "страна", "которой", "жаренные",
"предложения", "залетают", "прямо", "рот", "даже", "всемогущая",
"пунктуация", "не", "имеет", "власти", "над", "рыбными", "текстами",
"ведущими", "безорфографичный", "образ", "жизни", "однажды", "одна",
"маленькая", "строчка","рыбного", "текста", "имени", "lorem", "ipsum",
"решила", "выйти", "большой", "мир", "грамматики", "великий", "оксмокс",
"предупреждал", "о", "злых", "запятых", "диких", "знаках", "вопроса",
"коварных", "точках", "запятой", "но", "текст", "дал", "сбить",
"себя", "толку", "он", "собрал", "семь", "своих", "заглавных", "букв",
"подпоясал", "инициал", "за", "пояс", "пустился", "дорогу",
"взобравшись", "первую", "вершину", "курсивных", "гор", "бросил",
"последний", "взгляд", "назад", "силуэт", "своего", "родного", "города",
"буквоград", "заголовок", "деревни", "алфавит", "подзаголовок", "своего",
"переулка", "грустный", "реторический", "вопрос", "скатился", "его",
"щеке", "продолжил", "свой", "путь", "дороге", "встретил", "рукопись",
"она", "предупредила", "моей", "все", "переписывается", "несколько",
"раз", "единственное", "что", "меня", "осталось", "это", "приставка",
"возвращайся", "ты", "лучше", "свою", "безопасную", "страну", "послушавшись",
"рукописи", "наш", "продолжил", "свой", "путь", "вскоре", "ему",
"повстречался", "коварный", "составитель", "рекламных", "текстов",
"напоивший", "языком", "речью", "заманивший", "свое", "агентство",
"которое", "использовало", "снова", "снова", "своих", "проектах",
"если", "переписали", "то", "живет", "там", "до", "сих", "пор"]
};
var sp = {
"common": ["mujer", "uno", "dolor", "más", "de", "poder", "mismo", "si"],
"words": ["ejercicio", "preferencia", "perspicacia", "laboral", "paño",
"suntuoso", "molde", "namibia", "planeador", "mirar", "demás", "oficinista", "excepción",
"odio", "consecuencia", "casi", "auto", "chicharra", "velo", "elixir",
"ataque", "no", "odio", "temporal", "cuórum", "dignísimo",
"facilismo", "letra", "nihilista", "expedición", "alma", "alveolar", "aparte",
"león", "animal", "como", "paria", "belleza", "modo", "natividad",
"justo", "ataque", "séquito", "pillo", "sed", "ex", "y", "voluminoso",
"temporalidad", "verdades", "racional", "asunción", "incidente", "marejada",
"placenta", "amanecer", "fuga", "previsor", "presentación", "lejos",
"necesariamente", "sospechoso", "adiposidad", "quindío", "pócima",
"voluble", "débito", "sintió", "accesorio", "falda", "sapiencia",
"volutas", "queso", "permacultura", "laudo", "soluciones", "entero",
"pan", "litro", "tonelada", "culpa", "libertario", "mosca", "dictado",
"reincidente", "nascimiento", "dolor", "escolar", "impedimento", "mínima",
"mayores", "repugnante", "dulce", "obcecado", "montaña", "enigma",
"total", "deletéreo", "décima", "cábala", "fotografía", "dolores",
"molesto", "olvido", "paciencia", "resiliencia", "voluntad", "molestias",
"magnífico", "distinción", "ovni", "marejada", "cerro", "torre", "y",
"abogada", "manantial", "corporal", "agua", "crepúsculo", "ataque", "desierto",
"laboriosamente", "angustia", "afortunado", "alma", "encefalograma",
"materialidad", "cosas", "o", "renuncia", "error", "menos", "conejo",
"abadía", "analfabeto", "remo", "fugacidad", "oficio", "en", "almácigo", "vos", "pan",
"represión", "números", "triste", "refugiado", "trote", "inventor",
"corchea", "repelente", "magma", "recusado", "patrón", "explícito",
"paloma", "síndrome", "inmune", "autoinmune", "comodidad",
"ley", "vietnamita", "demonio", "tasmania", "repeler", "apéndice",
"arquitecto", "columna", "yugo", "computador", "mula", "a", "propósito",
"fantasía", "alias", "rayo", "tenedor", "deleznable", "ventana", "cara",
"anemia", "corrupto"]
};
const vocabularies = { ru, sp, latin };
const reLorem = /^lorem([a-z]*)(\d*)(-\d*)?$/i;
function lorem(node, ancestors, config) {
let m;
if (node.name && (m = node.name.match(reLorem))) {
const db = vocabularies[m[1]] || vocabularies.latin;
const minWordCount = m[2] ? Math.max(1, Number(m[2])) : 30;
const maxWordCount = m[3] ? Math.max(minWordCount, Number(m[3].slice(1))) : minWordCount;
const wordCount = rand(minWordCount, maxWordCount);
const repeat = node.repeat || findRepeater(ancestors);
node.name = node.attributes = void 0;
node.value = [paragraph(db, wordCount, !repeat || repeat.value === 0)];
if (node.repeat && ancestors.length > 1) {
resolveImplicitTag(node, ancestors, config);
}
}
}
/**
* Returns random integer between <code>from</code> and <code>to</code> values
*/
function rand(from, to) {
return Math.floor(Math.random() * (to - from) + from);
}
function sample(arr, count) {
const len = arr.length;
const iterations = Math.min(len, count);
const result = [];
while (result.length < iterations) {
const str = arr[rand(0, len)];
if (!result.includes(str)) {
result.push(str);
}
}
return result;
}
function choice(val) {
return val[rand(0, val.length - 1)];
}
function sentence(words, end) {
if (words.length) {
words = [capitalize(words[0])].concat(words.slice(1));
}
return words.join(' ') + (end || choice('?!...')); // more dots than question marks
}
function capitalize(word) {
return word[0].toUpperCase() + word.slice(1);
}
/**
* Insert commas at randomly selected words. This function modifies values
* inside `words` array
*/
function insertCommas(words) {
if (words.length < 2) {
return words;
}
words = words.slice();
const len = words.length;
const hasComma = /,$/;
let totalCommas = 0;
if (len > 3 && len <= 6) {
totalCommas = rand(0, 1);
}
else if (len > 6 && len <= 12) {
totalCommas = rand(0, 2);
}
else {
totalCommas = rand(1, 4);
}
for (let i = 0, pos; i < totalCommas; i++) {
pos = rand(0, len - 2);
if (!hasComma.test(words[pos])) {
words[pos] += ',';
}
}
return words;
}
/**
* Generate a paragraph of "Lorem ipsum" text
* @param dict Words dictionary
* @param wordCount Words count in paragraph
* @param startWithCommon Should paragraph start with common "lorem ipsum" sentence.
*/
function paragraph(dict, wordCount, startWithCommon) {
const result = [];
let totalWords = 0;
let words;
if (startWithCommon && dict.common) {
words = dict.common.slice(0, wordCount);
totalWords += words.length;
result.push(sentence(insertCommas(words), '.'));
}
while (totalWords < wordCount) {
words = sample(dict.words, Math.min(rand(2, 30), wordCount - totalWords));
totalWords += words.length;
result.push(sentence(insertCommas(words)));
}
return result.join(' ');
}
function findRepeater(ancestors) {
for (let i = ancestors.length - 1; i >= 0; i--) {
const element = ancestors[i];
if (element.type === 'AbbreviationNode' && element.repeat) {
return element.repeat;
}
}
}
/**
* XSL transformer: removes `select` attributes from certain nodes that contain
* children
*/
function xsl(node) {
if (matchesName(node.name) && node.attributes && (node.children.length || node.value)) {
node.attributes = node.attributes.filter(isAllowed);
}
}
function isAllowed(attr) {
return attr.name !== 'select';
}
function matchesName(name) {
return name === 'xsl:variable' || name === 'xsl:with-param';
}
const reElement = /^(-+)([a-z0-9]+[a-z0-9-]*)/i;
const reModifier = /^(_+)([a-z0-9]+[a-z0-9-_]*)/i;
const blockCandidates1 = (className) => /^[a-z]\-/i.test(className);
const blockCandidates2 = (className) => /^[a-z]/i.test(className);
function bem(node, ancestors, config) {
expandClassNames(node);
expandShortNotation(node, ancestors, config);
}
/**
* Expands existing class names in BEM notation in given `node`.
* For example, if node contains `b__el_mod` class name, this method ensures
* that element contains `b__el` class as well
*/
function expandClassNames(node) {
const data = getBEMData(node);
const classNames = [];
for (const cl of data.classNames) {
// remove all modifiers and element prefixes from class name to get a base element name
const ix = cl.indexOf('_');
if (ix > 0 && !cl.startsWith('-')) {
classNames.push(cl.slice(0, ix));
classNames.push(cl.slice(ix));
}
else {
classNames.push(cl);
}
}
if (classNames.length) {
data.classNames = classNames.filter(uniqueClass);
data.block = findBlockName(data.classNames);
updateClass(node, data.classNames.join(' '));
}
}
/**
* Expands short BEM notation, e.g. `-element` and `_modifier`
*/
function expandShortNotation(node, ancestors, config) {
const data = getBEMData(node);
const classNames = [];
const { options } = config;
const path = ancestors.slice(1).concat(node);
for (let cl of data.classNames) {
let prefix = '';
let m;
const originalClass = cl;
// parse element definition (could be only one)
if (m = cl.match(reElement)) {
prefix = getBlockName(path, m[1].length, config.context) + options['bem.element'] + m[2];
classNames.push(prefix);
cl = cl.slice(m[0].length);
}
// parse modifiers definitions
if (m = cl.match(reModifier)) {
if (!prefix) {
prefix = getBlockName(path, m[1].length);
classNames.push(prefix);
}
classNames.push(`${prefix}${options['bem.modifier']}${m[2]}`);
cl = cl.slice(m[0].length);
}
if (cl === originalClass) {
// class name wasnt modified: its not a BEM-specific class,
// add it as-is into output
classNames.push(originalClass);
}
}
const arrClassNames = classNames.filter(uniqueClass);
if (arrClassNames.length) {
updateClass(node, arrClassNames.join(' '));
}
}
/**
* Returns BEM data from given abbreviation node
*/
function getBEMData(node) {
if (!node._bem) {
let classValue = '';
if (node.attributes) {
for (const attr of node.attributes) {
if (attr.name === 'class' && attr.value) {
classValue = stringifyValue(attr.value);
break;
}
}
}
node._bem = parseBEM(classValue);
}
return node._bem;
}
function getBEMDataFromContext(context) {
if (!context._bem) {
context._bem = parseBEM(context.attributes && context.attributes.class || '');
}
return context._bem;
}
/**
* Parses BEM data from given class name
*/
function parseBEM(classValue) {
const classNames = classValue ? classValue.split(/\s+/) : [];
return {
classNames,
block: findBlockName(classNames)
};
}
/**
* Returns block name for given `node` by `prefix`, which tells the depth of
* of parent node lookup
*/
function getBlockName(ancestors, depth = 0, context) {
const maxParentIx = 0;
let parentIx = Math.max(ancestors.length - depth, maxParentIx);
do {
const parent = ancestors[parentIx];
if (parent) {
const data = getBEMData(parent);
if (data.block) {
return data.block;
}
}
} while (maxParentIx < parentIx--);
if (context) {
const data = getBEMDataFromContext(context);
if (data.block) {
return data.block;
}
}
return '';
}
function findBlockName(classNames) {
return find(classNames, blockCandidates1)
|| find(classNames, blockCandidates2)
|| void 0;
}
/**
* Finds class name from given list which may be used as block name
*/
function find(classNames, filter) {
for (const cl of classNames) {
if (reElement.test(cl) || reModifier.test(cl)) {
break;
}
if (filter(cl)) {
return cl;
}
}
}
function updateClass(node, value) {
for (const attr of node.attributes) {
if (attr.name === 'class') {
attr.value = [value];
break;
}
}
}
function stringifyValue(value) {
let result = '';
for (const t of value) {
result += typeof t === 'string' ? t : t.name;
}
return result;
}
function uniqueClass(item, ix, arr) {
return !!item && arr.indexOf(item) === ix;
}
function walk(abbr, visitor, state) {
const callback = (ctx, index, items) => {
const { parent, current } = state;
state.parent = current;
state.current = ctx;
visitor(ctx, index, items, state, next);
state.current = current;
state.parent = parent;
};
const next = (node, index, items) => {
state.ancestors.push(state.current);
callback(node, index, items);
state.ancestors.pop();
};
abbr.children.forEach(callback);
}
function createWalkState(config) {
return {
// @ts-ignore: Will set value in iterator
current: null,
parent: void 0,
ancestors: [],
config,
field: 1,
out: createOutputStream(config.options)
};
}
const caret = [{ type: 'Field', index: 0, name: '' }];
/**
* Check if given node is a snippet: a node without name and attributes
*/
function isSnippet(node) {
return node ? !node.name && !node.attributes : false;
}
/**
* Check if given node is inline-level element, e.g. element with explicitly
* defined node name
*/
function isInlineElement(node, config) {
return node ? isInline(node, config) : false;
}
/**
* Check if given value token is a field
*/
function isField(token) {
return typeof token === 'object' && token.type === 'Field';
}
function pushTokens(tokens, state) {
const { out } = state;
let largestIndex = -1;
for (const t of tokens) {
if (typeof t === 'string') {
pushString(out, t);
}
else {
pushField(out, state.field + t.index, t.name);
if (t.index > largestIndex) {
largestIndex = t.index;
}
}
}
if (largestIndex !== -1) {
state.field += largestIndex + 1;
}
}
/**
* Splits given value token by lines: returns array where each entry is a token list
* for a single line
*/
function splitByLines(tokens) {
const result = [];
let line = [];
for (const t of tokens) {
if (typeof t === 'string') {
const lines = t.split(/\r\n?|\n/g);
line.push(lines.shift() || '');
while (lines.length) {
result.push(line);
line = [lines.shift() || ''];
}
}
else {
line.push(t);
}
}
line.length && result.push(line);
return result;
}
/**
* Check if given attribute should be outputted
*/
function shouldOutputAttribute(attr) {
// In case if attribute is implied, check if it has a defined value:
// either non-empty value or quoted empty value
return !attr.implied || attr.valueType !== 'raw' || (!!attr.value && attr.value.length > 0);
}
var TemplateChars;
(function (TemplateChars) {
/** `[` character */
TemplateChars[TemplateChars["Start"] = 91] = "Start";
/** `]` character */
TemplateChars[TemplateChars["End"] = 93] = "End";
/* `_` character */
TemplateChars[TemplateChars["Underscore"] = 95] = "Underscore";
/* `-` character */
TemplateChars[TemplateChars["Dash"] = 45] = "Dash";
})(TemplateChars || (TemplateChars = {}));
/**
* Splits given string into template tokens.
* Template is a string which contains placeholders which are uppercase names
* between `[` and `]`, for example: `[PLACEHOLDER]`.
* Unlike other templates, a placeholder may contain extra characters before and
* after name: `[%PLACEHOLDER.]`. If data for `PLACEHOLDER` is defined, it will
* be outputted with with these extra character, otherwise will be completely omitted.
*/
function template(text) {
const tokens = [];
const scanner = { pos: 0, text };
let placeholder;
let offset = scanner.pos;
let pos = scanner.pos;
while (scanner.pos < scanner.text.length) {
pos = scanner.pos;
if (placeholder = consumePlaceholder(scanner)) {
if (offset !== scanner.pos) {
tokens.push(text.slice(offset, pos));
}
tokens.push(placeholder);
offset = scanner.pos;
}
else {
scanner.pos++;
}
}
if (offset !== scanner.pos) {
tokens.push(text.slice(offset));
}
return tokens;
}
/**
* Consumes placeholder like `[#ID]` from given scanner
*/
function consumePlaceholder(scanner) {
if (peek$1(scanner) === TemplateChars.Start) {
const start = ++scanner.pos;
let namePos = start;
let afterPos = start;
let stack = 1;
while (scanner.pos < scanner.text.length) {
const code = peek$1(scanner);
if (isTokenStart(code)) {
namePos = scanner.pos;
while (isToken(peek$1(scanner))) {
scanner.pos++;
}
afterPos = scanner.pos;
}
else {
if (code === TemplateChars.Start) {
stack++;
}
else if (code === TemplateChars.End) {
if (--stack === 0) {
return {
before: scanner.text.slice(start, namePos),
after: scanner.text.slice(afterPos, scanner.pos++),
name: scanner.text.slice(namePos, afterPos)
};
}
}
scanner.pos++;
}
}
}
}
function peek$1(scanner, pos = scanner.pos) {
return scanner.text.charCodeAt(pos);
}
function isTokenStart(code) {
return code >= 65 && code <= 90; // A-Z
}
function isToken(code) {
return isTokenStart(code)
|| (code > 47 && code < 58) /* 0-9 */
|| code === TemplateChars.Underscore
|| code === TemplateChars.Dash;
}
function createCommentState(config) {
const { options } = config;
return {
enabled: options['comment.enabled'],
trigger: options['comment.trigger'],
before: options['comment.before'] ? template(options['comment.before']) : void 0,
after: options['comment.after'] ? template(options['comment.after']) : void 0
};
}
/**
* Adds comment prefix for given node, if required
*/
function commentNodeBefore(node, state) {
if (shouldComment(node, state) && state.comment.before) {
output(node, state.comment.before, state);
}
}
/**
* Adds comment suffix for given node, if required
*/
function commentNodeAfter(node, state) {
if (shouldComment(node, state) && state.comment.after) {
output(node, state.comment.after, state);
}
}
/**
* Check if given node should be commented
*/
function shouldComment(node, state) {
const { comment } = state;
if (!comment.enabled || !comment.trigger || !node.name || !node.attributes) {
return false;
}
for (const attr of node.attributes) {
if (attr.name && comment.trigger.includes(attr.name)) {
return true;
}
}
return false;
}
/**
* Pushes given template tokens into output stream
*/
function output(node, tokens, state) {
const attrs = {};
const { out } = state;
// Collect attributes payload
for (const attr of node.attributes) {
if (attr.name && attr.value) {
attrs[attr.name.toUpperCase()] = attr.value;
}
}
// Output parsed tokens
for (const token of tokens) {
if (typeof token === 'string') {
pushString(out, token);
}
else if (attrs[token.name]) {
pushString(out, token.before);
pushTokens(attrs[token.name], state);
pushString(out, token.after);
}
}
}
const htmlTagRegex = /^<([\w\-:]+)[\s>]/;
const reservedKeywords = new Set([
'for', 'while', 'of', 'async', 'await', 'const', 'let', 'var', 'continue',
'break', 'debugger', 'do', 'export', 'import', 'in', 'instanceof', 'new', 'return',
'switch', 'this', 'throw', 'try', 'catch', 'typeof', 'void', 'with', 'yield'
]);
function html(abbr, config) {
const state = createWalkState(config);
state.comment = createCommentState(config);
walk(abbr, element$1, state);
return state.out.value;
}
/**
* Outputs `node` content to output stream of `state`
* @param node Context node
* @param index Index of `node` in `items`
* @param items List of `node`s siblings
* @param state Current walk state
*/
function element$1(node, index, items, state, next) {
const { out, config } = state;
const format = shouldFormat$1(node, index, items, state);
// Pick offset level for current node
const level = getIndent(state);
out.level += level;
format && pushNewline(out, true);
if (node.name) {
const name = tagName(node.name, config);
commentNodeBefore(node, state);
pushString(out, `<${name}`);
if (node.attributes) {
for (const attr of node.attributes) {
if (shouldOutputAttribute(attr)) {
pushAttribute(attr, state);
}
}
}
if (node.selfClosing && !node.children.length && !node.value) {
pushString(out, `${selfClose(config)}>`);
}
else {
pushString(out, '>');
if (!pushSnippet(node, state, next)) {
if (node.value) {
const innerFormat = node.value.some(hasNewline) || startsWithBlockTag(node.value, config);
innerFormat && pushNewline(state.out, ++out.level);
pushTokens(node.value, state);
innerFormat && pushNewline(state.out, --out.level);
}
node.children.forEach(next);
if (!node.value && !node.children.length) {
const innerFormat = config.options['output.formatLeafNode']
|| config.options['output.formatForce'].includes(node.name);
innerFormat && pushNewline(state.out, ++out.level);
pushTokens(caret, state);
innerFormat && pushNewline(state.out, --out.level);
}
}
pushString(out, `</${name}>`);
commentNodeAfter(node, state);
}
}
else if (!pushSnippet(node, state, next) && node.value) {
// A text-only node (snippet)
pushTokens(node.value, state);
node.children.forEach(next);
}
if (format && index === items.length - 1 && state.parent) {
const offset = isSnippet(state.parent) ? 0 : 1;
pushNewline(out, out.level - offset);
}
out.level -= level;
}
/**
* Outputs given attributes content into output stream
*/
function pushAttribute(attr, state) {
const { out, config } = state;
if (attr.name) {
const attributes = config.options['markup.attributes'];
const valuePrefix = config.options['markup.valuePrefix'];
let { name, value } = attr;
let lQuote = attrQuote(attr, config, true);
let rQuote = attrQuote(attr, config);
if (attributes) {
name = getMultiValue(name, attributes, attr.multiple) || name;
}
name = attrName(name, config);
if (config.options['jsx.enabled'] && attr.multiple) {
lQuote = expressionStart;
rQuote = expressionEnd;
}
const prefix = valuePrefix
? getMultiValue(attr.name, valuePrefix, attr.multiple)
: null;
if (prefix && (value === null || value === void 0 ? void 0 : value.length) === 1 && typeof value[0] === 'string') {
// Add given prefix in object notation
const val = value[0];
value = [isPropKey(val) ? `${prefix}.${val}` : `${prefix}['${val}']`];
if (config.options['jsx.enabled']) {
lQuote = expressionStart;
rQuote = expressionEnd;
}
}
if (isBooleanAttribute(attr, config) && !value) {
// If attribute value is omitted and its a boolean value, check for
// `compactBoolean` option: if its disabled, set value to attribute name
// (XML style)
if (!config.options['output.compactBoolean']) {
value = [name];
}
}
else if (!value) {
value = caret;
}
pushString(out, ' ' + name);
if (value) {
pushString(out, '=' + lQuote);
pushTokens(value, state);
pushString(out, rQuote);
}
else if (config.options['output.selfClosingStyle'] !== 'html') {
pushString(out, '=' + lQuote + rQuote);
}
}
}
function pushSnippet(node, state, next) {
if (node.value && node.children.length) {
// We have a value and child nodes. In case if value contains fields,
// we should output children as a content of first field
const fieldIx = node.value.findIndex(isField);
if (fieldIx !== -1) {
pushTokens(node.value.slice(0, fieldIx), state);
const line = state.out.line;
let pos = fieldIx + 1;
node.children.forEach(next);
// If there was a line change, trim leading whitespace for better result
if (state.out.line !== line && typeof node.value[pos] === 'string') {
pushString(state.out, node.value[pos++].trimLeft());
}
pushTokens(node.value.slice(pos), state);
return true;
}
}
return false;
}
/**
* Check if given node should be formatted in its parent context
*/
function shouldFormat$1(node, index, items, state) {
const { config, parent } = state;
if (!config.options['output.format']) {
return false;
}
if (index === 0 && !parent) {
// Do not format very first node
return false;
}
// Do not format single child of snippet
if (parent && isSnippet(parent) && items.length === 1) {
return false;
}
/**
* Adjacent text-only/snippet nodes
*/
if (isSnippet(node)) {
// Adjacent text-only/snippet nodes
const format = isSnippet(items[index - 1]) || isSnippet(items[index + 1])
// Has newlines: looks like wrapping code fragment
|| node.value.some(hasNewline)
// Format as wrapper: contains children which will be outputted as field content
|| (node.value.some(isField) && node.children.length);
if (format) {
return true;
}
}
if (isInline(node, config)) {
// Check if inline node is the next sibling of block-level node
if (index === 0) {
// First node in parent: format if its followed by a block-level element
for (let i = 0; i < items.length; i++) {
if (!isInline(items[i], config)) {
return true;
}
}
}
else if (!isInline(items[index - 1], config)) {
// Node is right after block-level element
return true;
}
if (config.options['output.inlineBreak']) {
// check for adjacent inline elements before and after current element
let adjacentInline = 1;
let before = index;
let after = index;
while (isInlineElement(items[--before], config)) {
adjacentInline++;
}
while (isInlineElement(items[++after], config)) {
adjacentInline++;
}
if (adjacentInline >= config.options['output.inlineBreak']) {
return true;
}
}
// Edge case: inline node contains node that should receive formatting
for (let i = 0, il = node.children.length; i < il; i++) {
if (shouldFormat$1(node.children[i], i, node.children, state)) {
return true;
}
}
return false;
}
return true;
}
/**
* Returns indentation offset for given node
*/
function getIndent(state) {
const { config, parent } = state;
if (!parent || isSnippet(parent) || (parent.name && config.options['output.formatSkip'].includes(parent.name))) {
return 0;
}
return 1;
}
/**
* Check if given node value contains newlines
*/
function hasNewline(value) {
return typeof value === 'string' && /\r|\n/.test(value);
}
/**
* Check if given node value starts with block-level tag
*/
function startsWithBlockTag(value, config) {
if (value.length && typeof value[0] === 'string') {
const matches = htmlTagRegex.exec(value[0]);
if ((matches === null || matches === void 0 ? void 0 : matches.length) && !config.options['inlineElements'].includes(matches[1].toLowerCase())) {
return true;
}
}
return false;
}
function getMultiValue(key, data, multiple) {
return (multiple && data[`${key}*`]) || data[key];
}
function isPropKey(name) {
return !reservedKeywords.has(name) && /^[a-zA-Z_$][\w_$]*$/.test(name);
}
function indentFormat(abbr, config, options) {
const state = createWalkState(config);
state.options = options || {};
walk(abbr, element, state);
return state.out.value;
}
/**
* Outputs `node` content to output stream of `state`
* @param node Context node
* @param index Index of `node` in `items`
* @param items List of `node`s siblings
* @param state Current walk state
*/
function element(node, index, items, state, next) {
const { out, options } = state;
const { primary, secondary } = collectAttributes(node);
// Pick offset level for current node
const level = state.parent ? 1 : 0;
out.level += level;
// Do not indent top-level elements
if (shouldFormat(node, index, items, state)) {
pushNewline(out, true);
}
if (node.name && (node.name !== 'div' || !primary.length)) {
pushString(out, (options.beforeName || '') + node.name + (options.afterName || ''));
}
pushPrimaryAttributes(primary, state);
pushSecondaryAttributes(secondary.filter(shouldOutputAttribute), state);
if (node.selfClosing && !node.value && !node.children.length) {
if (state.options.selfClose) {
pushString(out, state.options.selfClose);
}
}
else {
pushValue(node, state);
node.children.forEach(next);
}
out.level -= level;
}
/**
* From given node, collects all attributes as `primary` (id, class) and
* `secondary` (all the rest) lists. In most indent-based syntaxes, primary attribute
* has special syntax
*/
function collectAttributes(node) {
const primary = [];
const secondary = [];
if (node.attributes) {
for (const attr of node.attributes) {
if (isPrimaryAttribute(attr)) {
primary.push(attr);
}
else {
secondary.push(attr);
}
}
}
return { primary, secondary };
}
/**
* Outputs given attributes as primary into output stream
*/
function pushPrimaryAttributes(attrs, state) {
for (const attr of attrs) {
if (attr.value) {
if (attr.name === 'class') {
pushString(state.out, '.');
// All whitespace characters must be replaced with dots in class names
const tokens = attr.value.map(t => typeof t === 'string' ? t.replace(/\s+/g, '.') : t);
pushTokens(tokens, state);
}
else {
// ID attribute
pushString(state.out, '#');
pushTokens(attr.value, state);
}
}
}
}
/**
* Outputs given attributes as secondary into output stream
*/
function pushSecondaryAttributes(attrs, state) {
if (attrs.length) {
const { out, config, options } = state;
options.beforeAttribute && pushString(out, options.beforeAttribute);
for (let i = 0; i < attrs.length; i++) {
const attr = attrs[i];
pushString(out, attrName(attr.name || '', config));
if (isBooleanAttribute(attr, config) && !attr.value) {
if (!config.options['output.compactBoolean'] && options.booleanValue) {
pushString(out, '=' + options.booleanValue);
}
}
else {
pushString(out, '=' + attrQuote(attr, config, true));
pushTokens(attr.value || caret, state);
pushString(out, attrQuote(attr, config));
}
if (i !== attrs.length - 1 && options.glueAttribute) {
pushString(out, options.glueAttribute);
}
}
options.afterAttribute && pushString(out, options.afterAttribute);
}
}
/**
* Outputs given node value into state output stream
*/
function pushValue(node, state) {
// We should either output value or add caret but for leaf nodes only (no children)
if (!node.value && node.children.length) {
return;
}
const value = node.value || caret;
const lines = splitByLines(value);
const { out, options } = state;
if (lines.length === 1) {
if (node.name || node.attributes) {
push(out, ' ');
}
pushTokens(value, state);
}
else {
// We should format multi-line value with terminating `|` character
// and same line length
const lineLengths = [];
let maxLength = 0;
// Calculate lengths of all lines and max line length
for (const line of lines) {
const len = valueLength(line);
lineLengths.push(len);
if (len > maxLength) {
maxLength = len;
}
}
// Output each line, padded to max length
out.level++;
for (let i = 0; i < lines.length; i++) {
pushNewline(out, true);
options.beforeTextLine && push(out, options.beforeTextLine);
pushTokens(lines[i], state);
if (options.afterTextLine) {
push(out, ' '.repeat(maxLength - lineLengths[i]));
push(out, options.afterTextLine);
}
}
out.level--;
}
}
function isPrimaryAttribute(attr) {
return attr.name === 'class' || attr.name === 'id';
}
/**
* Calculates string length from given tokens
*/
function valueLength(tokens) {
let len = 0;
for (const token of tokens) {
len += typeof token === 'string' ? token.length : token.name.length;
}
return len;
}
function shouldFormat(node, index, items, state) {
// Do not format first top-level element or snippets
if (!state.parent && index === 0) {
return false;
}
return !isSnippet(node);
}
function haml(abbr, config) {
return indentFormat(abbr, config, {
beforeName: '%',
beforeAttribute: '(',
afterAttribute: ')',
glueAttribute: ' ',
afterTextLine: ' |',
booleanValue: 'true',
selfClose: '/'
});
}
function slim(abbr, config) {
return indentFormat(abbr, config, {
beforeAttribute: ' ',
glueAttribute: ' ',
beforeTextLine: '| ',
selfClose: '/'
});
}
function pug(abbr, config) {
return indentFormat(abbr, config, {
beforeAttribute: '(',
afterAttribute: ')',
glueAttribute: ', ',
beforeTextLine: '| ',
selfClose: config.options['output.selfClosingStyle'] === 'xml' ? '/' : ''
});
}
const formatters = { html, haml, slim, pug };
/**
* Parses given Emmet abbreviation into a final abbreviation tree with all
* required transformations applied
*/
function parse$1(abbr, config) {
let oldTextValue;
if (typeof abbr === 'string') {
const parseOpt = Object.assign({}, config);
if (config.options['jsx.enabled']) {
parseOpt.jsx = true;
}
if (config.options['markup.href']) {
parseOpt.href = true;
}
abbr = parseAbbreviation(abbr, parseOpt);
// remove text field before snippets(abbr, config) call
// as abbreviation(abbr, parseOpt) already handled it
oldTextValue = config.text;
config.text = undefined;
}
// Run abbreviation resolve in two passes:
// 1. Map each node to snippets, which are abbreviations as well. A single snippet
// may produce multiple nodes
// 2. Transform every resolved node
abbr = resolveSnippets(abbr, config);
walk$1(abbr, transform, config);
config.text = oldTextValue !== null && oldTextValue !== void 0 ? oldTextValue : config.text;
return abbr;
}
/**
* Converts given abbreviation to string according to provided `config`
*/
function stringify(abbr, config) {
const formatter = formatters[config.syntax] || html;
return formatter(abbr, config);
}
/**
* Modifies given node and prepares it for output
*/
function transform(node, ancestors, config) {
implicitTag(node, ancestors, config);
mergeAttributes(node, config);
lorem(node, ancestors, config);
if (config.syntax === 'xsl') {
xsl(node);
}
if (config.options['bem.enabled']) {
bem(node, ancestors, config);
}
}
var CSSSnippetType;
(function (CSSSnippetType) {
CSSSnippetType["Raw"] = "Raw";
CSSSnippetType["Property"] = "Property";
})(CSSSnippetType || (CSSSnippetType = {}));
const reProperty = /^([a-z-]+)(?:\s*:\s*([^\n\r;]+?);*)?$/;
const opt = { value: true };
/**
* Creates structure for holding resolved CSS snippet
*/
function createSnippet(key, value) {
// A snippet could be a raw text snippet (e.g. arbitrary text string) or a
// CSS property with possible values separated by `|`.
// In latter case, we have to parse snippet as CSS abbreviation
const m = value.match(reProperty);
if (m) {
const keywords = {};
const parsed = m[2] ? m[2].split('|').map(parseValue) : [];
for (const item of parsed) {
for (const cssVal of item) {
collectKeywords(cssVal, keywords);
}
}
return {
type: CSSSnippetType.Property,
key,
property: m[1],
value: parsed,
keywords,
dependencies: []
};
}
return { type: CSSSnippetType.Raw, key, value };
}
/**
* Nests more specific CSS properties into shorthand ones, e.g.
* `background-position-x` -> `background-position` -> `background`
*/
function nest(snippets) {
snippets = snippets.slice().sort(snippetsSort);
const stack = [];
let prev;
// For sorted list of CSS properties, create dependency graph where each
// shorthand property contains its more specific one, e.g.
// background -> background-position -> background-position-x
for (const cur of snippets.filter(isProperty)) {
// Check if current property belongs to one from parent stack.
// Since `snippets` array is sorted, items are perfectly aligned
// from shorthands to more specific variants
while (stack.length) {
prev = stack[stack.length - 1];
if (cur.property.startsWith(prev.property)
&& cur.property.charCodeAt(prev.property.length) === 45 /* - */) {
prev.dependencies.push(cur);
stack.push(cur);
break;
}
stack.pop();
}
if (!stack.length) {
stack.push(cur);
}
}
return snippets;
}
/**
* A sorting function for array of snippets
*/
function snippetsSort(a, b) {
if (a.key === b.key) {
return 0;
}
return a.key < b.key ? -1 : 1;
}
function parseValue(value) {
return parse$2(value.trim(), opt)[0].value;
}
function isProperty(snippet) {
return snippet.type === CSSSnippetType.Property;
}
function collectKeywords(cssVal, dest) {
for (const v of cssVal.value) {
if (v.type === 'Literal') {
dest[v.value] = v;
}
else if (v.type === 'FunctionCall') {
dest[v.name] = v;
}
else if (v.type === 'Field') {
// Create literal from field, if available
const value = v.name.trim();
if (value) {
dest[value] = { type: 'Literal', value };
}
}
}
}
/**
* Calculates how close `str1` matches `str2` using fuzzy match.
* How matching works:
* first characters of both `str1` and `str2` *must* match
* `str1` length larger than `str2` length is allowed only when `unmatched` is true
* ideal match is when `str1` equals to `str2` (score: 1)
* next best match is `str2` starts with `str1` (score: 1 × percent of matched characters)
* other scores depend on how close characters of `str1` to the beginning of `str2`
* @param partialMatch Allow length `str1` to be greater than `str2` length
*/
function scoreMatch(str1, str2, partialMatch = false) {
str1 = str1.toLowerCase();
str2 = str2.toLowerCase();
if (str1 === str2) {
return 1;
}
// Both strings MUST start with the same character
if (!str1 || !str2 || str1.charCodeAt(0) !== str2.charCodeAt(0)) {
return 0;
}
const str1Len = str1.length;
const str2Len = str2.length;
if (!partialMatch && str1Len > str2Len) {
return 0;
}
// Characters from `str1` which are closer to the beginning of a `str2` should
// have higher score.
// For example, if `str2` is `abcde`, its max score is:
// 5 + 4 + 3 + 2 + 1 = 15 (sum of character positions in reverse order)
// Matching `abd` against `abcde` should produce:
// 5 + 4 + 2 = 11
// Acronym bonus for match right after `-`. Matching `abd` against `abc-de`
// should produce:
// 6 + 5 + 4 (use `d` position in `abd`, not in abc-de`)
const minLength = Math.min(str1Len, str2Len);
const maxLength = Math.max(str1Len, str2Len);
let i = 1;
let j = 1;
let score = maxLength;
let ch1 = 0;
let ch2 = 0;
let found = false;
let acronym = false;
while (i < str1Len) {
ch1 = str1.charCodeAt(i);
found = false;
acronym = false;
while (j < str2Len) {
ch2 = str2.charCodeAt(j);
if (ch1 === ch2) {
found = true;
score += maxLength - (acronym ? i : j);
break;
}
// add acronym bonus for exactly next match after unmatched `-`
acronym = ch2 === 45 /* - */;
j++;
}
if (!found) {
if (!partialMatch) {
return 0;
}
break;
}
i++;
}
const matchRatio = i / maxLength;
const delta = maxLength - minLength;
const maxScore = sum(maxLength) - sum(delta);
return (score * matchRatio) / maxScore;
}
/**
* Calculates sum of first `n` numbers, e.g. 1+2+3+...n
*/
function sum(n) {
return n * (n + 1) / 2;
}
function color(token, shortHex) {
if (!token.r && !token.g && !token.b && !token.a) {
return 'transparent';
}
else if (token.a === 1) {
return asHex(token, shortHex);
}
return asRGB(token);
}
/**
* Output given color as hex value
* @param short Produce short value (e.g. #fff instead of #ffffff), if possible
*/
function asHex(token, short) {
const fn = (short && isShortHex(token.r) && isShortHex(token.g) && isShortHex(token.b))
? toShortHex : toHex;
return '#' + fn(token.r) + fn(token.g) + fn(token.b);
}
/**
* Output current color as `rgba?(...)` CSS color
*/
function asRGB(token) {
const values = [token.r, token.g, token.b];
if (token.a !== 1) {
values.push(frac(token.a, 8));
}
return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`;
}
function frac(num, digits = 4) {
return num.toFixed(digits).replace(/\.?0+$/, '');
}
function isShortHex(hex) {
return !(hex % 17);
}
function toShortHex(num) {
return (num >> 4).toString(16);
}
function toHex(num) {
return pad(num.toString(16), 2);
}
function pad(value, len) {
while (value.length < len) {
value = '0' + value;
}
return value;
}
const CSSAbbreviationScope = {
/** Include all possible snippets in match */
Global: '@@global',
/** Include raw snippets only (e.g. no properties) in abbreviation match */
Section: '@@section',
/** Include properties only in abbreviation match */
Property: '@@property',
/** Resolve abbreviation in context of CSS property value */
Value: '@@value',
};
function css(abbr, config) {
var _a;
const out = createOutputStream(config.options);
const format = config.options['output.format'];
if (((_a = config.context) === null || _a === void 0 ? void 0 : _a.name) === CSSAbbreviationScope.Section) {
// For section context, filter out unmatched snippets
abbr = abbr.filter(node => node.snippet);
}
for (let i = 0; i < abbr.length; i++) {
if (format && i !== 0) {
pushNewline(out, true);
}
property(abbr[i], out, config);
}
return out.value;
}
/**
* Outputs given abbreviation node into output stream
*/
function property(node, out, config) {
const isJSON = config.options['stylesheet.json'];
if (node.name) {
// Its a CSS property
const name = isJSON ? toCamelCase(node.name) : node.name;
pushString(out, name + config.options['stylesheet.between']);
if (node.value.length) {
propertyValue(node, out, config);
}
else {
pushField(out, 0, '');
}
if (isJSON) {
// For CSS-in-JS, always finalize property with comma
// NB: seems like `important` is not available in CSS-in-JS syntaxes
push(out, ',');
}
else {
outputImportant(node, out, true);
push(out, config.options['stylesheet.after']);
}
}
else {
// Its a regular snippet, output plain tokens without any additional formatting
for (const cssVal of node.value) {
for (const v of cssVal.value) {
outputToken(v, out, config);
}
}
outputImportant(node, out, node.value.length > 0);
}
}
function propertyValue(node, out, config) {
const isJSON = config.options['stylesheet.json'];
const num = isJSON ? getSingleNumeric(node) : null;
if (num && (!num.unit || num.unit === 'px')) {
// For CSS-in-JS, if property contains single numeric value, output it
// as JS number
push(out, String(num.value));
}
else {
const quote = getQuote(config);
isJSON && push(out, quote);
for (let i = 0; i < node.value.length; i++) {
if (i !== 0) {
push(out, ', ');
}
outputValue(node.value[i], out, config);
}
isJSON && push(out, quote);
}
}
function outputImportant(node, out, separator) {
if (node.important) {
if (separator) {
push(out, ' ');
}
push(out, '!important');
}
}
function outputValue(value, out, config) {
for (let i = 0, prevEnd = -1; i < value.value.length; i++) {
const token = value.value[i];
// Handle edge case: a field is written close to previous token like this: `foo${bar}`.
// We should not add delimiter here
if (i !== 0 && (token.type !== 'Field' || token.start !== prevEnd)) {
push(out, ' ');
}
outputToken(token, out, config);
prevEnd = token['end'];
}
}
function outputToken(token, out, config) {
if (token.type === 'ColorValue') {
push(out, color(token, config.options['stylesheet.shortHex']));
}
else if (token.type === 'Literal' || token.type === 'CustomProperty') {
pushString(out, token.value);
}
else if (token.type === 'NumberValue') {
pushString(out, frac(token.value, 4) + token.unit);
}
else if (token.type === 'StringValue') {
const quote = token.quote === 'double' ? '"' : '\'';
pushString(out, quote + token.value + quote);
}
else if (token.type === 'Field') {
pushField(out, token.index, token.name);
}
else if (token.type === 'FunctionCall') {
push(out, token.name + '(');
for (let i = 0; i < token.arguments.length; i++) {
if (i) {
push(out, ', ');
}
outputValue(token.arguments[i], out, config);
}
push(out, ')');
}
}
/**
* If value of given property is a single numeric value, returns this token
*/
function getSingleNumeric(node) {
if (node.value.length === 1) {
const cssVal = node.value[0];
if (cssVal.value.length === 1 && cssVal.value[0].type === 'NumberValue') {
return cssVal.value[0];
}
}
}
/**
* Converts kebab-case string to camelCase
*/
function toCamelCase(str) {
return str.replace(/\-(\w)/g, (_, letter) => letter.toUpperCase());
}
function getQuote(config) {
return config.options['stylesheet.jsonDoubleQuotes'] ? '"' : '\'';
}
const gradientName = 'lg';
/**
* Parses given Emmet abbreviation into a final abbreviation tree with all
* required transformations applied
*/
function parse(abbr, config) {
var _a;
const snippets = ((_a = config.cache) === null || _a === void 0 ? void 0 : _a.stylesheetSnippets) || convertSnippets(config.snippets);
if (config.cache) {
config.cache.stylesheetSnippets = snippets;
}
if (typeof abbr === 'string') {
abbr = parse$2(abbr, { value: isValueScope(config) });
}
const filteredSnippets = getSnippetsForScope(snippets, config);
for (const node of abbr) {
resolveNode(node, filteredSnippets, config);
}
return abbr;
}
/**
* Converts given raw snippets into internal snippets representation
*/
function convertSnippets(snippets) {
const result = [];
for (const key of Object.keys(snippets)) {
result.push(createSnippet(key, snippets[key]));
}
return nest(result);
}
/**
* Resolves given node: finds matched CSS snippets using fuzzy match and resolves
* keyword aliases from node value
*/
function resolveNode(node, snippets, config) {
if (!resolveGradient(node, config)) {
const score = config.options['stylesheet.fuzzySearchMinScore'];
if (isValueScope(config)) {
// Resolve as value of given CSS property
const propName = config.context.name;
const snippet = snippets.find(s => s.type === CSSSnippetType.Property && s.property === propName);
resolveValueKeywords(node, config, snippet, score);
node.snippet = snippet;
}
else if (node.name) {
const snippet = findBestMatch(node.name, snippets, score, true);
node.snippet = snippet;
if (snippet) {
if (snippet.type === CSSSnippetType.Property) {
resolveAsProperty(node, snippet, config);
}
else {
resolveAsSnippet(node, snippet);
}
}
}
}
if (node.name || config.context) {
// Resolve numeric values for CSS properties only
resolveNumericValue(node, config);
}
return node;
}
/**
* Resolves CSS gradient shortcut from given property, if possible
*/
function resolveGradient(node, config) {
let gradientFn = null;
const cssVal = node.value.length === 1 ? node.value[0] : null;
if (cssVal && cssVal.value.length === 1) {
const v = cssVal.value[0];
if (v.type === 'FunctionCall' && v.name === gradientName) {
gradientFn = v;
}
}
if (gradientFn || node.name === gradientName) {
if (!gradientFn) {
gradientFn = {
type: 'FunctionCall',
name: 'linear-gradient',
arguments: [cssValue(field(0, ''))]
};
}
else {
gradientFn = Object.assign(Object.assign({}, gradientFn), { name: 'linear-gradient' });
}
if (!config.context) {
node.name = 'background-image';
}
node.value = [cssValue(gradientFn)];
return true;
}
return false;
}
/**
* Resolves given parsed abbreviation node as CSS property
*/
function resolveAsProperty(node, snippet, config) {
const abbr = node.name;
// Check for unmatched part of abbreviation
// For example, in `dib` abbreviation the matched part is `d` and `ib` should
// be considered as inline value. If unmatched fragment exists, we should check
// if it matches actual value of snippet. If either explicit value is specified
// or unmatched fragment did not resolve to to a keyword, we should consider
// matched snippet as invalid
const inlineValue = getUnmatchedPart(abbr, snippet.key);
if (inlineValue) {
if (node.value.length) {
// Already have value: unmatched part indicates matched snippet is invalid
return node;
}
const kw = resolveKeyword(inlineValue, config, snippet);
if (!kw) {
return node;
}
node.value.push(cssValue(kw));
}
node.name = snippet.property;
if (node.value.length) {
// Replace keyword alias from current abbreviation node with matched keyword
resolveValueKeywords(node, config, snippet);
}
else if (snippet.value.length) {
const defaultValue = snippet.value[0];
// https://github.com/emmetio/emmet/issues/558
// We should auto-select inserted value only if theres multiple value
// choice
node.value = snippet.value.length === 1 || defaultValue.some(hasField)
? defaultValue
: defaultValue.map(n => wrapWithField(n, config));
}
return node;
}
function resolveValueKeywords(node, config, snippet, minScore) {
for (const cssVal of node.value) {
const value = [];
for (const token of cssVal.value) {
if (token.type === 'Literal') {
value.push(resolveKeyword(token.value, config, snippet, minScore) || token);
}
else if (token.type === 'FunctionCall') {
// For function calls, we should find matching function call
// and merge arguments
const match = resolveKeyword(token.name, config, snippet, minScore);
if (match && match.type === 'FunctionCall') {
value.push(Object.assign(Object.assign({}, match), { arguments: token.arguments.concat(match.arguments.slice(token.arguments.length)) }));
}
else {
value.push(token);
}
}
else {
value.push(token);
}
}
cssVal.value = value;
}
}
/**
* Resolves given parsed abbreviation node as a snippet: a plain code chunk
*/
function resolveAsSnippet(node, snippet) {
// When resolving snippets, we have to do the following:
// 1. Replace field placeholders with actual field tokens.
// 2. If input values given, put them instead of fields
let offset = 0;
let m;
const reField = /\$\{(\d+)(:[^}]+)?\}/g;
const inputValue = node.value[0];
const outputValue = [];
while (m = reField.exec(snippet.value)) {
if (offset !== m.index) {
outputValue.push(literal(snippet.value.slice(offset, m.index)));
}
offset = m.index + m[0].length;
if (inputValue && inputValue.value.length) {
outputValue.push(inputValue.value.shift());
}
else {
outputValue.push(field(Number(m[1]), m[2] ? m[2].slice(1) : ''));
}
}
const tail = snippet.value.slice(offset);
if (tail) {
outputValue.push(literal(tail));
}
node.name = void 0;
node.value = [cssValue(...outputValue)];
return node;
}
/**
* Finds best matching item from `items` array
* @param abbr Abbreviation to match
* @param items List of items for match
* @param minScore The minimum score the best matched item should have to be a valid match.
*/
function findBestMatch(abbr, items, minScore = 0, partialMatch = false) {
let matchedItem = null;
let maxScore = 0;
for (const item of items) {
const score = scoreMatch(abbr, getScoringPart(item), partialMatch);
if (score === 1) {
// direct hit, no need to look further
return item;
}
if (score && score >= maxScore) {
maxScore = score;
matchedItem = item;
}
}
return maxScore >= minScore ? matchedItem : null;
}
function getScoringPart(item) {
return typeof item === 'string' ? item : item.key;
}
/**
* Returns a part of `abbr` that wasnt directly matched against `str`.
* For example, if abbreviation `poas` is matched against `position`,
* the unmatched part will be `as` since `a` wasnt found in string stream
*/
function getUnmatchedPart(abbr, str) {
for (let i = 0, lastPos = 0; i < abbr.length; i++) {
lastPos = str.indexOf(abbr[i], lastPos);
if (lastPos === -1) {
return abbr.slice(i);
}
lastPos++;
}
return '';
}
/**
* Resolves given keyword shorthand into matched snippet keyword or global keyword,
* if possible
*/
function resolveKeyword(kw, config, snippet, minScore) {
let ref;
if (snippet) {
if (ref = findBestMatch(kw, Object.keys(snippet.keywords), minScore)) {
return snippet.keywords[ref];
}
for (const dep of snippet.dependencies) {
if (ref = findBestMatch(kw, Object.keys(dep.keywords), minScore)) {
return dep.keywords[ref];
}
}
}
if (ref = findBestMatch(kw, config.options['stylesheet.keywords'], minScore)) {
return literal(ref);
}
return null;
}
/**
* Resolves numeric values in given abbreviation node
*/
function resolveNumericValue(node, config) {
const aliases = config.options['stylesheet.unitAliases'];
const unitless = config.options['stylesheet.unitless'];
for (const v of node.value) {
for (const t of v.value) {
if (t.type === 'NumberValue') {
if (t.unit) {
t.unit = aliases[t.unit] || t.unit;
}
else if (t.value !== 0 && !unitless.includes(node.name)) {
t.unit = t.rawValue.includes('.')
? config.options['stylesheet.floatUnit']
: config.options['stylesheet.intUnit'];
}
}
}
}
}
/**
* Constructs CSS value token
*/
function cssValue(...args) {
return {
type: 'CSSValue',
value: args
};
}
/**
* Constructs literal token
*/
function literal(value) {
return { type: 'Literal', value };
}
/**
* Constructs field token
*/
function field(index, name) {
return { type: 'Field', index, name };
}
/**
* Check if given value contains fields
*/
function hasField(value) {
for (const v of value.value) {
if (v.type === 'Field' || (v.type === 'FunctionCall' && v.arguments.some(hasField))) {
return true;
}
}
return false;
}
/**
* Wraps tokens of given abbreviation with fields
*/
function wrapWithField(node, config, state = { index: 1 }) {
let value = [];
for (const v of node.value) {
switch (v.type) {
case 'ColorValue':
value.push(field(state.index++, color(v, config.options['stylesheet.shortHex'])));
break;
case 'Literal':
value.push(field(state.index++, v.value));
break;
case 'NumberValue':
value.push(field(state.index++, `${v.value}${v.unit}`));
break;
case 'StringValue':
const q = v.quote === 'single' ? '\'' : '"';
value.push(field(state.index++, q + v.value + q));
break;
case 'FunctionCall':
value.push(field(state.index++, v.name), literal('('));
for (let i = 0, il = v.arguments.length; i < il; i++) {
value = value.concat(wrapWithField(v.arguments[i], config, state).value);
if (i !== il - 1) {
value.push(literal(', '));
}
}
value.push(literal(')'));
break;
default:
value.push(v);
}
}
return Object.assign(Object.assign({}, node), { value });
}
/**
* Check if abbreviation should be expanded in CSS value context
*/
function isValueScope(config) {
if (config.context) {
return config.context.name === CSSAbbreviationScope.Value || !config.context.name.startsWith('@@');
}
return false;
}
/**
* Returns snippets for given scope
*/
function getSnippetsForScope(snippets, config) {
if (config.context) {
if (config.context.name === CSSAbbreviationScope.Section) {
return snippets.filter(s => s.type === CSSSnippetType.Raw);
}
if (config.context.name === CSSAbbreviationScope.Property) {
return snippets.filter(s => s.type === CSSSnippetType.Property);
}
}
return snippets;
}
var markupSnippets = {
"a": "a[href]",
"a:blank": "a[href='http://${0}' target='_blank' rel='noopener noreferrer']",
"a:link": "a[href='http://${0}']",
"a:mail": "a[href='mailto:${0}']",
"a:tel": "a[href='tel:+${0}']",
"abbr": "abbr[title]",
"acr|acronym": "acronym[title]",
"base": "base[href]/",
"basefont": "basefont/",
"br": "br/",
"frame": "frame/",
"hr": "hr/",
"bdo": "bdo[dir]",
"bdo:r": "bdo[dir=rtl]",
"bdo:l": "bdo[dir=ltr]",
"col": "col/",
"link": "link[rel=stylesheet href]/",
"link:css": "link[href='${1:style}.css']",
"link:print": "link[href='${1:print}.css' media=print]",
"link:favicon": "link[rel='shortcut icon' type=image/x-icon href='${1:favicon.ico}']",
"link:mf|link:manifest": "link[rel='manifest' href='${1:manifest.json}']",
"link:touch": "link[rel=apple-touch-icon href='${1:favicon.png}']",
"link:rss": "link[rel=alternate type=application/rss+xml title=RSS href='${1:rss.xml}']",
"link:atom": "link[rel=alternate type=application/atom+xml title=Atom href='${1:atom.xml}']",
"link:im|link:import": "link[rel=import href='${1:component}.html']",
"meta": "meta/",
"meta:utf": "meta[http-equiv=Content-Type content='text/html;charset=UTF-8']",
"meta:vp": "meta[name=viewport content='width=${1:device-width}, initial-scale=${2:1.0}']",
"meta:compat": "meta[http-equiv=X-UA-Compatible content='${1:IE=7}']",
"meta:edge": "meta:compat[content='${1:ie=edge}']",
"meta:redirect": "meta[http-equiv=refresh content='0; url=${1:http://example.com}']",
"meta:refresh": "meta[http-equiv=refresh content='${1:5}']",
"meta:kw": "meta[name=keywords content]",
"meta:desc": "meta[name=description content]",
"style": "style",
"script": "script",
"script:src": "script[src]",
"script:module": "script[type=module src]",
"img": "img[src alt]/",
"img:s|img:srcset": "img[srcset src alt]",
"img:z|img:sizes": "img[sizes srcset src alt]",
"picture": "picture",
"src|source": "source/",
"src:sc|source:src": "source[src type]",
"src:s|source:srcset": "source[srcset]",
"src:t|source:type": "source[srcset type='${1:image/}']",
"src:z|source:sizes": "source[sizes srcset]",
"src:m|source:media": "source[media='(${1:min-width: })' srcset]",
"src:mt|source:media:type": "source:media[type='${2:image/}']",
"src:mz|source:media:sizes": "source:media[sizes srcset]",
"src:zt|source:sizes:type": "source[sizes srcset type='${1:image/}']",
"iframe": "iframe[src frameborder=0]",
"embed": "embed[src type]/",
"object": "object[data type]",
"param": "param[name value]/",
"map": "map[name]",
"area": "area[shape coords href alt]/",
"area:d": "area[shape=default]",
"area:c": "area[shape=circle]",
"area:r": "area[shape=rect]",
"area:p": "area[shape=poly]",
"form": "form[action]",
"form:get": "form[method=get]",
"form:post": "form[method=post]",
"label": "label[for]",
"input": "input[type=${1:text}]/",
"inp": "input[name=${1} id=${1}]",
"input:h|input:hidden": "input[type=hidden name]",
"input:t|input:text": "inp[type=text]",
"input:search": "inp[type=search]",
"input:email": "inp[type=email]",
"input:url": "inp[type=url]",
"input:p|input:password": "inp[type=password]",
"input:datetime": "inp[type=datetime]",
"input:date": "inp[type=date]",
"input:datetime-local": "inp[type=datetime-local]",
"input:month": "inp[type=month]",
"input:week": "inp[type=week]",
"input:time": "inp[type=time]",
"input:tel": "inp[type=tel]",
"input:number": "inp[type=number]",
"input:color": "inp[type=color]",
"input:c|input:checkbox": "inp[type=checkbox]",
"input:r|input:radio": "inp[type=radio]",
"input:range": "inp[type=range]",
"input:f|input:file": "inp[type=file]",
"input:s|input:submit": "input[type=submit value]",
"input:i|input:image": "input[type=image src alt]",
"input:b|input:btn|input:button": "input[type=button value]",
"input:reset": "input:button[type=reset]",
"isindex": "isindex/",
"select": "select[name=${1} id=${1}]",
"select:d|select:disabled": "select[disabled.]",
"opt|option": "option[value]",
"textarea": "textarea[name=${1} id=${1} cols=${2:30} rows=${3:10}]",
"marquee": "marquee[behavior direction]",
"menu:c|menu:context": "menu[type=context]",
"menu:t|menu:toolbar": "menu[type=toolbar]",
"video": "video[src]",
"audio": "audio[src]",
"html:xml": "html[xmlns=http://www.w3.org/1999/xhtml]",
"keygen": "keygen/",
"command": "command/",
"btn:s|button:s|button:submit" : "button[type=submit]",
"btn:r|button:r|button:reset" : "button[type=reset]",
"btn:b|button:b|button:button" : "button[type=button]",
"btn:d|button:d|button:disabled" : "button[disabled.]",
"fst:d|fset:d|fieldset:d|fieldset:disabled" : "fieldset[disabled.]",
"bq": "blockquote",
"fig": "figure",
"figc": "figcaption",
"pic": "picture",
"ifr": "iframe",
"emb": "embed",
"obj": "object",
"cap": "caption",
"colg": "colgroup",
"fst": "fieldset",
"btn": "button",
"optg": "optgroup",
"tarea": "textarea",
"leg": "legend",
"sect": "section",
"art": "article",
"hdr": "header",
"ftr": "footer",
"adr": "address",
"dlg": "dialog",
"str": "strong",
"prog": "progress",
"mn": "main",
"tem": "template",
"fset": "fieldset",
"datal": "datalist",
"kg": "keygen",
"out": "output",
"det": "details",
"sum": "summary",
"cmd": "command",
"data": "data[value]",
"meter": "meter[value]",
"time": "time[datetime]",
"ri:d|ri:dpr": "img:s",
"ri:v|ri:viewport": "img:z",
"ri:a|ri:art": "pic>src:m+img",
"ri:t|ri:type": "pic>src:t+img",
"!!!": "{<!DOCTYPE html>}",
"doc": "html[lang=${lang}]>(head>meta[charset=${charset}]+meta:vp+title{${1:Document}})+body",
"!|html:5": "!!!+doc",
"c": "{<!-- ${0} -->}",
"cc:ie": "{<!--[if IE]>${0}<![endif]-->}",
"cc:noie": "{<!--[if !IE]><!-->${0}<!--<![endif]-->}"
};
var stylesheetSnippets = {
"@f": "@font-face {\n\tfont-family: ${1};\n\tsrc: url(${2});\n}",
"@ff": "@font-face {\n\tfont-family: '${1:FontName}';\n\tsrc: url('${2:FileName}.eot');\n\tsrc: url('${2:FileName}.eot?#iefix') format('embedded-opentype'),\n\t\t url('${2:FileName}.woff') format('woff'),\n\t\t url('${2:FileName}.ttf') format('truetype'),\n\t\t url('${2:FileName}.svg#${1:FontName}') format('svg');\n\tfont-style: ${3:normal};\n\tfont-weight: ${4:normal};\n}",
"@i|@import": "@import url(${0});",
"@kf": "@keyframes ${1:identifier} {\n\t${2}\n}",
"@m|@media": "@media ${1:screen} {\n\t${0}\n}",
"ac": "align-content:start|end|flex-start|flex-end|center|space-between|space-around|stretch|space-evenly",
"ai": "align-items:start|end|flex-start|flex-end|center|baseline|stretch",
"anim": "animation:${1:name} ${2:duration} ${3:timing-function} ${4:delay} ${5:iteration-count} ${6:direction} ${7:fill-mode}",
"animdel": "animation-delay:time",
"animdir": "animation-direction:normal|reverse|alternate|alternate-reverse",
"animdur": "animation-duration:${1:0}s",
"animfm": "animation-fill-mode:both|forwards|backwards",
"animic": "animation-iteration-count:1|infinite",
"animn": "animation-name",
"animps": "animation-play-state:running|paused",
"animtf": "animation-timing-function:linear|ease|ease-in|ease-out|ease-in-out|cubic-bezier(${1:0.1}, ${2:0.7}, ${3:1.0}, ${3:0.1})",
"ap": "appearance:none",
"as": "align-self:start|end|auto|flex-start|flex-end|center|baseline|stretch",
"b": "bottom",
"bd": "border:${1:1px} ${2:solid} ${3:#000}",
"bdb": "border-bottom:${1:1px} ${2:solid} ${3:#000}",
"bdbc": "border-bottom-color:${1:#000}",
"bdbi": "border-bottom-image:url(${0})",
"bdbk": "border-break:close",
"bdbli": "border-bottom-left-image:url(${0})|continue",
"bdblrs": "border-bottom-left-radius",
"bdbri": "border-bottom-right-image:url(${0})|continue",
"bdbrrs": "border-bottom-right-radius",
"bdbs": "border-bottom-style",
"bdbw": "border-bottom-width",
"bdc": "border-color:${1:#000}",
"bdci": "border-corner-image:url(${0})|continue",
"bdcl": "border-collapse:collapse|separate",
"bdf": "border-fit:repeat|clip|scale|stretch|overwrite|overflow|space",
"bdi": "border-image:url(${0})",
"bdl": "border-left:${1:1px} ${2:solid} ${3:#000}",
"bdlc": "border-left-color:${1:#000}",
"bdlen": "border-length",
"bdli": "border-left-image:url(${0})",
"bdls": "border-left-style",
"bdlw": "border-left-width",
"bdr": "border-right:${1:1px} ${2:solid} ${3:#000}",
"bdrc": "border-right-color:${1:#000}",
"bdri": "border-right-image:url(${0})",
"bdrs": "border-radius",
"bdrst": "border-right-style",
"bdrw": "border-right-width",
"bds": "border-style:none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset",
"bdsp": "border-spacing",
"bdt": "border-top:${1:1px} ${2:solid} ${3:#000}",
"bdtc": "border-top-color:${1:#000}",
"bdti": "border-top-image:url(${0})",
"bdtli": "border-top-left-image:url(${0})|continue",
"bdtlrs": "border-top-left-radius",
"bdtri": "border-top-right-image:url(${0})|continue",
"bdtrrs": "border-top-right-radius",
"bdts": "border-top-style",
"bdtw": "border-top-width",
"bdw": "border-width",
"bbs": "border-block-start",
"bbe": "border-block-end",
"bis": "border-inline-start",
"bie": "border-inline-end",
"bfv": "backface-visibility:hidden|visible",
"bg": "background:${1:#000}",
"bg:n": "background: none",
"bga": "background-attachment:fixed|scroll",
"bgbk": "background-break:bounding-box|each-box|continuous",
"bgc": "background-color:${1:#fff}",
"bgcp": "background-clip:padding-box|border-box|content-box|no-clip",
"bgi": "background-image:url(${0})",
"bgo": "background-origin:padding-box|border-box|content-box",
"bgp": "background-position:${1:0} ${2:0}",
"bgpx": "background-position-x",
"bgpy": "background-position-y",
"bgr": "background-repeat:no-repeat|repeat-x|repeat-y|space|round",
"bgsz": "background-size:contain|cover",
"bs": "block-size",
"bxsh": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:#000}|none",
"bxsz": "box-sizing:border-box|content-box|border-box",
"c": "color:${1:#000}",
"cr": "color:rgb(${1:0}, ${2:0}, ${3:0})",
"cra": "color:rgba(${1:0}, ${2:0}, ${3:0}, ${4:.5})",
"cl": "clear:both|left|right|none",
"cm": "/* ${0} */",
"cnt": "content:'${0}'|normal|open-quote|no-open-quote|close-quote|no-close-quote|attr(${0})|counter(${0})|counters(${0})",
"coi": "counter-increment",
"colm": "columns",
"colmc": "column-count",
"colmf": "column-fill",
"colmg": "column-gap",
"colmr": "column-rule",
"colmrc": "column-rule-color",
"colmrs": "column-rule-style",
"colmrw": "column-rule-width",
"colms": "column-span",
"colmw": "column-width",
"cor": "counter-reset",
"cp": "clip:auto|rect(${1:top} ${2:right} ${3:bottom} ${4:left})",
"cps": "caption-side:top|bottom",
"cur": "cursor:pointer|auto|default|crosshair|hand|help|move|pointer|text",
"d": "display:block|none|flex|inline-flex|inline|inline-block|grid|inline-grid|subgrid|list-item|run-in|contents|table|inline-table|table-caption|table-column|table-column-group|table-header-group|table-footer-group|table-row|table-row-group|table-cell|ruby|ruby-base|ruby-base-group|ruby-text|ruby-text-group",
"ec": "empty-cells:show|hide",
"f": "font:${1:1em} ${2:sans-serif}",
"fd": "font-display:auto|block|swap|fallback|optional",
"fef": "font-effect:none|engrave|emboss|outline",
"fem": "font-emphasize",
"femp": "font-emphasize-position:before|after",
"fems": "font-emphasize-style:none|accent|dot|circle|disc",
"ff": "font-family:serif|sans-serif|cursive|fantasy|monospace",
"fft": "font-family:\"Times New Roman\", Times, Baskerville, Georgia, serif",
"ffa": "font-family:Arial, \"Helvetica Neue\", Helvetica, sans-serif",
"ffv": "font-family:Verdana, Geneva, sans-serif",
"fl": "float:left|right|none",
"fs": "font-style:italic|normal|oblique",
"fsm": "font-smoothing:antialiased|subpixel-antialiased|none",
"fst": "font-stretch:normal|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded",
"fv": "font-variant:normal|small-caps",
"fvs": "font-variation-settings:normal|inherit|initial|unset",
"fw": "font-weight:normal|bold|bolder|lighter",
"fx": "flex",
"fxb": "flex-basis:fill|max-content|min-content|fit-content|content",
"fxd": "flex-direction:row|row-reverse|column|column-reverse",
"fxf": "flex-flow",
"fxg": "flex-grow",
"fxsh": "flex-shrink",
"fxw": "flex-wrap:nowrap|wrap|wrap-reverse",
"fsz": "font-size",
"fsza": "font-size-adjust",
"g": "gap",
"gtc": "grid-template-columns:repeat(${0})|minmax()",
"gtr": "grid-template-rows:repeat(${0})|minmax()",
"gta": "grid-template-areas",
"gt": "grid-template",
"gg": "grid-gap",
"gcg": "grid-column-gap",
"grg": "grid-row-gap",
"gac": "grid-auto-columns:auto|minmax()",
"gar": "grid-auto-rows:auto|minmax()",
"gaf": "grid-auto-flow:row|column|dense|inherit|initial|unset",
"gd": "grid",
"gc": "grid-column",
"gcs": "grid-column-start",
"gce": "grid-column-end",
"gr": "grid-row",
"grs": "grid-row-start",
"gre": "grid-row-end",
"ga": "grid-area",
"h": "height",
"is": "inline-size",
"jc": "justify-content:start|end|stretch|flex-start|flex-end|center|space-between|space-around|space-evenly",
"ji": "justify-items:start|end|center|stretch",
"js": "justify-self:start|end|center|stretch",
"l": "left",
"lg": "background-image:linear-gradient(${1})",
"lh": "line-height",
"lis": "list-style",
"lisi": "list-style-image",
"lisp": "list-style-position:inside|outside",
"list": "list-style-type:disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman",
"lts": "letter-spacing:normal",
"m": "margin",
"mah": "max-height",
"mar": "max-resolution",
"maw": "max-width",
"mb": "margin-bottom",
"mih": "min-height",
"mir": "min-resolution",
"miw": "min-width",
"ml": "margin-left",
"mr": "margin-right",
"mt": "margin-top",
"mbs": "margin-block-start",
"mbe": "margin-block-end",
"mis": "margin-inline-start",
"mie": "margin-inline-end",
"ol": "outline",
"olc": "outline-color:${1:#000}|invert",
"olo": "outline-offset",
"ols": "outline-style:none|dotted|dashed|solid|double|groove|ridge|inset|outset",
"olw": "outline-width:thin|medium|thick",
"op|opa": "opacity",
"ord": "order",
"ori": "orientation:landscape|portrait",
"orp": "orphans",
"ov": "overflow:hidden|visible|hidden|scroll|auto",
"ovs": "overflow-style:scrollbar|auto|scrollbar|panner|move|marquee",
"ovx": "overflow-x:hidden|visible|hidden|scroll|auto",
"ovy": "overflow-y:hidden|visible|hidden|scroll|auto",
"p": "padding",
"pb": "padding-bottom",
"pgba": "page-break-after:auto|always|left|right",
"pgbb": "page-break-before:auto|always|left|right",
"pgbi": "page-break-inside:auto|avoid",
"pl": "padding-left",
"pos": "position:relative|absolute|relative|fixed|static",
"pr": "padding-right",
"pt": "padding-top",
"pbs": "padding-block-start",
"pbe": "padding-block-end",
"pis": "padding-inline-start",
"pie": "padding-inline-end",
"spbs": "scroll-padding-block-start",
"spbe": "scroll-padding-block-end",
"spis": "scroll-padding-inline-start",
"spie": "scroll-padding-inline-end",
"q": "quotes",
"qen": "quotes:'\\201C' '\\201D' '\\2018' '\\2019'",
"qru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C'",
"r": "right",
"rsz": "resize:none|both|horizontal|vertical",
"t": "top",
"ta": "text-align:left|center|right|justify",
"tal": "text-align-last:left|center|right",
"tbl": "table-layout:fixed",
"td": "text-decoration:none|underline|overline|line-through",
"te": "text-emphasis:none|accent|dot|circle|disc|before|after",
"th": "text-height:auto|font-size|text-size|max-size",
"ti": "text-indent",
"tj": "text-justify:auto|inter-word|inter-ideograph|inter-cluster|distribute|kashida|tibetan",
"to": "text-outline:${1:0} ${2:0} ${3:#000}",
"tov": "text-overflow:ellipsis|clip",
"tr": "text-replace",
"trf": "transform:${1}|skewX(${1:angle})|skewY(${1:angle})|scale(${1:x}, ${2:y})|scaleX(${1:x})|scaleY(${1:y})|scaleZ(${1:z})|scale3d(${1:x}, ${2:y}, ${3:z})|rotate(${1:angle})|rotateX(${1:angle})|rotateY(${1:angle})|rotateZ(${1:angle})|translate(${1:x}, ${2:y})|translateX(${1:x})|translateY(${1:y})|translateZ(${1:z})|translate3d(${1:tx}, ${2:ty}, ${3:tz})",
"trfo": "transform-origin",
"trfs": "transform-style:preserve-3d",
"trs": "transition:${1:prop} ${2:time}",
"trsde": "transition-delay:${1:time}",
"trsdu": "transition-duration:${1:time}",
"trsp": "transition-property:${1:prop}",
"trstf": "transition-timing-function:${1:fn}",
"tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000}",
"tt": "text-transform:uppercase|lowercase|capitalize|none",
"tw": "text-wrap:none|normal|unrestricted|suppress",
"us": "user-select:none",
"v": "visibility:hidden|visible|collapse",
"va": "vertical-align:top|super|text-top|middle|baseline|bottom|text-bottom|sub",
"w": "width",
"whs": "white-space:nowrap|pre|pre-wrap|pre-line|normal",
"whsc": "white-space-collapse:normal|keep-all|loose|break-strict|break-all",
"wid": "widows",
"wm": "writing-mode:lr-tb|lr-tb|lr-bt|rl-tb|rl-bt|tb-rl|tb-lr|bt-lr|bt-rl",
"wob": "word-break:normal|keep-all|break-all",
"wos": "word-spacing",
"wow": "word-wrap:none|unrestricted|suppress|break-word|normal",
"z": "z-index",
"zom": "zoom:1"
};
var xslSnippets = {
"tm|tmatch": "xsl:template[match mode]",
"tn|tname": "xsl:template[name]",
"call": "xsl:call-template[name]",
"ap": "xsl:apply-templates[select mode]",
"api": "xsl:apply-imports",
"imp": "xsl:import[href]",
"inc": "xsl:include[href]",
"ch": "xsl:choose",
"wh|xsl:when": "xsl:when[test]",
"ot": "xsl:otherwise",
"if": "xsl:if[test]",
"par": "xsl:param[name]",
"pare": "xsl:param[name select]",
"var": "xsl:variable[name]",
"vare": "xsl:variable[name select]",
"wp": "xsl:with-param[name select]",
"key": "xsl:key[name match use]",
"elem": "xsl:element[name]",
"attr": "xsl:attribute[name]",
"attrs": "xsl:attribute-set[name]",
"cp": "xsl:copy[select]",
"co": "xsl:copy-of[select]",
"val": "xsl:value-of[select]",
"for|each": "xsl:for-each[select]",
"tex": "xsl:text",
"com": "xsl:comment",
"msg": "xsl:message[terminate=no]",
"fall": "xsl:fallback",
"num": "xsl:number[value]",
"nam": "namespace-alias[stylesheet-prefix result-prefix]",
"pres": "xsl:preserve-space[elements]",
"strip": "xsl:strip-space[elements]",
"proc": "xsl:processing-instruction[name]",
"sort": "xsl:sort[select order]",
"choose": "xsl:choose>xsl:when+xsl:otherwise",
"xsl": "!!!+xsl:stylesheet[version=1.0 xmlns:xsl=http://www.w3.org/1999/XSL/Transform]>{\n|}",
"!!!": "{<?xml version=\"1.0\" encoding=\"UTF-8\"?>}"
};
var pugSnippets = {
"!!!": "{doctype html}"
};
var variables = {
"lang": "en",
"locale": "en-US",
"charset": "UTF-8",
"indentation": "\t",
"newline": "\n"
};
/**
* Default syntaxes for abbreviation types
*/
const defaultSyntaxes = {
markup: 'html',
stylesheet: 'css'
};
const defaultOptions$1 = {
'inlineElements': [
'a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
'textarea', 'tt', 'u', 'var'
],
'output.indent': '\t',
'output.baseIndent': '',
'output.newline': '\n',
'output.tagCase': '',
'output.attributeCase': '',
'output.attributeQuotes': 'double',
'output.format': true,
'output.formatLeafNode': false,
'output.formatSkip': ['html'],
'output.formatForce': ['body'],
'output.inlineBreak': 3,
'output.compactBoolean': false,
'output.booleanAttributes': [
'contenteditable', 'seamless', 'async', 'autofocus',
'autoplay', 'checked', 'controls', 'defer', 'disabled', 'formnovalidate',
'hidden', 'ismap', 'loop', 'multiple', 'muted', 'novalidate', 'readonly',
'required', 'reversed', 'selected', 'typemustmatch'
],
'output.reverseAttributes': false,
'output.selfClosingStyle': 'html',
'output.field': (index, placeholder) => placeholder,
'output.text': text => text,
'markup.href': true,
'comment.enabled': false,
'comment.trigger': ['id', 'class'],
'comment.before': '',
'comment.after': '\n<!-- /[#ID][.CLASS] -->',
'bem.enabled': false,
'bem.element': '__',
'bem.modifier': '_',
'jsx.enabled': false,
'stylesheet.keywords': ['auto', 'inherit', 'unset', 'none'],
'stylesheet.unitless': ['z-index', 'line-height', 'opacity', 'font-weight', 'zoom', 'flex', 'flex-grow', 'flex-shrink'],
'stylesheet.shortHex': true,
'stylesheet.between': ': ',
'stylesheet.after': ';',
'stylesheet.intUnit': 'px',
'stylesheet.floatUnit': 'em',
'stylesheet.unitAliases': { e: 'em', p: '%', x: 'ex', r: 'rem' },
'stylesheet.json': false,
'stylesheet.jsonDoubleQuotes': false,
'stylesheet.fuzzySearchMinScore': 0
};
const defaultConfig = {
type: 'markup',
syntax: 'html',
variables,
snippets: {},
options: defaultOptions$1
};
/**
* Default per-syntax config
*/
const syntaxConfig = {
markup: {
snippets: parseSnippets(markupSnippets),
},
xhtml: {
options: {
'output.selfClosingStyle': 'xhtml'
}
},
xml: {
options: {
'output.selfClosingStyle': 'xml'
}
},
xsl: {
snippets: parseSnippets(xslSnippets),
options: {
'output.selfClosingStyle': 'xml'
}
},
jsx: {
options: {
'jsx.enabled': true,
'markup.attributes': {
'class': 'className',
'class*': 'styleName',
'for': 'htmlFor'
},
'markup.valuePrefix': {
'class*': 'styles'
}
}
},
vue: {
options: {
'markup.attributes': {
'class*': ':class',
}
}
},
svelte: {
options: {
'jsx.enabled': true
}
},
pug: {
snippets: parseSnippets(pugSnippets)
},
stylesheet: {
snippets: parseSnippets(stylesheetSnippets)
},
sass: {
options: {
'stylesheet.after': ''
}
},
stylus: {
options: {
'stylesheet.between': ' ',
'stylesheet.after': '',
}
}
};
/**
* Parses raw snippets definitions with possibly multiple keys into a plan
* snippet map
*/
function parseSnippets(snippets) {
const result = {};
Object.keys(snippets).forEach(k => {
for (const name of k.split('|')) {
result[name] = snippets[k];
}
});
return result;
}
function resolveConfig(config = {}, globals = {}) {
const type = config.type || 'markup';
const syntax = config.syntax || defaultSyntaxes[type];
return Object.assign(Object.assign(Object.assign({}, defaultConfig), config), { type,
syntax, variables: mergedData(type, syntax, 'variables', config, globals), snippets: mergedData(type, syntax, 'snippets', config, globals), options: mergedData(type, syntax, 'options', config, globals) });
}
function mergedData(type, syntax, key, config, globals = {}) {
const typeDefaults = syntaxConfig[type];
const typeOverride = globals[type];
const syntaxDefaults = syntaxConfig[syntax];
const syntaxOverride = globals[syntax];
return Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({}, defaultConfig[key]), (typeDefaults && typeDefaults[key])), (syntaxDefaults && syntaxDefaults[key])), (typeOverride && typeOverride[key])), (syntaxOverride && syntaxOverride[key])), config[key]);
}
/**
* Creates structure for scanning given string in backward direction
*/
function backwardScanner(text, start = 0) {
return { text, start, pos: text.length };
}
/**
* Check if given scanner position is at start of scanned text
*/
function sol(scanner) {
return scanner.pos === scanner.start;
}
/**
* “Peeks” character code an current scanner location without advancing it
*/
function peek(scanner, offset = 0) {
return scanner.text.charCodeAt(scanner.pos - 1 + offset);
}
/**
* Returns current character code and moves character location one symbol back
*/
function previous(scanner) {
if (!sol(scanner)) {
return scanner.text.charCodeAt(--scanner.pos);
}
}
/**
* Consumes current character code if it matches given `match` code or function
*/
function consume(scanner, match) {
if (sol(scanner)) {
return false;
}
const ok = typeof match === 'function'
? match(peek(scanner))
: match === peek(scanner);
if (ok) {
scanner.pos--;
}
return !!ok;
}
function consumeWhile(scanner, match) {
const start = scanner.pos;
while (consume(scanner, match)) {
// empty
}
return scanner.pos < start;
}
var Chars$1;
(function (Chars) {
Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
Chars[Chars["Escape"] = 92] = "Escape";
})(Chars$1 || (Chars$1 = {}));
/**
* Check if given character code is a quote
*/
function isQuote(c) {
return c === Chars$1.SingleQuote || c === Chars$1.DoubleQuote;
}
/**
* Consumes quoted value, if possible
* @return Returns `true` is value was consumed
*/
function consumeQuoted(scanner) {
const start = scanner.pos;
const quote = previous(scanner);
if (isQuote(quote)) {
while (!sol(scanner)) {
if (previous(scanner) === quote && peek(scanner) !== Chars$1.Escape) {
return true;
}
}
}
scanner.pos = start;
return false;
}
var Brackets;
(function (Brackets) {
Brackets[Brackets["SquareL"] = 91] = "SquareL";
Brackets[Brackets["SquareR"] = 93] = "SquareR";
Brackets[Brackets["RoundL"] = 40] = "RoundL";
Brackets[Brackets["RoundR"] = 41] = "RoundR";
Brackets[Brackets["CurlyL"] = 123] = "CurlyL";
Brackets[Brackets["CurlyR"] = 125] = "CurlyR";
})(Brackets || (Brackets = {}));
const bracePairs = {
[Brackets.SquareL]: Brackets.SquareR,
[Brackets.RoundL]: Brackets.RoundR,
[Brackets.CurlyL]: Brackets.CurlyR,
};
var Chars;
(function (Chars) {
Chars[Chars["Tab"] = 9] = "Tab";
Chars[Chars["Space"] = 32] = "Space";
/** `-` character */
Chars[Chars["Dash"] = 45] = "Dash";
/** `/` character */
Chars[Chars["Slash"] = 47] = "Slash";
/** `:` character */
Chars[Chars["Colon"] = 58] = "Colon";
/** `=` character */
Chars[Chars["Equals"] = 61] = "Equals";
/** `<` character */
Chars[Chars["AngleLeft"] = 60] = "AngleLeft";
/** `>` character */
Chars[Chars["AngleRight"] = 62] = "AngleRight";
})(Chars || (Chars = {}));
/**
* Check if given readers current position points at the end of HTML tag
*/
function isHtml(scanner) {
const start = scanner.pos;
if (!consume(scanner, Chars.AngleRight)) {
return false;
}
let ok = false;
consume(scanner, Chars.Slash); // possibly self-closed element
while (!sol(scanner)) {
consumeWhile(scanner, isWhiteSpace);
if (consumeIdent(scanner)) {
// ate identifier: could be a tag name, boolean attribute or unquoted
// attribute value
if (consume(scanner, Chars.Slash)) {
// either closing tag or invalid tag
ok = consume(scanner, Chars.AngleLeft);
break;
}
else if (consume(scanner, Chars.AngleLeft)) {
// opening tag
ok = true;
break;
}
else if (consume(scanner, isWhiteSpace)) {
// boolean attribute
continue;
}
else if (consume(scanner, Chars.Equals)) {
// simple unquoted value or invalid attribute
if (consumeIdent(scanner)) {
continue;
}
break;
}
else if (consumeAttributeWithUnquotedValue(scanner)) {
// identifier was a part of unquoted value
ok = true;
break;
}
// invalid tag
break;
}
if (consumeAttribute(scanner)) {
continue;
}
break;
}
scanner.pos = start;
return ok;
}
/**
* Consumes HTML attribute from given string.
* @return `true` if attribute was consumed.
*/
function consumeAttribute(scanner) {
return consumeAttributeWithQuotedValue(scanner) || consumeAttributeWithUnquotedValue(scanner);
}
function consumeAttributeWithQuotedValue(scanner) {
const start = scanner.pos;
if (consumeQuoted(scanner) && consume(scanner, Chars.Equals) && consumeIdent(scanner)) {
return true;
}
scanner.pos = start;
return false;
}
function consumeAttributeWithUnquotedValue(scanner) {
const start = scanner.pos;
const stack = [];
while (!sol(scanner)) {
const ch = peek(scanner);
if (isCloseBracket(ch)) {
stack.push(ch);
}
else if (isOpenBracket(ch)) {
if (stack.pop() !== bracePairs[ch]) {
// Unexpected open bracket
break;
}
}
else if (!isUnquotedValue(ch)) {
break;
}
scanner.pos--;
}
if (start !== scanner.pos && consume(scanner, Chars.Equals) && consumeIdent(scanner)) {
return true;
}
scanner.pos = start;
return false;
}
/**
* Consumes HTML identifier from stream
*/
function consumeIdent(scanner) {
return consumeWhile(scanner, isIdent);
}
/**
* Check if given character code belongs to HTML identifier
*/
function isIdent(ch) {
return ch === Chars.Colon || ch === Chars.Dash || isAlpha(ch) || isNumber(ch);
}
/**
* Check if given character code is alpha code (letter though A to Z)
*/
function isAlpha(ch) {
ch &= ~32; // quick hack to convert any char code to uppercase char code
return ch >= 65 && ch <= 90; // A-Z
}
/**
* Check if given code is a number
*/
function isNumber(ch) {
return ch > 47 && ch < 58;
}
/**
* Check if given code is a whitespace
*/
function isWhiteSpace(ch) {
return ch === Chars.Space || ch === Chars.Tab;
}
/**
* Check if given code may belong to unquoted attribute value
*/
function isUnquotedValue(ch) {
return !isNaN(ch) && ch !== Chars.Equals && !isWhiteSpace(ch) && !isQuote(ch);
}
function isOpenBracket(ch) {
return ch === Brackets.CurlyL || ch === Brackets.RoundL || ch === Brackets.SquareL;
}
function isCloseBracket(ch) {
return ch === Brackets.CurlyR || ch === Brackets.RoundR || ch === Brackets.SquareR;
}
const code = (ch) => ch.charCodeAt(0);
const specialChars = '#.*:$-_!@%^+>/'.split('').map(code);
const defaultOptions = {
type: 'markup',
lookAhead: true,
prefix: ''
};
/**
* Extracts Emmet abbreviation from given string.
* The goal of this module is to extract abbreviation from current editors line,
* e.g. like this: `<span>.foo[title=bar|]</span>` -> `.foo[title=bar]`, where
* `|` is a current caret position.
* @param line A text line where abbreviation should be expanded
* @param pos Caret position in line. If not given, uses end of line
* @param options Extracting options
*/
function extractAbbreviation(line, pos = line.length, options = {}) {
// make sure `pos` is within line range
const opt = Object.assign(Object.assign({}, defaultOptions), options);
pos = Math.min(line.length, Math.max(0, pos == null ? line.length : pos));
if (opt.lookAhead) {
pos = offsetPastAutoClosed(line, pos, opt);
}
let ch;
const start = getStartOffset(line, pos, opt.prefix || '');
if (start === -1) {
return void 0;
}
const scanner = backwardScanner(line, start);
scanner.pos = pos;
const stack = [];
while (!sol(scanner)) {
ch = peek(scanner);
if (stack.includes(Brackets.CurlyR)) {
if (ch === Brackets.CurlyR) {
stack.push(ch);
scanner.pos--;
continue;
}
if (ch !== Brackets.CurlyL) {
scanner.pos--;
continue;
}
}
if (isCloseBrace(ch, opt.type)) {
stack.push(ch);
}
else if (isOpenBrace(ch, opt.type)) {
if (stack.pop() !== bracePairs[ch]) {
// unexpected brace
break;
}
}
else if (stack.includes(Brackets.SquareR) || stack.includes(Brackets.CurlyR)) {
// respect all characters inside attribute sets or text nodes
scanner.pos--;
continue;
}
else if (isHtml(scanner) || !isAbbreviation(ch)) {
break;
}
scanner.pos--;
}
if (!stack.length && scanner.pos !== pos) {
// Found something, remove some invalid symbols from the
// beginning and return abbreviation
const abbreviation = line.slice(scanner.pos, pos).replace(/^[*+>^]+/, '');
return {
abbreviation,
location: pos - abbreviation.length,
start: options.prefix
? start - options.prefix.length
: pos - abbreviation.length,
end: pos
};
}
}
/**
* Returns new `line` index which is right after characters beyound `pos` that
* editor will likely automatically close, e.g. }, ], and quotes
*/
function offsetPastAutoClosed(line, pos, options) {
// closing quote is allowed only as a next character
if (isQuote(line.charCodeAt(pos))) {
pos++;
}
// offset pointer until non-autoclosed character is found
while (isCloseBrace(line.charCodeAt(pos), options.type)) {
pos++;
}
return pos;
}
/**
* Returns start offset (left limit) in `line` where we should stop looking for
* abbreviation: its nearest to `pos` location of `prefix` token
*/
function getStartOffset(line, pos, prefix) {
if (!prefix) {
return 0;
}
const scanner = backwardScanner(line);
const compiledPrefix = prefix.split('').map(code);
scanner.pos = pos;
let result;
while (!sol(scanner)) {
if (consumePair(scanner, Brackets.SquareR, Brackets.SquareL) || consumePair(scanner, Brackets.CurlyR, Brackets.CurlyL)) {
continue;
}
result = scanner.pos;
if (consumeArray(scanner, compiledPrefix)) {
return result;
}
scanner.pos--;
}
return -1;
}
/**
* Consumes full character pair, if possible
*/
function consumePair(scanner, close, open) {
const start = scanner.pos;
if (consume(scanner, close)) {
while (!sol(scanner)) {
if (consume(scanner, open)) {
return true;
}
scanner.pos--;
}
}
scanner.pos = start;
return false;
}
/**
* Consumes all character codes from given array, right-to-left, if possible
*/
function consumeArray(scanner, arr) {
const start = scanner.pos;
let consumed = false;
for (let i = arr.length - 1; i >= 0 && !sol(scanner); i--) {
if (!consume(scanner, arr[i])) {
break;
}
consumed = i === 0;
}
if (!consumed) {
scanner.pos = start;
}
return consumed;
}
function isAbbreviation(ch) {
return (ch > 64 && ch < 91) // uppercase letter
|| (ch > 96 && ch < 123) // lowercase letter
|| (ch > 47 && ch < 58) // number
|| specialChars.includes(ch); // special character
}
function isOpenBrace(ch, syntax) {
return ch === Brackets.RoundL || (syntax === 'markup' && (ch === Brackets.SquareL || ch === Brackets.CurlyL));
}
function isCloseBrace(ch, syntax) {
return ch === Brackets.RoundR || (syntax === 'markup' && (ch === Brackets.SquareR || ch === Brackets.CurlyR));
}
function expandAbbreviation(abbr, config) {
const resolvedConfig = resolveConfig(config);
return resolvedConfig.type === 'stylesheet'
? stylesheet(abbr, resolvedConfig)
: markup(abbr, resolvedConfig);
}
/**
* Expands given *markup* abbreviation (e.g. regular Emmet abbreviation that
* produces structured output like HTML) and outputs it according to options
* provided in config
*/
function markup(abbr, config) {
return stringify(parse$1(abbr, config), config);
}
/**
* Expands given *stylesheet* abbreviation (a special Emmet abbreviation designed for
* stylesheet languages like CSS, SASS etc.) and outputs it according to options
* provided in config
*/
function stylesheet(abbr, config) {
return css(parse(abbr, config), config);
}
export { CSSAbbreviationScope, expandAbbreviation as default, extractAbbreviation as extract, markup, parseAbbreviation as markupAbbreviation, parse$1 as parseMarkup, parse as parseStylesheet, convertSnippets as parseStylesheetSnippets, resolveConfig, stringify as stringifyMarkup, css as stringifyStylesheet, stylesheet, parse$2 as stylesheetAbbreviation };
//# sourceMappingURL=emmet.es.js.map