UNPKG

emmet

Version:

Emmet — the essential toolkit for web-developers

1 lines 269 kB
{"version":3,"file":"emmet.cjs","sources":["../packages/scanner/scanner.js","../packages/abbreviation/dist/index.js","../packages/css-abbreviation/dist/index.js","../src/markup/attributes.ts","../src/markup/utils.ts","../src/markup/snippets.ts","../src/output-stream.ts","../src/markup/implicit-tag.ts","../src/markup/lorem/latin.json","../src/markup/lorem/russian.json","../src/markup/lorem/spanish.json","../src/markup/lorem/index.ts","../src/markup/addon/xsl.ts","../src/markup/addon/bem.ts","../src/markup/addon/label.ts","../src/markup/format/walk.ts","../src/markup/format/utils.ts","../src/markup/format/template.ts","../src/markup/format/comment.ts","../src/markup/format/html.ts","../src/markup/format/indent-format.ts","../src/markup/format/haml.ts","../src/markup/format/slim.ts","../src/markup/format/pug.ts","../src/markup/index.ts","../src/stylesheet/snippets.ts","../src/stylesheet/score.ts","../src/stylesheet/color.ts","../src/stylesheet/format.ts","../src/stylesheet/index.ts","../src/snippets/html.json","../src/snippets/css.json","../src/snippets/xsl.json","../src/snippets/pug.json","../src/snippets/variables.json","../src/config.ts","../src/extract-abbreviation/reader.ts","../src/extract-abbreviation/quotes.ts","../src/extract-abbreviation/brackets.ts","../src/extract-abbreviation/is-html.ts","../src/extract-abbreviation/index.ts","../src/index.ts"],"sourcesContent":["const defaultQuotedOptions = {\n escape: 92,\n throws: false\n};\n/**\n * Check if given code is a number\n */\nfunction isNumber(code) {\n return code > 47 && code < 58;\n}\n/**\n * Check if given character code is alpha code (letter through A to Z)\n */\nfunction isAlpha(code, from, to) {\n from = from || 65; // A\n to = to || 90; // Z\n code &= ~32; // quick hack to convert any char code to uppercase char code\n return code >= from && code <= to;\n}\n/**\n * Check if given character code is alpha-numeric (letter through A to Z or number)\n */\nfunction isAlphaNumeric(code) {\n return isNumber(code) || isAlpha(code);\n}\nfunction isAlphaNumericWord(code) {\n return isNumber(code) || isAlphaWord(code);\n}\nfunction isAlphaWord(code) {\n return code === 95 /* _ */ || isAlpha(code);\n}\n/**\n * Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü\n */\nfunction isUmlaut(code) {\n return code === 196\n || code == 214\n || code === 220\n || code === 228\n || code === 246\n || code === 252;\n}\n/**\n * Check if given character code is a white-space character: a space character\n * or line breaks\n */\nfunction isWhiteSpace(code) {\n return code === 32 /* space */\n || code === 9 /* tab */\n || code === 160; /* non-breaking space */\n}\n/**\n * Check if given character code is a space character\n */\nfunction isSpace(code) {\n return isWhiteSpace(code)\n || code === 10 /* LF */\n || code === 13; /* CR */\n}\n/**\n * Consumes 'single' or \"double\"-quoted string from given string, if possible\n * @return `true` if quoted string was consumed. The contents of quoted string\n * will be available as `stream.current()`\n */\nfunction eatQuoted(stream, options) {\n options = Object.assign(Object.assign({}, defaultQuotedOptions), options);\n const start = stream.pos;\n const quote = stream.peek();\n if (stream.eat(isQuote)) {\n while (!stream.eof()) {\n switch (stream.next()) {\n case quote:\n stream.start = start;\n return true;\n case options.escape:\n stream.next();\n break;\n }\n }\n // If we’re here then stream wasn’t properly consumed.\n // Revert stream and decide what to do\n stream.pos = start;\n if (options.throws) {\n throw stream.error('Unable to consume quoted string');\n }\n }\n return false;\n}\n/**\n * Check if given character code is a quote character\n */\nfunction isQuote(code) {\n return code === 39 /* ' */ || code === 34 /* \" */;\n}\n/**\n * Eats paired characters substring, for example `(foo)` or `[bar]`\n * @param open Character code of pair opening\n * @param close Character code of pair closing\n * @return Returns `true` if character pair was successfully consumed, it’s\n * content will be available as `stream.current()`\n */\nfunction eatPair(stream, open, close, options) {\n options = Object.assign(Object.assign({}, defaultQuotedOptions), options);\n const start = stream.pos;\n if (stream.eat(open)) {\n let stack = 1;\n let ch;\n while (!stream.eof()) {\n if (eatQuoted(stream, options)) {\n continue;\n }\n ch = stream.next();\n if (ch === open) {\n stack++;\n }\n else if (ch === close) {\n stack--;\n if (!stack) {\n stream.start = start;\n return true;\n }\n }\n else if (ch === options.escape) {\n stream.next();\n }\n }\n // If we’re here then paired character can’t be consumed\n stream.pos = start;\n if (options.throws) {\n throw stream.error(`Unable to find matching pair for ${String.fromCharCode(open)}`);\n }\n }\n return false;\n}\n\n/**\n * A streaming, character code-based string reader\n */\nclass Scanner {\n constructor(str, start, end) {\n if (end == null && typeof str === 'string') {\n end = str.length;\n }\n this.string = str;\n this.pos = this.start = start || 0;\n this.end = end || 0;\n }\n /**\n * Returns true only if the stream is at the end of the file.\n */\n eof() {\n return this.pos >= this.end;\n }\n /**\n * Creates a new stream instance which is limited to given `start` and `end`\n * range. E.g. its `eof()` method will look at `end` property, not actual\n * stream end\n */\n limit(start, end) {\n return new Scanner(this.string, start, end);\n }\n /**\n * Returns the next character code in the stream without advancing it.\n * Will return NaN at the end of the file.\n */\n peek() {\n return this.string.charCodeAt(this.pos);\n }\n /**\n * Returns the next character in the stream and advances it.\n * Also returns <code>undefined</code> when no more characters are available.\n */\n next() {\n if (this.pos < this.string.length) {\n return this.string.charCodeAt(this.pos++);\n }\n }\n /**\n * `match` can be a character code or a function that takes a character code\n * and returns a boolean. If the next character in the stream 'matches'\n * the given argument, it is consumed and returned.\n * Otherwise, `false` is returned.\n */\n eat(match) {\n const ch = this.peek();\n const ok = typeof match === 'function' ? match(ch) : ch === match;\n if (ok) {\n this.next();\n }\n return ok;\n }\n /**\n * Repeatedly calls <code>eat</code> with the given argument, until it\n * fails. Returns <code>true</code> if any characters were eaten.\n */\n eatWhile(match) {\n const start = this.pos;\n while (!this.eof() && this.eat(match)) { /* */ }\n return this.pos !== start;\n }\n /**\n * Backs up the stream n characters. Backing it up further than the\n * start of the current token will cause things to break, so be careful.\n */\n backUp(n) {\n this.pos -= (n || 1);\n }\n /**\n * Get the string between the start of the current token and the\n * current stream position.\n */\n current() {\n return this.substring(this.start, this.pos);\n }\n /**\n * Returns substring for given range\n */\n substring(start, end) {\n return this.string.slice(start, end);\n }\n /**\n * Creates error object with current stream state\n */\n error(message, pos = this.pos) {\n return new ScannerError(`${message} at ${pos + 1}`, pos, this.string);\n }\n}\nclass ScannerError extends Error {\n constructor(message, pos, str) {\n super(message);\n this.pos = pos;\n this.string = str;\n }\n}\n\nexport { ScannerError, Scanner as default, eatPair, eatQuoted, isAlpha, isAlphaNumeric, isAlphaNumericWord, isAlphaWord, isNumber, isQuote, isSpace, isUmlaut, isWhiteSpace };\n//# sourceMappingURL=scanner.js.map\n","import Scanner, { isNumber, isQuote as isQuote$1, isSpace, isAlpha, isAlphaNumericWord, isUmlaut, ScannerError } from '@emmetio/scanner';\n\nfunction tokenScanner(tokens) {\n return {\n tokens,\n start: 0,\n pos: 0,\n size: tokens.length\n };\n}\nfunction peek(scanner) {\n return scanner.tokens[scanner.pos];\n}\nfunction next(scanner) {\n return scanner.tokens[scanner.pos++];\n}\nfunction slice(scanner, from = scanner.start, to = scanner.pos) {\n return scanner.tokens.slice(from, to);\n}\nfunction readable(scanner) {\n return scanner.pos < scanner.size;\n}\nfunction consume(scanner, test) {\n const token = peek(scanner);\n if (token && test(token)) {\n scanner.pos++;\n return true;\n }\n return false;\n}\nfunction error(scanner, message, token = peek(scanner)) {\n if (token && token.start != null) {\n message += ` at ${token.start}`;\n }\n const err = new Error(message);\n err['pos'] = token && token.start;\n return err;\n}\n\nfunction abbreviation(abbr, options = {}) {\n const scanner = tokenScanner(abbr);\n const result = statements(scanner, options);\n if (readable(scanner)) {\n throw error(scanner, 'Unexpected character');\n }\n return result;\n}\nfunction statements(scanner, options) {\n const result = {\n type: 'TokenGroup',\n elements: []\n };\n let ctx = result;\n let node;\n const stack = [];\n while (readable(scanner)) {\n if (node = element(scanner, options) || group(scanner, options)) {\n ctx.elements.push(node);\n if (consume(scanner, isChildOperator)) {\n stack.push(ctx);\n ctx = node;\n }\n else if (consume(scanner, isSiblingOperator)) {\n continue;\n }\n else if (consume(scanner, isClimbOperator)) {\n do {\n if (stack.length) {\n ctx = stack.pop();\n }\n } while (consume(scanner, isClimbOperator));\n }\n }\n else {\n break;\n }\n }\n return result;\n}\n/**\n * Consumes group from given scanner\n */\nfunction group(scanner, options) {\n if (consume(scanner, isGroupStart)) {\n const result = statements(scanner, options);\n const token = next(scanner);\n if (isBracket(token, 'group', false)) {\n result.repeat = repeater$1(scanner);\n }\n return result;\n }\n}\n/**\n * Consumes single element from given scanner\n */\nfunction element(scanner, options) {\n let attr;\n const elem = {\n type: 'TokenElement',\n name: void 0,\n attributes: void 0,\n value: void 0,\n repeat: void 0,\n selfClose: false,\n elements: []\n };\n if (elementName(scanner, options)) {\n elem.name = slice(scanner);\n }\n while (readable(scanner)) {\n scanner.start = scanner.pos;\n if (!elem.repeat && !isEmpty(elem) && consume(scanner, isRepeater)) {\n elem.repeat = scanner.tokens[scanner.pos - 1];\n }\n else if (!elem.value && text(scanner)) {\n elem.value = getText(scanner);\n }\n else if (attr = shortAttribute(scanner, 'id', options) || shortAttribute(scanner, 'class', options) || attributeSet(scanner)) {\n if (!elem.attributes) {\n elem.attributes = Array.isArray(attr) ? attr.slice() : [attr];\n }\n else {\n elem.attributes = elem.attributes.concat(attr);\n }\n }\n else {\n if (!isEmpty(elem) && consume(scanner, isCloseOperator)) {\n elem.selfClose = true;\n if (!elem.repeat && consume(scanner, isRepeater)) {\n elem.repeat = scanner.tokens[scanner.pos - 1];\n }\n }\n break;\n }\n }\n return !isEmpty(elem) ? elem : void 0;\n}\n/**\n * Consumes attribute set from given scanner\n */\nfunction attributeSet(scanner) {\n if (consume(scanner, isAttributeSetStart)) {\n const attributes = [];\n let attr;\n while (readable(scanner)) {\n if (attr = attribute(scanner)) {\n attributes.push(attr);\n }\n else if (consume(scanner, isAttributeSetEnd)) {\n break;\n }\n else if (!consume(scanner, isWhiteSpace)) {\n throw error(scanner, `Unexpected \"${peek(scanner).type}\" token`);\n }\n }\n return attributes;\n }\n}\n/**\n * Consumes attribute shorthand (class or id) from given scanner\n */\nfunction shortAttribute(scanner, type, options) {\n if (isOperator(peek(scanner), type)) {\n scanner.pos++;\n // Consume multiple operators\n let count = 1;\n while (isOperator(peek(scanner), type)) {\n scanner.pos++;\n count++;\n }\n const attr = {\n name: [createLiteral(type)]\n };\n if (count > 1) {\n attr.multiple = true;\n }\n // Consume expression after shorthand start for React-like components\n if (options.jsx && text(scanner)) {\n attr.value = getText(scanner);\n attr.expression = true;\n }\n else {\n attr.value = literal$1(scanner) ? slice(scanner) : void 0;\n }\n return attr;\n }\n}\n/**\n * Consumes single attribute from given scanner\n */\nfunction attribute(scanner) {\n if (quoted(scanner)) {\n // Consumed quoted value: it’s a value for default attribute\n return {\n value: slice(scanner)\n };\n }\n if (literal$1(scanner, true)) {\n const name = slice(scanner);\n let value;\n if (consume(scanner, isEquals)) {\n if (quoted(scanner) || literal$1(scanner, true)) {\n value = slice(scanner);\n }\n }\n return { name, value };\n }\n}\nfunction repeater$1(scanner) {\n return isRepeater(peek(scanner))\n ? scanner.tokens[scanner.pos++]\n : void 0;\n}\n/**\n * Consumes quoted value from given scanner, if possible\n */\nfunction quoted(scanner) {\n const start = scanner.pos;\n const quote = peek(scanner);\n if (isQuote(quote)) {\n scanner.pos++;\n while (readable(scanner)) {\n if (isQuote(next(scanner), quote.single)) {\n scanner.start = start;\n return true;\n }\n }\n throw error(scanner, 'Unclosed quote', quote);\n }\n return false;\n}\n/**\n * Consumes literal (unquoted value) from given scanner\n */\nfunction literal$1(scanner, allowBrackets) {\n const start = scanner.pos;\n const brackets = {\n attribute: 0,\n expression: 0,\n group: 0\n };\n while (readable(scanner)) {\n const token = peek(scanner);\n if (brackets.expression) {\n // If we’re inside expression, we should consume all content in it\n if (isBracket(token, 'expression')) {\n brackets[token.context] += token.open ? 1 : -1;\n }\n }\n else if (isQuote(token) || isOperator(token) || isWhiteSpace(token) || isRepeater(token)) {\n break;\n }\n else if (isBracket(token)) {\n if (!allowBrackets) {\n break;\n }\n if (token.open) {\n brackets[token.context]++;\n }\n else if (!brackets[token.context]) {\n // Stop if found unmatched closing brace: it must be handled\n // by parent consumer\n break;\n }\n else {\n brackets[token.context]--;\n }\n }\n scanner.pos++;\n }\n if (start !== scanner.pos) {\n scanner.start = start;\n return true;\n }\n return false;\n}\n/**\n * Consumes element name from given scanner\n */\nfunction elementName(scanner, options) {\n const start = scanner.pos;\n if (options.jsx && consume(scanner, isCapitalizedLiteral)) {\n // Check for edge case: consume immediate capitalized class names\n // for React-like components, e.g. `Foo.Bar.Baz`\n while (readable(scanner)) {\n const { pos } = scanner;\n if (!consume(scanner, isClassNameOperator) || !consume(scanner, isCapitalizedLiteral)) {\n scanner.pos = pos;\n break;\n }\n }\n }\n while (readable(scanner) && consume(scanner, isElementName$1)) {\n // empty\n }\n if (scanner.pos !== start) {\n scanner.start = start;\n return true;\n }\n return false;\n}\n/**\n * Consumes text value from given scanner\n */\nfunction text(scanner) {\n const start = scanner.pos;\n if (consume(scanner, isTextStart)) {\n let brackets = 0;\n while (readable(scanner)) {\n const token = next(scanner);\n if (isBracket(token, 'expression')) {\n if (token.open) {\n brackets++;\n }\n else if (!brackets) {\n break;\n }\n else {\n brackets--;\n }\n }\n }\n scanner.start = start;\n return true;\n }\n return false;\n}\nfunction getText(scanner) {\n let from = scanner.start;\n let to = scanner.pos;\n if (isBracket(scanner.tokens[from], 'expression', true)) {\n from++;\n }\n if (isBracket(scanner.tokens[to - 1], 'expression', false)) {\n to--;\n }\n return slice(scanner, from, to);\n}\nfunction isBracket(token, context, isOpen) {\n return Boolean(token && token.type === 'Bracket'\n && (!context || token.context === context)\n && (isOpen == null || token.open === isOpen));\n}\nfunction isOperator(token, type) {\n return Boolean(token && token.type === 'Operator' && (!type || token.operator === type));\n}\nfunction isQuote(token, isSingle) {\n return Boolean(token && token.type === 'Quote' && (isSingle == null || token.single === isSingle));\n}\nfunction isWhiteSpace(token) {\n return Boolean(token && token.type === 'WhiteSpace');\n}\nfunction isEquals(token) {\n return isOperator(token, 'equal');\n}\nfunction isRepeater(token) {\n return Boolean(token && token.type === 'Repeater');\n}\nfunction isLiteral(token) {\n return token.type === 'Literal';\n}\nfunction isCapitalizedLiteral(token) {\n if (isLiteral(token)) {\n const ch = token.value.charCodeAt(0);\n return ch >= 65 && ch <= 90;\n }\n return false;\n}\nfunction isElementName$1(token) {\n return token.type === 'Literal' || token.type === 'RepeaterNumber' || token.type === 'RepeaterPlaceholder';\n}\nfunction isClassNameOperator(token) {\n return isOperator(token, 'class');\n}\nfunction isAttributeSetStart(token) {\n return isBracket(token, 'attribute', true);\n}\nfunction isAttributeSetEnd(token) {\n return isBracket(token, 'attribute', false);\n}\nfunction isTextStart(token) {\n return isBracket(token, 'expression', true);\n}\nfunction isGroupStart(token) {\n return isBracket(token, 'group', true);\n}\nfunction createLiteral(value) {\n return { type: 'Literal', value };\n}\nfunction isEmpty(elem) {\n return !elem.name && !elem.value && !elem.attributes;\n}\nfunction isChildOperator(token) {\n return isOperator(token, 'child');\n}\nfunction isSiblingOperator(token) {\n return isOperator(token, 'sibling');\n}\nfunction isClimbOperator(token) {\n return isOperator(token, 'climb');\n}\nfunction isCloseOperator(token) {\n return isOperator(token, 'close');\n}\n\nvar Chars;\n(function (Chars) {\n /** `{` character */\n Chars[Chars[\"CurlyBracketOpen\"] = 123] = \"CurlyBracketOpen\";\n /** `}` character */\n Chars[Chars[\"CurlyBracketClose\"] = 125] = \"CurlyBracketClose\";\n /** `\\\\` character */\n Chars[Chars[\"Escape\"] = 92] = \"Escape\";\n /** `=` character */\n Chars[Chars[\"Equals\"] = 61] = \"Equals\";\n /** `[` character */\n Chars[Chars[\"SquareBracketOpen\"] = 91] = \"SquareBracketOpen\";\n /** `]` character */\n Chars[Chars[\"SquareBracketClose\"] = 93] = \"SquareBracketClose\";\n /** `*` character */\n Chars[Chars[\"Asterisk\"] = 42] = \"Asterisk\";\n /** `#` character */\n Chars[Chars[\"Hash\"] = 35] = \"Hash\";\n /** `$` character */\n Chars[Chars[\"Dollar\"] = 36] = \"Dollar\";\n /** `-` character */\n Chars[Chars[\"Dash\"] = 45] = \"Dash\";\n /** `.` character */\n Chars[Chars[\"Dot\"] = 46] = \"Dot\";\n /** `/` character */\n Chars[Chars[\"Slash\"] = 47] = \"Slash\";\n /** `:` character */\n Chars[Chars[\"Colon\"] = 58] = \"Colon\";\n /** `!` character */\n Chars[Chars[\"Excl\"] = 33] = \"Excl\";\n /** `@` character */\n Chars[Chars[\"At\"] = 64] = \"At\";\n /** `_` character */\n Chars[Chars[\"Underscore\"] = 95] = \"Underscore\";\n /** `(` character */\n Chars[Chars[\"RoundBracketOpen\"] = 40] = \"RoundBracketOpen\";\n /** `)` character */\n Chars[Chars[\"RoundBracketClose\"] = 41] = \"RoundBracketClose\";\n /** `+` character */\n Chars[Chars[\"Sibling\"] = 43] = \"Sibling\";\n /** `>` character */\n Chars[Chars[\"Child\"] = 62] = \"Child\";\n /** `^` character */\n Chars[Chars[\"Climb\"] = 94] = \"Climb\";\n /** `'` character */\n Chars[Chars[\"SingleQuote\"] = 39] = \"SingleQuote\";\n /** `\"\"` character */\n Chars[Chars[\"DoubleQuote\"] = 34] = \"DoubleQuote\";\n})(Chars || (Chars = {}));\n/**\n * If consumes escape character, sets current stream range to escaped value\n */\nfunction escaped(scanner) {\n if (scanner.eat(Chars.Escape)) {\n scanner.start = scanner.pos;\n if (!scanner.eof()) {\n scanner.pos++;\n }\n return true;\n }\n return false;\n}\n\nfunction tokenize(source) {\n const scanner = new Scanner(source);\n const result = [];\n const ctx = {\n group: 0,\n attribute: 0,\n expression: 0,\n quote: 0\n };\n let ch = 0;\n let token;\n while (!scanner.eof()) {\n ch = scanner.peek();\n token = getToken(scanner, ctx);\n if (token) {\n result.push(token);\n if (token.type === 'Quote') {\n ctx.quote = ch === ctx.quote ? 0 : ch;\n }\n else if (token.type === 'Bracket') {\n ctx[token.context] += token.open ? 1 : -1;\n }\n }\n else {\n throw scanner.error('Unexpected character');\n }\n }\n return result;\n}\n/**\n * Returns next token from given scanner, if possible\n */\nfunction getToken(scanner, ctx) {\n return field(scanner, ctx)\n || repeaterPlaceholder(scanner)\n || repeaterNumber(scanner)\n || repeater(scanner)\n || whiteSpace(scanner)\n || literal(scanner, ctx)\n || operator(scanner)\n || quote(scanner)\n || bracket(scanner);\n}\n/**\n * Consumes literal from given scanner\n */\nfunction literal(scanner, ctx) {\n const start = scanner.pos;\n const expressionStart = ctx.expression;\n let value = '';\n while (!scanner.eof()) {\n // Consume escaped sequence no matter of context\n if (escaped(scanner)) {\n value += scanner.current();\n continue;\n }\n const ch = scanner.peek();\n if (ch === Chars.Slash && !ctx.quote && !ctx.expression && !ctx.attribute) {\n // Special case for `/` character between numbers in class names\n const prev = scanner.string.charCodeAt(scanner.pos - 1);\n const next = scanner.string.charCodeAt(scanner.pos + 1);\n if (isNumber(prev) && isNumber(next)) {\n value += scanner.string[scanner.pos++];\n continue;\n }\n }\n if (ch === ctx.quote || ch === Chars.Dollar || isAllowedOperator(ch, ctx)) {\n // 1. Found matching quote\n // 2. The `$` character has special meaning in every context\n // 3. Depending on context, some characters should be treated as operators\n break;\n }\n if (expressionStart) {\n // Consume nested expressions, e.g. span{{foo}}\n if (ch === Chars.CurlyBracketOpen) {\n ctx.expression++;\n }\n else if (ch === Chars.CurlyBracketClose) {\n if (ctx.expression > expressionStart) {\n ctx.expression--;\n }\n else {\n break;\n }\n }\n }\n else if (!ctx.quote) {\n // Consuming element name\n if (!ctx.attribute && !isElementName(ch)) {\n break;\n }\n if (isAllowedSpace(ch, ctx) || isAllowedRepeater(ch, ctx) || isQuote$1(ch) || bracketType(ch)) {\n // Stop for characters not allowed in unquoted literal\n break;\n }\n }\n value += scanner.string[scanner.pos++];\n }\n if (start !== scanner.pos) {\n scanner.start = start;\n return {\n type: 'Literal',\n value,\n start,\n end: scanner.pos\n };\n }\n}\n/**\n * Consumes white space characters as string literal from given scanner\n */\nfunction whiteSpace(scanner) {\n const start = scanner.pos;\n if (scanner.eatWhile(isSpace)) {\n return {\n type: 'WhiteSpace',\n start,\n end: scanner.pos,\n value: scanner.substring(start, scanner.pos)\n };\n }\n}\n/**\n * Consumes quote from given scanner\n */\nfunction quote(scanner) {\n const ch = scanner.peek();\n if (isQuote$1(ch)) {\n return {\n type: 'Quote',\n single: ch === Chars.SingleQuote,\n start: scanner.pos++,\n end: scanner.pos\n };\n }\n}\n/**\n * Consumes bracket from given scanner\n */\nfunction bracket(scanner) {\n const ch = scanner.peek();\n const context = bracketType(ch);\n if (context) {\n return {\n type: 'Bracket',\n open: isOpenBracket(ch),\n context,\n start: scanner.pos++,\n end: scanner.pos\n };\n }\n}\n/**\n * Consumes operator from given scanner\n */\nfunction operator(scanner) {\n const op = operatorType(scanner.peek());\n if (op) {\n return {\n type: 'Operator',\n operator: op,\n start: scanner.pos++,\n end: scanner.pos\n };\n }\n}\n/**\n * Consumes node repeat token from current stream position and returns its\n * parsed value\n */\nfunction repeater(scanner) {\n const start = scanner.pos;\n if (scanner.eat(Chars.Asterisk)) {\n scanner.start = scanner.pos;\n let count = 1;\n let implicit = false;\n if (scanner.eatWhile(isNumber)) {\n count = Number(scanner.current());\n }\n else {\n implicit = true;\n }\n return {\n type: 'Repeater',\n count,\n value: 0,\n implicit,\n start,\n end: scanner.pos\n };\n }\n}\n/**\n * Consumes repeater placeholder `$#` from given scanner\n */\nfunction repeaterPlaceholder(scanner) {\n const start = scanner.pos;\n if (scanner.eat(Chars.Dollar) && scanner.eat(Chars.Hash)) {\n return {\n type: 'RepeaterPlaceholder',\n value: void 0,\n start,\n end: scanner.pos\n };\n }\n scanner.pos = start;\n}\n/**\n * Consumes numbering token like `$` from given scanner state\n */\nfunction repeaterNumber(scanner) {\n const start = scanner.pos;\n if (scanner.eatWhile(Chars.Dollar)) {\n const size = scanner.pos - start;\n let reverse = false;\n let base = 1;\n let parent = 0;\n if (scanner.eat(Chars.At)) {\n // Consume numbering modifiers\n while (scanner.eat(Chars.Climb)) {\n parent++;\n }\n reverse = scanner.eat(Chars.Dash);\n scanner.start = scanner.pos;\n if (scanner.eatWhile(isNumber)) {\n base = Number(scanner.current());\n }\n }\n scanner.start = start;\n return {\n type: 'RepeaterNumber',\n size,\n reverse,\n base,\n parent,\n start,\n end: scanner.pos\n };\n }\n}\nfunction field(scanner, ctx) {\n const start = scanner.pos;\n // Fields are allowed inside expressions and attributes\n if ((ctx.expression || ctx.attribute) && scanner.eat(Chars.Dollar) && scanner.eat(Chars.CurlyBracketOpen)) {\n scanner.start = scanner.pos;\n let index;\n let name = '';\n if (scanner.eatWhile(isNumber)) {\n // It’s a field\n index = Number(scanner.current());\n name = scanner.eat(Chars.Colon) ? consumePlaceholder(scanner) : '';\n }\n else if (isAlpha(scanner.peek())) {\n // It’s a variable\n name = consumePlaceholder(scanner);\n }\n if (scanner.eat(Chars.CurlyBracketClose)) {\n return {\n type: 'Field',\n index, name,\n start,\n end: scanner.pos\n };\n }\n throw scanner.error('Expecting }');\n }\n // If we reached here then there’s no valid field here, revert\n // back to starting position\n scanner.pos = start;\n}\n/**\n * Consumes a placeholder: value right after `:` in field. Could be empty\n */\nfunction consumePlaceholder(stream) {\n const stack = [];\n stream.start = stream.pos;\n while (!stream.eof()) {\n if (stream.eat(Chars.CurlyBracketOpen)) {\n stack.push(stream.pos);\n }\n else if (stream.eat(Chars.CurlyBracketClose)) {\n if (!stack.length) {\n stream.pos--;\n break;\n }\n stack.pop();\n }\n else {\n stream.pos++;\n }\n }\n if (stack.length) {\n stream.pos = stack.pop();\n throw stream.error(`Expecting }`);\n }\n return stream.current();\n}\n/**\n * Check if given character code is an operator and it’s allowed in current context\n */\nfunction isAllowedOperator(ch, ctx) {\n const op = operatorType(ch);\n if (!op || ctx.quote || ctx.expression) {\n // No operators inside quoted values or expressions\n return false;\n }\n // Inside attributes, only `equals` is allowed\n return !ctx.attribute || op === 'equal';\n}\n/**\n * Check if given character is a space character and is allowed to be consumed\n * as a space token in current context\n */\nfunction isAllowedSpace(ch, ctx) {\n return isSpace(ch) && !ctx.expression;\n}\n/**\n * Check if given character can be consumed as repeater in current context\n */\nfunction isAllowedRepeater(ch, ctx) {\n return ch === Chars.Asterisk && !ctx.attribute && !ctx.expression;\n}\n/**\n * If given character is a bracket, returns it’s type\n */\nfunction bracketType(ch) {\n if (ch === Chars.RoundBracketOpen || ch === Chars.RoundBracketClose) {\n return 'group';\n }\n if (ch === Chars.SquareBracketOpen || ch === Chars.SquareBracketClose) {\n return 'attribute';\n }\n if (ch === Chars.CurlyBracketOpen || ch === Chars.CurlyBracketClose) {\n return 'expression';\n }\n}\n/**\n * If given character is an operator, returns it’s type\n */\nfunction operatorType(ch) {\n return (ch === Chars.Child && 'child')\n || (ch === Chars.Sibling && 'sibling')\n || (ch === Chars.Climb && 'climb')\n || (ch === Chars.Dot && 'class')\n || (ch === Chars.Hash && 'id')\n || (ch === Chars.Slash && 'close')\n || (ch === Chars.Equals && 'equal')\n || void 0;\n}\n/**\n * Check if given character is an open bracket\n */\nfunction isOpenBracket(ch) {\n return ch === Chars.CurlyBracketOpen\n || ch === Chars.SquareBracketOpen\n || ch === Chars.RoundBracketOpen;\n}\n/**\n * Check if given character is allowed in element name\n */\nfunction isElementName(ch) {\n return isAlphaNumericWord(ch)\n || isUmlaut(ch)\n || ch === Chars.Dash\n || ch === Chars.Colon\n || ch === Chars.Excl;\n}\n\nconst operators = {\n child: '>',\n class: '.',\n climb: '^',\n id: '#',\n equal: '=',\n close: '/',\n sibling: '+'\n};\nconst tokenVisitor = {\n Literal(token) {\n return token.value;\n },\n Quote(token) {\n return token.single ? '\\'' : '\"';\n },\n Bracket(token) {\n if (token.context === 'attribute') {\n return token.open ? '[' : ']';\n }\n else if (token.context === 'expression') {\n return token.open ? '{' : '}';\n }\n else {\n return token.open ? '(' : '}';\n }\n },\n Operator(token) {\n return operators[token.operator];\n },\n Field(token, state) {\n if (token.index != null) {\n // It’s a field: by default, return TextMate-compatible field\n return token.name\n ? `\\${${token.index}:${token.name}}`\n : `\\${${token.index}`;\n }\n else if (token.name) {\n // It’s a variable\n return state.getVariable(token.name);\n }\n return '';\n },\n RepeaterPlaceholder(token, state) {\n // Find closest implicit repeater\n let repeater;\n for (let i = state.repeaters.length - 1; i >= 0; i--) {\n if (state.repeaters[i].implicit) {\n repeater = state.repeaters[i];\n break;\n }\n }\n state.inserted = true;\n return state.getText(repeater && repeater.value);\n },\n RepeaterNumber(token, state) {\n let value = 1;\n const lastIx = state.repeaters.length - 1;\n // const repeaterIx = Math.max(0, state.repeaters.length - 1 - token.parent);\n const repeater = state.repeaters[lastIx];\n if (repeater) {\n value = token.reverse\n ? token.base + repeater.count - repeater.value - 1\n : token.base + repeater.value;\n if (token.parent) {\n const parentIx = Math.max(0, lastIx - token.parent);\n if (parentIx !== lastIx) {\n const parentRepeater = state.repeaters[parentIx];\n value += repeater.count * parentRepeater.value;\n }\n }\n }\n let result = String(value);\n while (result.length < token.size) {\n result = '0' + result;\n }\n return result;\n },\n WhiteSpace(token) {\n return token.value;\n }\n};\n/**\n * Converts given value token to string\n */\nfunction stringify(token, state) {\n if (!tokenVisitor[token.type]) {\n throw new Error(`Unknown token ${token.type}`);\n }\n return tokenVisitor[token.type](token, state);\n}\n\nconst urlRegex = /^((https?:|ftp:|file:)?\\/\\/|(www|ftp)\\.)[^ ]*$/;\nconst emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\\.[a-z]{2,5}$/;\n/**\n * Converts given token-based abbreviation into simplified and unrolled node-based\n * abbreviation\n */\nfunction convert(abbr, options = {}) {\n let textInserted = false;\n let cleanText;\n if (options.text) {\n if (Array.isArray(options.text)) {\n cleanText = options.text.filter(s => s.trim());\n }\n else {\n cleanText = options.text;\n }\n }\n const result = {\n type: 'Abbreviation',\n children: convertGroup(abbr, {\n inserted: false,\n repeaters: [],\n text: options.text,\n cleanText,\n repeatGuard: options.maxRepeat || Number.POSITIVE_INFINITY,\n getText(pos) {\n var _a;\n textInserted = true;\n let value;\n if (Array.isArray(options.text)) {\n if (pos !== undefined && pos >= 0 && pos < cleanText.length) {\n return cleanText[pos];\n }\n value = pos !== undefined ? options.text[pos] : options.text.join('\\n');\n }\n else {\n value = (_a = options.text) !== null && _a !== void 0 ? _a : '';\n }\n return value;\n },\n getVariable(name) {\n const varValue = options.variables && options.variables[name];\n return varValue != null ? varValue : name;\n }\n })\n };\n if (options.text != null && !textInserted) {\n // Text given but no implicitly repeated elements: insert it into\n // deepest child\n const deepest = deepestNode(last(result.children));\n if (deepest) {\n const text = Array.isArray(options.text) ? options.text.join('\\n') : options.text;\n insertText(deepest, text);\n if (deepest.name === 'a' && options.href) {\n // Automatically update value of `<a>` element if inserting URL or email\n insertHref(deepest, text);\n }\n }\n }\n return result;\n}\n/**\n * Converts given statement to abbreviation nodes\n */\nfunction convertStatement(node, state) {\n let result = [];\n if (node.repeat) {\n // Node is repeated: we should create copies of given node\n // and supply context token with actual repeater state\n const original = node.repeat;\n const repeat = Object.assign({}, original);\n repeat.count = repeat.implicit && Array.isArray(state.text)\n ? state.cleanText.length\n : (repeat.count || 1);\n let items;\n state.repeaters.push(repeat);\n for (let i = 0; i < repeat.count; i++) {\n repeat.value = i;\n node.repeat = repeat;\n items = isGroup(node)\n ? convertGroup(node, state)\n : convertElement(node, state);\n if (repeat.implicit && !state.inserted) {\n // It’s an implicit repeater but no repeater placeholders found inside,\n // we should insert text into deepest node\n const target = last(items);\n const deepest = target && deepestNode(target);\n if (deepest) {\n insertText(deepest, state.getText(repeat.value));\n }\n }\n result = result.concat(items);\n // We should output at least one repeated item even if it’s reached\n // repeat limit\n if (--state.repeatGuard <= 0) {\n break;\n }\n }\n state.repeaters.pop();\n node.repeat = original;\n if (repeat.implicit) {\n state.inserted = true;\n }\n }\n else {\n result = result.concat(isGroup(node) ? convertGroup(node, state) : convertElement(node, state));\n }\n return result;\n}\nfunction convertElement(node, state) {\n let children = [];\n const elem = {\n type: 'AbbreviationNode',\n name: node.name && stringifyName(node.name, state),\n value: node.value && stringifyValue(node.value, state),\n attributes: void 0,\n children,\n repeat: node.repeat && Object.assign({}, node.repeat),\n selfClosing: node.selfClose,\n };\n let result = [elem];\n for (const child of node.elements) {\n children = children.concat(convertStatement(child, state));\n }\n if (node.attributes) {\n elem.attributes = [];\n for (const attr of node.attributes) {\n elem.attributes.push(convertAttribute(attr, state));\n }\n }\n // In case if current node is a text-only snippet without fields, we should\n // put all children as siblings\n if (!elem.name && !elem.attributes && elem.value && !elem.value.some(isField)) {\n // XXX it’s unclear that `children` is not bound to `elem`\n // due to concat operation\n result = result.concat(children);\n }\n else {\n elem.children = children;\n }\n return result;\n}\nfunction convertGroup(node, state) {\n let result = [];\n for (const child of node.elements) {\n result = result.concat(convertStatement(child, state));\n }\n if (node.repeat) {\n result = attachRepeater(result, node.repeat);\n }\n return result;\n}\nfunction convertAttribute(node, state) {\n let implied = false;\n let isBoolean = false;\n let valueType = node.expression ? 'expression' : 'raw';\n let value;\n const name = node.name && stringifyName(node.name, state);\n if (name && name[0] === '!') {\n implied = true;\n }\n if (name && name[name.length - 1] === '.') {\n isBoolean = true;\n }\n if (node.value) {\n const tokens = node.value.slice();\n if (isQuote(tokens[0])) {\n // It’s a quoted value: remove quotes from output but mark attribute\n // value as quoted\n const quote = tokens.shift();\n if (tokens.length && last(tokens).type === quote.type) {\n tokens.pop();\n }\n valueType = quote.single ? 'singleQuote' : 'doubleQuote';\n }\n else if (isBracket(tokens[0], 'expression', true)) {\n // Value is expression: remove brackets but mark value type\n valueType = 'expression';\n tokens.shift();\n if (isBracket(last(tokens), 'expression', false)) {\n tokens.pop();\n }\n }\n value = stringifyValue(tokens, state);\n }\n return {\n name: isBoolean || implied\n ? name.slice(implied ? 1 : 0, isBoolean ? -1 : void 0)\n : name,\n value,\n boolean: isBoolean,\n implied,\n valueType,\n multiple: node.multiple\n };\n}\n/**\n * Converts given token list to string\n */\nfunction stringifyName(tokens, state) {\n let str = '';\n for (let i = 0; i < tokens.length; i++) {\n str += stringify(tokens[i], state);\n }\n return str;\n}\n/**\n * Converts given token list to value list\n */\nfunction stringifyValue(tokens, state) {\n const result = [];\n let str = '';\n for (let i = 0, token; i < tokens.length; i++) {\n token = tokens[i];\n if (isField(token)) {\n // We should keep original fields in output since some editors has their\n // own syntax for field or doesn’t support fields at all so we should\n // capture actual field location in output stream\n if (str) {\n result.push(str);\n str = '';\n }\n result.push(token);\n }\n else {\n str += stringify(token, state);\n }\n }\n if (str) {\n result.push(str);\n }\n return result;\n}\nfunction isGroup(node) {\n return node.type === 'TokenGroup';\n}\nfunction isField(token) {\n return typeof token === 'object' && token.type === 'Field' && token.index != null;\n}\nfunction last(arr) {\n return arr[arr.length - 1];\n}\nfunction deepestNode(node) {\n return node.children.length ? deepestNode(last(node.children)) : node;\n}\nfunction insertText(node, text) {\n if (node.value) {\n const lastToken = last(node.value);\n if (typeof lastToken === 'string') {\n node.value[node.value.length - 1] += text;\n }\n else {\n node.value.push(text);\n }\n }\n else {\n node.value = [text];\n }\n}\nfunction insertHref(node, text) {\n var _a;\n let href = '';\n if (urlRegex.test(text)) {\n href = text;\n if (!/\\w+:/.test(href) && !href.startsWith('//')) {\n href = `http://${href}`;\n }\n }\n else if (emailRegex.test(text)) {\n href = `mailto:${text}`;\n }\n const hrefAttribute = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.find(attr => attr.name === 'href');\n if (!hrefAttribute) {\n if (!node.attributes) {\n node.attributes = [];\n }\n node.attributes.push({ name: 'href', value: [href], valueType: 'doubleQuote' });\n }\n else if (!hrefAttribute.value) {\n hrefAttribute.value = [href];\n }\n}\nfunction attachRepeater(items, repeater) {\n for (const item of items) {\n if (!item.repeat) {\n item.repeat = Object.assign({}, repeater);\n }\n }\n return items;\n}\n\n/**\n * Parses given abbreviation into node tree\n */\nfunction parseAbbreviation(abbr, options) {\n try {\n const tokens = typeof abbr === 'string' ? tokenize(abbr) : abbr;\n return convert(abbreviation(tokens, options), options);\n }\n catch (err) {\n if (err instanceof ScannerError && typeof abbr === 'string') {\n err.message += `\\n${abbr}\\n${'-'.repeat(err.pos)}^`;\n }\n throw err;\n }\n}\n\nexport { convert, parseAbbreviation as default, getToken, abbreviation as parse, tokenize };\n//# sourceMappingURL=index.js.map\n","import Scanner, { isNumber, isAlpha, isAlphaWord, isQuote, isSpace, isAlphaNumericWord, ScannerError } from '@emmetio/scanner';\n\nvar OperatorType;\n(function (OperatorType) {\n OperatorType[\"Sibling\"] = \"+\";\n OperatorType[\"Important\"] = \"!\";\n OperatorType[\"ArgumentDelimiter\"] = \",\";\n OperatorType[\"ValueDelimiter\"] = \"-\";\n OperatorType[\"PropertyDelimiter\"] = \":\";\n})(OperatorType || (OperatorType = {}));\n\nvar Chars;\n(function (Chars) {\n /** `#` character */\n Chars[Chars[\"Hash\"] = 35] = \"Hash\";\n /** `$` character */\n Chars[Chars[\"Dollar\"] = 36] = \"Dollar\";\n /** `-` character */\n Chars[Chars[\"Dash\"] = 45] = \"Dash\";\n /** `.` character */\n Chars[Chars[\"Dot\"] = 46] = \"Dot\";\n /** `:` character */\n Chars[Chars[\"Colon\"] = 58] = \"Colon\";\n /** `,` character */\n Chars[Chars[\"Comma\"] = 44] = \"Comma\";\n /** `!` character */\n Chars[Chars[\"Excl\"] = 33] = \"Excl\";\n /** `@` character */\n Chars[Chars[\"At\"] = 64] = \"At\";\n /** `%` character */\n Chars[Chars[\"Percent\"] = 37] = \"Percent\";\n /** `_` character */\n Chars[Chars[\"Underscore\"] = 95] = \"Underscore\";\n /** `(` character */\n Chars[Chars[\"RoundBracketOpen\"] = 40] = \"RoundBracketOpen\";\n /** `)` character */\n Chars[Chars[\"RoundBracketClose\"] = 41] = \"RoundBracketClose\";\n /** `{` character */\n Chars[Chars[\"CurlyBracketOpen\"] = 123] = \"CurlyBracketOpen\";\n /** `}` character */\n Chars[Chars[\"CurlyBracketClose\"] = 125] = \"CurlyBracketClose\";\n /** `+` character */\n Chars[Chars[\"Sibling\"] = 43] = \"Sibling\";\n /** `'` character */\n Chars[Chars[\"SingleQuote\"] = 39] = \"SingleQuote\";\n /** `\"` character */\n Chars[Chars[\"DoubleQuote\"] = 34] = \"DoubleQuote\";\n /** `t` character */\n Chars[Chars[\"Transparent\"] = 116] = \"Transparent\";\n /** `/` character */\n Chars[Chars[\"Slash\"] = 47] = \"Slash\";\n})(Chars || (Chars = {}));\n\nfunction tokenize(abbr, isValue) {\n let brackets = 0;\n let token;\n const scanner = new Scanner(abbr);\n const tokens = [];\n while (!scanner.eof()) {\n token = getToken(scanner, brackets === 0 && !isValue);\n if (!token) {\n throw scanner.error('Unexpected character');\n }\n if (token.type === 'Bracket') {\n if (!brackets && token.open) {\n mergeTokens(scanner, tokens);\n }\n brackets += token.open ? 1 : -1;\n if (brackets < 0) {\n throw scanner.error('Unexpected bracket', token.start);\n }\n }\n tokens.push(token);\n // Forcibly consume next operator after unit-less numeric value or color:\n // next dash `-` must be used as value delimiter\n if (shouldConsumeDashAfter(token) && (token = operator(scanner))) {\n tokens.push(token);\n }\n }\n return tokens;\n}\n/**\n * Returns next token from given scanner, if possible\n */\nfunction getToken(scanner, short) {\n return field(scanner)\n || customProperty(scanner)\n || numberValue(scanner)\n || colorValue(scanner)\n || stringValue(scanner)\n || bracket(scanner)\n || operator(scanner)\n || whiteSpace(scanner)\n || literal(scanner, short);\n}\nfunction field(