UNPKG

176 kBJavaScriptView Raw
1/**
2 * Check if given code is a number
3 */
4function isNumber$1(code) {
5 return code > 47 && code < 58;
6}
7/**
8 * Check if given character code is alpha code (letter through A to Z)
9 */
10function isAlpha$1(code, from, to) {
11 from = from || 65; // A
12 to = to || 90; // Z
13 code &= ~32; // quick hack to convert any char code to uppercase char code
14 return code >= from && code <= to;
15}
16function isAlphaNumericWord(code) {
17 return isNumber$1(code) || isAlphaWord(code);
18}
19function isAlphaWord(code) {
20 return code === 95 /* _ */ || isAlpha$1(code);
21}
22/**
23 * Check for Umlauts i.e. ä, Ä, ö, Ö, ü and Ü
24 */
25function isUmlaut(code) {
26 return code === 196
27 || code == 214
28 || code === 220
29 || code === 228
30 || code === 246
31 || code === 252;
32}
33/**
34 * Check if given character code is a white-space character: a space character
35 * or line breaks
36 */
37function isWhiteSpace$3(code) {
38 return code === 32 /* space */
39 || code === 9 /* tab */
40 || code === 160; /* non-breaking space */
41}
42/**
43 * Check if given character code is a space character
44 */
45function isSpace(code) {
46 return isWhiteSpace$3(code)
47 || code === 10 /* LF */
48 || code === 13; /* CR */
49}
50/**
51 * Check if given character code is a quote character
52 */
53function isQuote$2(code) {
54 return code === 39 /* ' */ || code === 34 /* " */;
55}
56
57/**
58 * A streaming, character code-based string reader
59 */
60class Scanner {
61 constructor(str, start, end) {
62 if (end == null && typeof str === 'string') {
63 end = str.length;
64 }
65 this.string = str;
66 this.pos = this.start = start || 0;
67 this.end = end || 0;
68 }
69 /**
70 * Returns true only if the stream is at the end of the file.
71 */
72 eof() {
73 return this.pos >= this.end;
74 }
75 /**
76 * Creates a new stream instance which is limited to given `start` and `end`
77 * range. E.g. its `eof()` method will look at `end` property, not actual
78 * stream end
79 */
80 limit(start, end) {
81 return new Scanner(this.string, start, end);
82 }
83 /**
84 * Returns the next character code in the stream without advancing it.
85 * Will return NaN at the end of the file.
86 */
87 peek() {
88 return this.string.charCodeAt(this.pos);
89 }
90 /**
91 * Returns the next character in the stream and advances it.
92 * Also returns <code>undefined</code> when no more characters are available.
93 */
94 next() {
95 if (this.pos < this.string.length) {
96 return this.string.charCodeAt(this.pos++);
97 }
98 }
99 /**
100 * `match` can be a character code or a function that takes a character code
101 * and returns a boolean. If the next character in the stream 'matches'
102 * the given argument, it is consumed and returned.
103 * Otherwise, `false` is returned.
104 */
105 eat(match) {
106 const ch = this.peek();
107 const ok = typeof match === 'function' ? match(ch) : ch === match;
108 if (ok) {
109 this.next();
110 }
111 return ok;
112 }
113 /**
114 * Repeatedly calls <code>eat</code> with the given argument, until it
115 * fails. Returns <code>true</code> if any characters were eaten.
116 */
117 eatWhile(match) {
118 const start = this.pos;
119 while (!this.eof() && this.eat(match)) { /* */ }
120 return this.pos !== start;
121 }
122 /**
123 * Backs up the stream n characters. Backing it up further than the
124 * start of the current token will cause things to break, so be careful.
125 */
126 backUp(n) {
127 this.pos -= (n || 1);
128 }
129 /**
130 * Get the string between the start of the current token and the
131 * current stream position.
132 */
133 current() {
134 return this.substring(this.start, this.pos);
135 }
136 /**
137 * Returns substring for given range
138 */
139 substring(start, end) {
140 return this.string.slice(start, end);
141 }
142 /**
143 * Creates error object with current stream state
144 */
145 error(message, pos = this.pos) {
146 return new ScannerError(`${message} at ${pos + 1}`, pos, this.string);
147 }
148}
149class ScannerError extends Error {
150 constructor(message, pos, str) {
151 super(message);
152 this.pos = pos;
153 this.string = str;
154 }
155}
156
157function tokenScanner$1(tokens) {
158 return {
159 tokens,
160 start: 0,
161 pos: 0,
162 size: tokens.length
163 };
164}
165function peek$3(scanner) {
166 return scanner.tokens[scanner.pos];
167}
168function next(scanner) {
169 return scanner.tokens[scanner.pos++];
170}
171function slice(scanner, from = scanner.start, to = scanner.pos) {
172 return scanner.tokens.slice(from, to);
173}
174function readable$1(scanner) {
175 return scanner.pos < scanner.size;
176}
177function consume$2(scanner, test) {
178 const token = peek$3(scanner);
179 if (token && test(token)) {
180 scanner.pos++;
181 return true;
182 }
183 return false;
184}
185function error$1(scanner, message, token = peek$3(scanner)) {
186 if (token && token.start != null) {
187 message += ` at ${token.start}`;
188 }
189 const err = new Error(message);
190 err['pos'] = token && token.start;
191 return err;
192}
193
194function abbreviation(abbr, options = {}) {
195 const scanner = tokenScanner$1(abbr);
196 const result = statements(scanner, options);
197 if (readable$1(scanner)) {
198 throw error$1(scanner, 'Unexpected character');
199 }
200 return result;
201}
202function statements(scanner, options) {
203 const result = {
204 type: 'TokenGroup',
205 elements: []
206 };
207 let ctx = result;
208 let node;
209 const stack = [];
210 while (readable$1(scanner)) {
211 if (node = element$2(scanner, options) || group(scanner, options)) {
212 ctx.elements.push(node);
213 if (consume$2(scanner, isChildOperator)) {
214 stack.push(ctx);
215 ctx = node;
216 }
217 else if (consume$2(scanner, isSiblingOperator$1)) {
218 continue;
219 }
220 else if (consume$2(scanner, isClimbOperator)) {
221 do {
222 if (stack.length) {
223 ctx = stack.pop();
224 }
225 } while (consume$2(scanner, isClimbOperator));
226 }
227 }
228 else {
229 break;
230 }
231 }
232 return result;
233}
234/**
235 * Consumes group from given scanner
236 */
237function group(scanner, options) {
238 if (consume$2(scanner, isGroupStart)) {
239 const result = statements(scanner, options);
240 const token = next(scanner);
241 if (isBracket$2(token, 'group', false)) {
242 result.repeat = repeater$1(scanner);
243 }
244 return result;
245 }
246}
247/**
248 * Consumes single element from given scanner
249 */
250function element$2(scanner, options) {
251 let attr;
252 const elem = {
253 type: 'TokenElement',
254 name: void 0,
255 attributes: void 0,
256 value: void 0,
257 repeat: void 0,
258 selfClose: false,
259 elements: []
260 };
261 if (elementName(scanner, options)) {
262 elem.name = slice(scanner);
263 }
264 while (readable$1(scanner)) {
265 scanner.start = scanner.pos;
266 if (!elem.repeat && !isEmpty(elem) && consume$2(scanner, isRepeater)) {
267 elem.repeat = scanner.tokens[scanner.pos - 1];
268 }
269 else if (!elem.value && text(scanner)) {
270 elem.value = getText(scanner);
271 }
272 else if (attr = shortAttribute(scanner, 'id', options) || shortAttribute(scanner, 'class', options) || attributeSet(scanner)) {
273 if (!elem.attributes) {
274 elem.attributes = Array.isArray(attr) ? attr.slice() : [attr];
275 }
276 else {
277 elem.attributes = elem.attributes.concat(attr);
278 }
279 }
280 else {
281 if (!isEmpty(elem) && consume$2(scanner, isCloseOperator)) {
282 elem.selfClose = true;
283 if (!elem.repeat && consume$2(scanner, isRepeater)) {
284 elem.repeat = scanner.tokens[scanner.pos - 1];
285 }
286 }
287 break;
288 }
289 }
290 return !isEmpty(elem) ? elem : void 0;
291}
292/**
293 * Consumes attribute set from given scanner
294 */
295function attributeSet(scanner) {
296 if (consume$2(scanner, isAttributeSetStart)) {
297 const attributes = [];
298 let attr;
299 while (readable$1(scanner)) {
300 if (attr = attribute(scanner)) {
301 attributes.push(attr);
302 }
303 else if (consume$2(scanner, isAttributeSetEnd)) {
304 break;
305 }
306 else if (!consume$2(scanner, isWhiteSpace$2)) {
307 throw error$1(scanner, `Unexpected "${peek$3(scanner).type}" token`);
308 }
309 }
310 return attributes;
311 }
312}
313/**
314 * Consumes attribute shorthand (class or id) from given scanner
315 */
316function shortAttribute(scanner, type, options) {
317 if (isOperator$1(peek$3(scanner), type)) {
318 scanner.pos++;
319 // Consume multiple operators
320 let count = 1;
321 while (isOperator$1(peek$3(scanner), type)) {
322 scanner.pos++;
323 count++;
324 }
325 const attr = {
326 name: [createLiteral$1(type)]
327 };
328 if (count > 1) {
329 attr.multiple = true;
330 }
331 // Consume expression after shorthand start for React-like components
332 if (options.jsx && text(scanner)) {
333 attr.value = getText(scanner);
334 attr.expression = true;
335 }
336 else {
337 attr.value = literal$1$1(scanner) ? slice(scanner) : void 0;
338 }
339 return attr;
340 }
341}
342/**
343 * Consumes single attribute from given scanner
344 */
345function attribute(scanner) {
346 if (quoted(scanner)) {
347 // Consumed quoted value: it’s a value for default attribute
348 return {
349 value: slice(scanner)
350 };
351 }
352 if (literal$1$1(scanner, true)) {
353 const name = slice(scanner);
354 let value;
355 if (consume$2(scanner, isEquals)) {
356 if (quoted(scanner) || literal$1$1(scanner, true)) {
357 value = slice(scanner);
358 }
359 }
360 return { name, value };
361 }
362}
363function repeater$1(scanner) {
364 return isRepeater(peek$3(scanner))
365 ? scanner.tokens[scanner.pos++]
366 : void 0;
367}
368/**
369 * Consumes quoted value from given scanner, if possible
370 */
371function quoted(scanner) {
372 const start = scanner.pos;
373 const quote = peek$3(scanner);
374 if (isQuote$1(quote)) {
375 scanner.pos++;
376 while (readable$1(scanner)) {
377 if (isQuote$1(next(scanner), quote.single)) {
378 scanner.start = start;
379 return true;
380 }
381 }
382 throw error$1(scanner, 'Unclosed quote', quote);
383 }
384 return false;
385}
386/**
387 * Consumes literal (unquoted value) from given scanner
388 */
389function literal$1$1(scanner, allowBrackets) {
390 const start = scanner.pos;
391 const brackets = {
392 attribute: 0,
393 expression: 0,
394 group: 0
395 };
396 while (readable$1(scanner)) {
397 const token = peek$3(scanner);
398 if (brackets.expression) {
399 // If we’re inside expression, we should consume all content in it
400 if (isBracket$2(token, 'expression')) {
401 brackets[token.context] += token.open ? 1 : -1;
402 }
403 }
404 else if (isQuote$1(token) || isOperator$1(token) || isWhiteSpace$2(token) || isRepeater(token)) {
405 break;
406 }
407 else if (isBracket$2(token)) {
408 if (!allowBrackets) {
409 break;
410 }
411 if (token.open) {
412 brackets[token.context]++;
413 }
414 else if (!brackets[token.context]) {
415 // Stop if found unmatched closing brace: it must be handled
416 // by parent consumer
417 break;
418 }
419 else {
420 brackets[token.context]--;
421 }
422 }
423 scanner.pos++;
424 }
425 if (start !== scanner.pos) {
426 scanner.start = start;
427 return true;
428 }
429 return false;
430}
431/**
432 * Consumes element name from given scanner
433 */
434function elementName(scanner, options) {
435 const start = scanner.pos;
436 if (options.jsx && consume$2(scanner, isCapitalizedLiteral)) {
437 // Check for edge case: consume immediate capitalized class names
438 // for React-like components, e.g. `Foo.Bar.Baz`
439 while (readable$1(scanner)) {
440 const { pos } = scanner;
441 if (!consume$2(scanner, isClassNameOperator) || !consume$2(scanner, isCapitalizedLiteral)) {
442 scanner.pos = pos;
443 break;
444 }
445 }
446 }
447 while (readable$1(scanner) && consume$2(scanner, isElementName$1)) {
448 // empty
449 }
450 if (scanner.pos !== start) {
451 scanner.start = start;
452 return true;
453 }
454 return false;
455}
456/**
457 * Consumes text value from given scanner
458 */
459function text(scanner) {
460 const start = scanner.pos;
461 if (consume$2(scanner, isTextStart)) {
462 let brackets = 0;
463 while (readable$1(scanner)) {
464 const token = next(scanner);
465 if (isBracket$2(token, 'expression')) {
466 if (token.open) {
467 brackets++;
468 }
469 else if (!brackets) {
470 break;
471 }
472 else {
473 brackets--;
474 }
475 }
476 }
477 scanner.start = start;
478 return true;
479 }
480 return false;
481}
482function getText(scanner) {
483 let from = scanner.start;
484 let to = scanner.pos;
485 if (isBracket$2(scanner.tokens[from], 'expression', true)) {
486 from++;
487 }
488 if (isBracket$2(scanner.tokens[to - 1], 'expression', false)) {
489 to--;
490 }
491 return slice(scanner, from, to);
492}
493function isBracket$2(token, context, isOpen) {
494 return Boolean(token && token.type === 'Bracket'
495 && (!context || token.context === context)
496 && (isOpen == null || token.open === isOpen));
497}
498function isOperator$1(token, type) {
499 return Boolean(token && token.type === 'Operator' && (!type || token.operator === type));
500}
501function isQuote$1(token, isSingle) {
502 return Boolean(token && token.type === 'Quote' && (isSingle == null || token.single === isSingle));
503}
504function isWhiteSpace$2(token) {
505 return Boolean(token && token.type === 'WhiteSpace');
506}
507function isEquals(token) {
508 return isOperator$1(token, 'equal');
509}
510function isRepeater(token) {
511 return Boolean(token && token.type === 'Repeater');
512}
513function isLiteral$2(token) {
514 return token.type === 'Literal';
515}
516function isCapitalizedLiteral(token) {
517 if (isLiteral$2(token)) {
518 const ch = token.value.charCodeAt(0);
519 return ch >= 65 && ch <= 90;
520 }
521 return false;
522}
523function isElementName$1(token) {
524 return token.type === 'Literal' || token.type === 'RepeaterNumber' || token.type === 'RepeaterPlaceholder';
525}
526function isClassNameOperator(token) {
527 return isOperator$1(token, 'class');
528}
529function isAttributeSetStart(token) {
530 return isBracket$2(token, 'attribute', true);
531}
532function isAttributeSetEnd(token) {
533 return isBracket$2(token, 'attribute', false);
534}
535function isTextStart(token) {
536 return isBracket$2(token, 'expression', true);
537}
538function isGroupStart(token) {
539 return isBracket$2(token, 'group', true);
540}
541function createLiteral$1(value) {
542 return { type: 'Literal', value };
543}
544function isEmpty(elem) {
545 return !elem.name && !elem.value && !elem.attributes;
546}
547function isChildOperator(token) {
548 return isOperator$1(token, 'child');
549}
550function isSiblingOperator$1(token) {
551 return isOperator$1(token, 'sibling');
552}
553function isClimbOperator(token) {
554 return isOperator$1(token, 'climb');
555}
556function isCloseOperator(token) {
557 return isOperator$1(token, 'close');
558}
559
560var Chars$3;
561(function (Chars) {
562 /** `{` character */
563 Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
564 /** `}` character */
565 Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
566 /** `\\` character */
567 Chars[Chars["Escape"] = 92] = "Escape";
568 /** `=` character */
569 Chars[Chars["Equals"] = 61] = "Equals";
570 /** `[` character */
571 Chars[Chars["SquareBracketOpen"] = 91] = "SquareBracketOpen";
572 /** `]` character */
573 Chars[Chars["SquareBracketClose"] = 93] = "SquareBracketClose";
574 /** `*` character */
575 Chars[Chars["Asterisk"] = 42] = "Asterisk";
576 /** `#` character */
577 Chars[Chars["Hash"] = 35] = "Hash";
578 /** `$` character */
579 Chars[Chars["Dollar"] = 36] = "Dollar";
580 /** `-` character */
581 Chars[Chars["Dash"] = 45] = "Dash";
582 /** `.` character */
583 Chars[Chars["Dot"] = 46] = "Dot";
584 /** `/` character */
585 Chars[Chars["Slash"] = 47] = "Slash";
586 /** `:` character */
587 Chars[Chars["Colon"] = 58] = "Colon";
588 /** `!` character */
589 Chars[Chars["Excl"] = 33] = "Excl";
590 /** `@` character */
591 Chars[Chars["At"] = 64] = "At";
592 /** `_` character */
593 Chars[Chars["Underscore"] = 95] = "Underscore";
594 /** `(` character */
595 Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
596 /** `)` character */
597 Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
598 /** `+` character */
599 Chars[Chars["Sibling"] = 43] = "Sibling";
600 /** `>` character */
601 Chars[Chars["Child"] = 62] = "Child";
602 /** `^` character */
603 Chars[Chars["Climb"] = 94] = "Climb";
604 /** `'` character */
605 Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
606 /** `""` character */
607 Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
608})(Chars$3 || (Chars$3 = {}));
609/**
610 * If consumes escape character, sets current stream range to escaped value
611 */
612function escaped(scanner) {
613 if (scanner.eat(Chars$3.Escape)) {
614 scanner.start = scanner.pos;
615 if (!scanner.eof()) {
616 scanner.pos++;
617 }
618 return true;
619 }
620 return false;
621}
622
623function tokenize$1(source) {
624 const scanner = new Scanner(source);
625 const result = [];
626 const ctx = {
627 group: 0,
628 attribute: 0,
629 expression: 0,
630 quote: 0
631 };
632 let ch = 0;
633 let token;
634 while (!scanner.eof()) {
635 ch = scanner.peek();
636 token = getToken$1(scanner, ctx);
637 if (token) {
638 result.push(token);
639 if (token.type === 'Quote') {
640 ctx.quote = ch === ctx.quote ? 0 : ch;
641 }
642 else if (token.type === 'Bracket') {
643 ctx[token.context] += token.open ? 1 : -1;
644 }
645 }
646 else {
647 throw scanner.error('Unexpected character');
648 }
649 }
650 return result;
651}
652/**
653 * Returns next token from given scanner, if possible
654 */
655function getToken$1(scanner, ctx) {
656 return field$2(scanner, ctx)
657 || repeaterPlaceholder(scanner)
658 || repeaterNumber(scanner)
659 || repeater(scanner)
660 || whiteSpace$1(scanner)
661 || literal$2(scanner, ctx)
662 || operator$1(scanner)
663 || quote(scanner)
664 || bracket$1(scanner);
665}
666/**
667 * Consumes literal from given scanner
668 */
669function literal$2(scanner, ctx) {
670 const start = scanner.pos;
671 const expressionStart = ctx.expression;
672 let value = '';
673 while (!scanner.eof()) {
674 // Consume escaped sequence no matter of context
675 if (escaped(scanner)) {
676 value += scanner.current();
677 continue;
678 }
679 const ch = scanner.peek();
680 if (ch === Chars$3.Slash && !ctx.quote && !ctx.expression && !ctx.attribute) {
681 // Special case for `/` character between numbers in class names
682 const prev = scanner.string.charCodeAt(scanner.pos - 1);
683 const next = scanner.string.charCodeAt(scanner.pos + 1);
684 if (isNumber$1(prev) && isNumber$1(next)) {
685 value += scanner.string[scanner.pos++];
686 continue;
687 }
688 }
689 if (ch === ctx.quote || ch === Chars$3.Dollar || isAllowedOperator(ch, ctx)) {
690 // 1. Found matching quote
691 // 2. The `$` character has special meaning in every context
692 // 3. Depending on context, some characters should be treated as operators
693 break;
694 }
695 if (expressionStart) {
696 // Consume nested expressions, e.g. span{{foo}}
697 if (ch === Chars$3.CurlyBracketOpen) {
698 ctx.expression++;
699 }
700 else if (ch === Chars$3.CurlyBracketClose) {
701 if (ctx.expression > expressionStart) {
702 ctx.expression--;
703 }
704 else {
705 break;
706 }
707 }
708 }
709 else if (!ctx.quote) {
710 // Consuming element name
711 if (!ctx.attribute && !isElementName(ch)) {
712 break;
713 }
714 if (isAllowedSpace(ch, ctx) || isAllowedRepeater(ch, ctx) || isQuote$2(ch) || bracketType(ch)) {
715 // Stop for characters not allowed in unquoted literal
716 break;
717 }
718 }
719 value += scanner.string[scanner.pos++];
720 }
721 if (start !== scanner.pos) {
722 scanner.start = start;
723 return {
724 type: 'Literal',
725 value,
726 start,
727 end: scanner.pos
728 };
729 }
730}
731/**
732 * Consumes white space characters as string literal from given scanner
733 */
734function whiteSpace$1(scanner) {
735 const start = scanner.pos;
736 if (scanner.eatWhile(isSpace)) {
737 return {
738 type: 'WhiteSpace',
739 start,
740 end: scanner.pos,
741 value: scanner.substring(start, scanner.pos)
742 };
743 }
744}
745/**
746 * Consumes quote from given scanner
747 */
748function quote(scanner) {
749 const ch = scanner.peek();
750 if (isQuote$2(ch)) {
751 return {
752 type: 'Quote',
753 single: ch === Chars$3.SingleQuote,
754 start: scanner.pos++,
755 end: scanner.pos
756 };
757 }
758}
759/**
760 * Consumes bracket from given scanner
761 */
762function bracket$1(scanner) {
763 const ch = scanner.peek();
764 const context = bracketType(ch);
765 if (context) {
766 return {
767 type: 'Bracket',
768 open: isOpenBracket$2(ch),
769 context,
770 start: scanner.pos++,
771 end: scanner.pos
772 };
773 }
774}
775/**
776 * Consumes operator from given scanner
777 */
778function operator$1(scanner) {
779 const op = operatorType$1(scanner.peek());
780 if (op) {
781 return {
782 type: 'Operator',
783 operator: op,
784 start: scanner.pos++,
785 end: scanner.pos
786 };
787 }
788}
789/**
790 * Consumes node repeat token from current stream position and returns its
791 * parsed value
792 */
793function repeater(scanner) {
794 const start = scanner.pos;
795 if (scanner.eat(Chars$3.Asterisk)) {
796 scanner.start = scanner.pos;
797 let count = 1;
798 let implicit = false;
799 if (scanner.eatWhile(isNumber$1)) {
800 count = Number(scanner.current());
801 }
802 else {
803 implicit = true;
804 }
805 return {
806 type: 'Repeater',
807 count,
808 value: 0,
809 implicit,
810 start,
811 end: scanner.pos
812 };
813 }
814}
815/**
816 * Consumes repeater placeholder `$#` from given scanner
817 */
818function repeaterPlaceholder(scanner) {
819 const start = scanner.pos;
820 if (scanner.eat(Chars$3.Dollar) && scanner.eat(Chars$3.Hash)) {
821 return {
822 type: 'RepeaterPlaceholder',
823 value: void 0,
824 start,
825 end: scanner.pos
826 };
827 }
828 scanner.pos = start;
829}
830/**
831 * Consumes numbering token like `$` from given scanner state
832 */
833function repeaterNumber(scanner) {
834 const start = scanner.pos;
835 if (scanner.eatWhile(Chars$3.Dollar)) {
836 const size = scanner.pos - start;
837 let reverse = false;
838 let base = 1;
839 let parent = 0;
840 if (scanner.eat(Chars$3.At)) {
841 // Consume numbering modifiers
842 while (scanner.eat(Chars$3.Climb)) {
843 parent++;
844 }
845 reverse = scanner.eat(Chars$3.Dash);
846 scanner.start = scanner.pos;
847 if (scanner.eatWhile(isNumber$1)) {
848 base = Number(scanner.current());
849 }
850 }
851 scanner.start = start;
852 return {
853 type: 'RepeaterNumber',
854 size,
855 reverse,
856 base,
857 parent,
858 start,
859 end: scanner.pos
860 };
861 }
862}
863function field$2(scanner, ctx) {
864 const start = scanner.pos;
865 // Fields are allowed inside expressions and attributes
866 if ((ctx.expression || ctx.attribute) && scanner.eat(Chars$3.Dollar) && scanner.eat(Chars$3.CurlyBracketOpen)) {
867 scanner.start = scanner.pos;
868 let index;
869 let name = '';
870 if (scanner.eatWhile(isNumber$1)) {
871 // It’s a field
872 index = Number(scanner.current());
873 name = scanner.eat(Chars$3.Colon) ? consumePlaceholder$2(scanner) : '';
874 }
875 else if (isAlpha$1(scanner.peek())) {
876 // It’s a variable
877 name = consumePlaceholder$2(scanner);
878 }
879 if (scanner.eat(Chars$3.CurlyBracketClose)) {
880 return {
881 type: 'Field',
882 index, name,
883 start,
884 end: scanner.pos
885 };
886 }
887 throw scanner.error('Expecting }');
888 }
889 // If we reached here then there’s no valid field here, revert
890 // back to starting position
891 scanner.pos = start;
892}
893/**
894 * Consumes a placeholder: value right after `:` in field. Could be empty
895 */
896function consumePlaceholder$2(stream) {
897 const stack = [];
898 stream.start = stream.pos;
899 while (!stream.eof()) {
900 if (stream.eat(Chars$3.CurlyBracketOpen)) {
901 stack.push(stream.pos);
902 }
903 else if (stream.eat(Chars$3.CurlyBracketClose)) {
904 if (!stack.length) {
905 stream.pos--;
906 break;
907 }
908 stack.pop();
909 }
910 else {
911 stream.pos++;
912 }
913 }
914 if (stack.length) {
915 stream.pos = stack.pop();
916 throw stream.error(`Expecting }`);
917 }
918 return stream.current();
919}
920/**
921 * Check if given character code is an operator and it’s allowed in current context
922 */
923function isAllowedOperator(ch, ctx) {
924 const op = operatorType$1(ch);
925 if (!op || ctx.quote || ctx.expression) {
926 // No operators inside quoted values or expressions
927 return false;
928 }
929 // Inside attributes, only `equals` is allowed
930 return !ctx.attribute || op === 'equal';
931}
932/**
933 * Check if given character is a space character and is allowed to be consumed
934 * as a space token in current context
935 */
936function isAllowedSpace(ch, ctx) {
937 return isSpace(ch) && !ctx.expression;
938}
939/**
940 * Check if given character can be consumed as repeater in current context
941 */
942function isAllowedRepeater(ch, ctx) {
943 return ch === Chars$3.Asterisk && !ctx.attribute && !ctx.expression;
944}
945/**
946 * If given character is a bracket, returns it’s type
947 */
948function bracketType(ch) {
949 if (ch === Chars$3.RoundBracketOpen || ch === Chars$3.RoundBracketClose) {
950 return 'group';
951 }
952 if (ch === Chars$3.SquareBracketOpen || ch === Chars$3.SquareBracketClose) {
953 return 'attribute';
954 }
955 if (ch === Chars$3.CurlyBracketOpen || ch === Chars$3.CurlyBracketClose) {
956 return 'expression';
957 }
958}
959/**
960 * If given character is an operator, returns it’s type
961 */
962function operatorType$1(ch) {
963 return (ch === Chars$3.Child && 'child')
964 || (ch === Chars$3.Sibling && 'sibling')
965 || (ch === Chars$3.Climb && 'climb')
966 || (ch === Chars$3.Dot && 'class')
967 || (ch === Chars$3.Hash && 'id')
968 || (ch === Chars$3.Slash && 'close')
969 || (ch === Chars$3.Equals && 'equal')
970 || void 0;
971}
972/**
973 * Check if given character is an open bracket
974 */
975function isOpenBracket$2(ch) {
976 return ch === Chars$3.CurlyBracketOpen
977 || ch === Chars$3.SquareBracketOpen
978 || ch === Chars$3.RoundBracketOpen;
979}
980/**
981 * Check if given character is allowed in element name
982 */
983function isElementName(ch) {
984 return isAlphaNumericWord(ch)
985 || isUmlaut(ch)
986 || ch === Chars$3.Dash
987 || ch === Chars$3.Colon
988 || ch === Chars$3.Excl;
989}
990
991const operators = {
992 child: '>',
993 class: '.',
994 climb: '^',
995 id: '#',
996 equal: '=',
997 close: '/',
998 sibling: '+'
999};
1000const tokenVisitor = {
1001 Literal(token) {
1002 return token.value;
1003 },
1004 Quote(token) {
1005 return token.single ? '\'' : '"';
1006 },
1007 Bracket(token) {
1008 if (token.context === 'attribute') {
1009 return token.open ? '[' : ']';
1010 }
1011 else if (token.context === 'expression') {
1012 return token.open ? '{' : '}';
1013 }
1014 else {
1015 return token.open ? '(' : '}';
1016 }
1017 },
1018 Operator(token) {
1019 return operators[token.operator];
1020 },
1021 Field(token, state) {
1022 if (token.index != null) {
1023 // It’s a field: by default, return TextMate-compatible field
1024 return token.name
1025 ? `\${${token.index}:${token.name}}`
1026 : `\${${token.index}`;
1027 }
1028 else if (token.name) {
1029 // It’s a variable
1030 return state.getVariable(token.name);
1031 }
1032 return '';
1033 },
1034 RepeaterPlaceholder(token, state) {
1035 // Find closest implicit repeater
1036 let repeater;
1037 for (let i = state.repeaters.length - 1; i >= 0; i--) {
1038 if (state.repeaters[i].implicit) {
1039 repeater = state.repeaters[i];
1040 break;
1041 }
1042 }
1043 state.inserted = true;
1044 return state.getText(repeater && repeater.value);
1045 },
1046 RepeaterNumber(token, state) {
1047 let value = 1;
1048 const lastIx = state.repeaters.length - 1;
1049 // const repeaterIx = Math.max(0, state.repeaters.length - 1 - token.parent);
1050 const repeater = state.repeaters[lastIx];
1051 if (repeater) {
1052 value = token.reverse
1053 ? token.base + repeater.count - repeater.value - 1
1054 : token.base + repeater.value;
1055 if (token.parent) {
1056 const parentIx = Math.max(0, lastIx - token.parent);
1057 if (parentIx !== lastIx) {
1058 const parentRepeater = state.repeaters[parentIx];
1059 value += repeater.count * parentRepeater.value;
1060 }
1061 }
1062 }
1063 let result = String(value);
1064 while (result.length < token.size) {
1065 result = '0' + result;
1066 }
1067 return result;
1068 },
1069 WhiteSpace(token) {
1070 return token.value;
1071 }
1072};
1073/**
1074 * Converts given value token to string
1075 */
1076function stringify$1(token, state) {
1077 if (!tokenVisitor[token.type]) {
1078 throw new Error(`Unknown token ${token.type}`);
1079 }
1080 return tokenVisitor[token.type](token, state);
1081}
1082
1083const urlRegex = /^((https?:|ftp:|file:)?\/\/|(www|ftp)\.)[^ ]*$/;
1084const emailRegex = /^[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,5}$/;
1085/**
1086 * Converts given token-based abbreviation into simplified and unrolled node-based
1087 * abbreviation
1088 */
1089function convert(abbr, options = {}) {
1090 let textInserted = false;
1091 let cleanText;
1092 if (options.text) {
1093 if (Array.isArray(options.text)) {
1094 cleanText = options.text.filter(s => s.trim());
1095 }
1096 else {
1097 cleanText = options.text;
1098 }
1099 }
1100 const result = {
1101 type: 'Abbreviation',
1102 children: convertGroup(abbr, {
1103 inserted: false,
1104 repeaters: [],
1105 text: options.text,
1106 cleanText,
1107 repeatGuard: options.maxRepeat || Number.POSITIVE_INFINITY,
1108 getText(pos) {
1109 var _a;
1110 textInserted = true;
1111 let value;
1112 if (Array.isArray(options.text)) {
1113 if (pos !== undefined && pos >= 0 && pos < cleanText.length) {
1114 return cleanText[pos];
1115 }
1116 value = pos !== undefined ? options.text[pos] : options.text.join('\n');
1117 }
1118 else {
1119 value = (_a = options.text) !== null && _a !== void 0 ? _a : '';
1120 }
1121 return value;
1122 },
1123 getVariable(name) {
1124 const varValue = options.variables && options.variables[name];
1125 return varValue != null ? varValue : name;
1126 }
1127 })
1128 };
1129 if (options.text != null && !textInserted) {
1130 // Text given but no implicitly repeated elements: insert it into
1131 // deepest child
1132 const deepest = deepestNode(last$1(result.children));
1133 if (deepest) {
1134 const text = Array.isArray(options.text) ? options.text.join('\n') : options.text;
1135 insertText(deepest, text);
1136 if (deepest.name === 'a' && options.href) {
1137 // Automatically update value of `<a>` element if inserting URL or email
1138 insertHref(deepest, text);
1139 }
1140 }
1141 }
1142 return result;
1143}
1144/**
1145 * Converts given statement to abbreviation nodes
1146 */
1147function convertStatement(node, state) {
1148 let result = [];
1149 if (node.repeat) {
1150 // Node is repeated: we should create copies of given node
1151 // and supply context token with actual repeater state
1152 const original = node.repeat;
1153 const repeat = Object.assign({}, original);
1154 repeat.count = repeat.implicit && Array.isArray(state.text)
1155 ? state.cleanText.length
1156 : (repeat.count || 1);
1157 let items;
1158 state.repeaters.push(repeat);
1159 for (let i = 0; i < repeat.count; i++) {
1160 repeat.value = i;
1161 node.repeat = repeat;
1162 items = isGroup(node)
1163 ? convertGroup(node, state)
1164 : convertElement(node, state);
1165 if (repeat.implicit && !state.inserted) {
1166 // It’s an implicit repeater but no repeater placeholders found inside,
1167 // we should insert text into deepest node
1168 const target = last$1(items);
1169 const deepest = target && deepestNode(target);
1170 if (deepest) {
1171 insertText(deepest, state.getText(repeat.value));
1172 }
1173 }
1174 result = result.concat(items);
1175 // We should output at least one repeated item even if it’s reached
1176 // repeat limit
1177 if (--state.repeatGuard <= 0) {
1178 break;
1179 }
1180 }
1181 state.repeaters.pop();
1182 node.repeat = original;
1183 if (repeat.implicit) {
1184 state.inserted = true;
1185 }
1186 }
1187 else {
1188 result = result.concat(isGroup(node) ? convertGroup(node, state) : convertElement(node, state));
1189 }
1190 return result;
1191}
1192function convertElement(node, state) {
1193 let children = [];
1194 const elem = {
1195 type: 'AbbreviationNode',
1196 name: node.name && stringifyName(node.name, state),
1197 value: node.value && stringifyValue$1(node.value, state),
1198 attributes: void 0,
1199 children,
1200 repeat: node.repeat && Object.assign({}, node.repeat),
1201 selfClosing: node.selfClose,
1202 };
1203 let result = [elem];
1204 for (const child of node.elements) {
1205 children = children.concat(convertStatement(child, state));
1206 }
1207 if (node.attributes) {
1208 elem.attributes = [];
1209 for (const attr of node.attributes) {
1210 elem.attributes.push(convertAttribute(attr, state));
1211 }
1212 }
1213 // In case if current node is a text-only snippet without fields, we should
1214 // put all children as siblings
1215 if (!elem.name && !elem.attributes && elem.value && !elem.value.some(isField$1)) {
1216 // XXX it’s unclear that `children` is not bound to `elem`
1217 // due to concat operation
1218 result = result.concat(children);
1219 }
1220 else {
1221 elem.children = children;
1222 }
1223 return result;
1224}
1225function convertGroup(node, state) {
1226 let result = [];
1227 for (const child of node.elements) {
1228 result = result.concat(convertStatement(child, state));
1229 }
1230 if (node.repeat) {
1231 result = attachRepeater(result, node.repeat);
1232 }
1233 return result;
1234}
1235function convertAttribute(node, state) {
1236 let implied = false;
1237 let isBoolean = false;
1238 let valueType = node.expression ? 'expression' : 'raw';
1239 let value;
1240 const name = node.name && stringifyName(node.name, state);
1241 if (name && name[0] === '!') {
1242 implied = true;
1243 }
1244 if (name && name[name.length - 1] === '.') {
1245 isBoolean = true;
1246 }
1247 if (node.value) {
1248 const tokens = node.value.slice();
1249 if (isQuote$1(tokens[0])) {
1250 // It’s a quoted value: remove quotes from output but mark attribute
1251 // value as quoted
1252 const quote = tokens.shift();
1253 if (tokens.length && last$1(tokens).type === quote.type) {
1254 tokens.pop();
1255 }
1256 valueType = quote.single ? 'singleQuote' : 'doubleQuote';
1257 }
1258 else if (isBracket$2(tokens[0], 'expression', true)) {
1259 // Value is expression: remove brackets but mark value type
1260 valueType = 'expression';
1261 tokens.shift();
1262 if (isBracket$2(last$1(tokens), 'expression', false)) {
1263 tokens.pop();
1264 }
1265 }
1266 value = stringifyValue$1(tokens, state);
1267 }
1268 return {
1269 name: isBoolean || implied
1270 ? name.slice(implied ? 1 : 0, isBoolean ? -1 : void 0)
1271 : name,
1272 value,
1273 boolean: isBoolean,
1274 implied,
1275 valueType,
1276 multiple: node.multiple
1277 };
1278}
1279/**
1280 * Converts given token list to string
1281 */
1282function stringifyName(tokens, state) {
1283 let str = '';
1284 for (let i = 0; i < tokens.length; i++) {
1285 str += stringify$1(tokens[i], state);
1286 }
1287 return str;
1288}
1289/**
1290 * Converts given token list to value list
1291 */
1292function stringifyValue$1(tokens, state) {
1293 const result = [];
1294 let str = '';
1295 for (let i = 0, token; i < tokens.length; i++) {
1296 token = tokens[i];
1297 if (isField$1(token)) {
1298 // We should keep original fields in output since some editors has their
1299 // own syntax for field or doesn’t support fields at all so we should
1300 // capture actual field location in output stream
1301 if (str) {
1302 result.push(str);
1303 str = '';
1304 }
1305 result.push(token);
1306 }
1307 else {
1308 str += stringify$1(token, state);
1309 }
1310 }
1311 if (str) {
1312 result.push(str);
1313 }
1314 return result;
1315}
1316function isGroup(node) {
1317 return node.type === 'TokenGroup';
1318}
1319function isField$1(token) {
1320 return typeof token === 'object' && token.type === 'Field' && token.index != null;
1321}
1322function last$1(arr) {
1323 return arr[arr.length - 1];
1324}
1325function deepestNode(node) {
1326 return node.children.length ? deepestNode(last$1(node.children)) : node;
1327}
1328function insertText(node, text) {
1329 if (node.value) {
1330 const lastToken = last$1(node.value);
1331 if (typeof lastToken === 'string') {
1332 node.value[node.value.length - 1] += text;
1333 }
1334 else {
1335 node.value.push(text);
1336 }
1337 }
1338 else {
1339 node.value = [text];
1340 }
1341}
1342function insertHref(node, text) {
1343 var _a;
1344 let href = '';
1345 if (urlRegex.test(text)) {
1346 href = text;
1347 if (!/\w+:/.test(href) && !href.startsWith('//')) {
1348 href = `http://${href}`;
1349 }
1350 }
1351 else if (emailRegex.test(text)) {
1352 href = `mailto:${text}`;
1353 }
1354 const hrefAttribute = (_a = node.attributes) === null || _a === void 0 ? void 0 : _a.find(attr => attr.name === 'href');
1355 if (!hrefAttribute) {
1356 if (!node.attributes) {
1357 node.attributes = [];
1358 }
1359 node.attributes.push({ name: 'href', value: [href], valueType: 'doubleQuote' });
1360 }
1361 else if (!hrefAttribute.value) {
1362 hrefAttribute.value = [href];
1363 }
1364}
1365function attachRepeater(items, repeater) {
1366 for (const item of items) {
1367 if (!item.repeat) {
1368 item.repeat = Object.assign({}, repeater);
1369 }
1370 }
1371 return items;
1372}
1373
1374/**
1375 * Parses given abbreviation into node tree
1376 */
1377function parseAbbreviation(abbr, options) {
1378 try {
1379 const tokens = typeof abbr === 'string' ? tokenize$1(abbr) : abbr;
1380 return convert(abbreviation(tokens, options), options);
1381 }
1382 catch (err) {
1383 if (err instanceof ScannerError && typeof abbr === 'string') {
1384 err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
1385 }
1386 throw err;
1387 }
1388}
1389
1390var OperatorType;
1391(function (OperatorType) {
1392 OperatorType["Sibling"] = "+";
1393 OperatorType["Important"] = "!";
1394 OperatorType["ArgumentDelimiter"] = ",";
1395 OperatorType["ValueDelimiter"] = "-";
1396 OperatorType["PropertyDelimiter"] = ":";
1397})(OperatorType || (OperatorType = {}));
1398
1399var Chars$2;
1400(function (Chars) {
1401 /** `#` character */
1402 Chars[Chars["Hash"] = 35] = "Hash";
1403 /** `$` character */
1404 Chars[Chars["Dollar"] = 36] = "Dollar";
1405 /** `-` character */
1406 Chars[Chars["Dash"] = 45] = "Dash";
1407 /** `.` character */
1408 Chars[Chars["Dot"] = 46] = "Dot";
1409 /** `:` character */
1410 Chars[Chars["Colon"] = 58] = "Colon";
1411 /** `,` character */
1412 Chars[Chars["Comma"] = 44] = "Comma";
1413 /** `!` character */
1414 Chars[Chars["Excl"] = 33] = "Excl";
1415 /** `@` character */
1416 Chars[Chars["At"] = 64] = "At";
1417 /** `%` character */
1418 Chars[Chars["Percent"] = 37] = "Percent";
1419 /** `_` character */
1420 Chars[Chars["Underscore"] = 95] = "Underscore";
1421 /** `(` character */
1422 Chars[Chars["RoundBracketOpen"] = 40] = "RoundBracketOpen";
1423 /** `)` character */
1424 Chars[Chars["RoundBracketClose"] = 41] = "RoundBracketClose";
1425 /** `{` character */
1426 Chars[Chars["CurlyBracketOpen"] = 123] = "CurlyBracketOpen";
1427 /** `}` character */
1428 Chars[Chars["CurlyBracketClose"] = 125] = "CurlyBracketClose";
1429 /** `+` character */
1430 Chars[Chars["Sibling"] = 43] = "Sibling";
1431 /** `'` character */
1432 Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
1433 /** `"` character */
1434 Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
1435 /** `t` character */
1436 Chars[Chars["Transparent"] = 116] = "Transparent";
1437 /** `/` character */
1438 Chars[Chars["Slash"] = 47] = "Slash";
1439})(Chars$2 || (Chars$2 = {}));
1440
1441function tokenize(abbr, isValue) {
1442 let brackets = 0;
1443 let token;
1444 const scanner = new Scanner(abbr);
1445 const tokens = [];
1446 while (!scanner.eof()) {
1447 token = getToken(scanner, brackets === 0 && !isValue);
1448 if (!token) {
1449 throw scanner.error('Unexpected character');
1450 }
1451 if (token.type === 'Bracket') {
1452 if (!brackets && token.open) {
1453 mergeTokens(scanner, tokens);
1454 }
1455 brackets += token.open ? 1 : -1;
1456 if (brackets < 0) {
1457 throw scanner.error('Unexpected bracket', token.start);
1458 }
1459 }
1460 tokens.push(token);
1461 // Forcibly consume next operator after unit-less numeric value or color:
1462 // next dash `-` must be used as value delimiter
1463 if (shouldConsumeDashAfter(token) && (token = operator(scanner))) {
1464 tokens.push(token);
1465 }
1466 }
1467 return tokens;
1468}
1469/**
1470 * Returns next token from given scanner, if possible
1471 */
1472function getToken(scanner, short) {
1473 return field$1(scanner)
1474 || customProperty(scanner)
1475 || numberValue(scanner)
1476 || colorValue(scanner)
1477 || stringValue(scanner)
1478 || bracket(scanner)
1479 || operator(scanner)
1480 || whiteSpace(scanner)
1481 || literal$1(scanner, short);
1482}
1483function field$1(scanner) {
1484 const start = scanner.pos;
1485 if (scanner.eat(Chars$2.Dollar) && scanner.eat(Chars$2.CurlyBracketOpen)) {
1486 scanner.start = scanner.pos;
1487 let index;
1488 let name = '';
1489 if (scanner.eatWhile(isNumber$1)) {
1490 // It’s a field
1491 index = Number(scanner.current());
1492 name = scanner.eat(Chars$2.Colon) ? consumePlaceholder$1(scanner) : '';
1493 }
1494 else if (isAlpha$1(scanner.peek())) {
1495 // It’s a variable
1496 name = consumePlaceholder$1(scanner);
1497 }
1498 if (scanner.eat(Chars$2.CurlyBracketClose)) {
1499 return {
1500 type: 'Field',
1501 index, name,
1502 start,
1503 end: scanner.pos
1504 };
1505 }
1506 throw scanner.error('Expecting }');
1507 }
1508 // If we reached here then there’s no valid field here, revert
1509 // back to starting position
1510 scanner.pos = start;
1511}
1512/**
1513 * Consumes a placeholder: value right after `:` in field. Could be empty
1514 */
1515function consumePlaceholder$1(stream) {
1516 const stack = [];
1517 stream.start = stream.pos;
1518 while (!stream.eof()) {
1519 if (stream.eat(Chars$2.CurlyBracketOpen)) {
1520 stack.push(stream.pos);
1521 }
1522 else if (stream.eat(Chars$2.CurlyBracketClose)) {
1523 if (!stack.length) {
1524 stream.pos--;
1525 break;
1526 }
1527 stack.pop();
1528 }
1529 else {
1530 stream.pos++;
1531 }
1532 }
1533 if (stack.length) {
1534 stream.pos = stack.pop();
1535 throw stream.error(`Expecting }`);
1536 }
1537 return stream.current();
1538}
1539/**
1540 * Consumes literal from given scanner
1541 * @param short Use short notation for consuming value.
1542 * The difference between “short” and “full” notation is that first one uses
1543 * alpha characters only and used for extracting keywords from abbreviation,
1544 * while “full” notation also supports numbers and dashes
1545 */
1546function literal$1(scanner, short) {
1547 const start = scanner.pos;
1548 if (scanner.eat(isIdentPrefix)) {
1549 // SCSS or LESS variable
1550 // NB a bit dirty hack: if abbreviation starts with identifier prefix,
1551 // consume alpha characters only to allow embedded variables
1552 scanner.eatWhile(start ? isKeyword : isLiteral$1);
1553 }
1554 else if (scanner.eat(isAlphaWord)) {
1555 scanner.eatWhile(short ? isLiteral$1 : isKeyword);
1556 }
1557 else {
1558 // Allow dots only at the beginning of literal
1559 scanner.eat(Chars$2.Dot);
1560 scanner.eatWhile(isLiteral$1);
1561 }
1562 if (start !== scanner.pos) {
1563 scanner.start = start;
1564 return createLiteral(scanner, scanner.start = start);
1565 }
1566}
1567function createLiteral(scanner, start = scanner.start, end = scanner.pos) {
1568 return {
1569 type: 'Literal',
1570 value: scanner.substring(start, end),
1571 start,
1572 end
1573 };
1574}
1575/**
1576 * Consumes numeric CSS value (number with optional unit) from current stream,
1577 * if possible
1578 */
1579function numberValue(scanner) {
1580 const start = scanner.pos;
1581 if (consumeNumber(scanner)) {
1582 scanner.start = start;
1583 const rawValue = scanner.current();
1584 // eat unit, which can be a % or alpha word
1585 scanner.start = scanner.pos;
1586 scanner.eat(Chars$2.Percent) || scanner.eatWhile(isAlphaWord);
1587 return {
1588 type: 'NumberValue',
1589 value: Number(rawValue),
1590 rawValue,
1591 unit: scanner.current(),
1592 start,
1593 end: scanner.pos
1594 };
1595 }
1596}
1597/**
1598 * Consumes quoted string value from given scanner
1599 */
1600function stringValue(scanner) {
1601 const ch = scanner.peek();
1602 const start = scanner.pos;
1603 let finished = false;
1604 if (isQuote$2(ch)) {
1605 scanner.pos++;
1606 while (!scanner.eof()) {
1607 // Do not throw error on malformed string
1608 if (scanner.eat(ch)) {
1609 finished = true;
1610 break;
1611 }
1612 else {
1613 scanner.pos++;
1614 }
1615 }
1616 scanner.start = start;
1617 return {
1618 type: 'StringValue',
1619 value: scanner.substring(start + 1, scanner.pos - (finished ? 1 : 0)),
1620 quote: ch === Chars$2.SingleQuote ? 'single' : 'double',
1621 start,
1622 end: scanner.pos
1623 };
1624 }
1625}
1626/**
1627 * Consumes a color token from given string
1628 */
1629function colorValue(scanner) {
1630 // supported color variations:
1631 // #abc → #aabbccc
1632 // #0 → #000000
1633 // #fff.5 → rgba(255, 255, 255, 0.5)
1634 // #t → transparent
1635 const start = scanner.pos;
1636 if (scanner.eat(Chars$2.Hash)) {
1637 const valueStart = scanner.pos;
1638 let color = '';
1639 let alpha = '';
1640 if (scanner.eatWhile(isHex)) {
1641 color = scanner.substring(valueStart, scanner.pos);
1642 alpha = colorAlpha(scanner);
1643 }
1644 else if (scanner.eat(Chars$2.Transparent)) {
1645 color = '0';
1646 alpha = colorAlpha(scanner) || '0';
1647 }
1648 else {
1649 alpha = colorAlpha(scanner);
1650 }
1651 if (color || alpha || scanner.eof()) {
1652 const { r, g, b, a } = parseColor(color, alpha);
1653 return {
1654 type: 'ColorValue',
1655 r, g, b, a,
1656 raw: scanner.substring(start + 1, scanner.pos),
1657 start,
1658 end: scanner.pos
1659 };
1660 }
1661 else {
1662 // Consumed # but no actual value: invalid color value, treat it as literal
1663 return createLiteral(scanner, start);
1664 }
1665 }
1666 scanner.pos = start;
1667}
1668/**
1669 * Consumes alpha value of color: `.1`
1670 */
1671function colorAlpha(scanner) {
1672 const start = scanner.pos;
1673 if (scanner.eat(Chars$2.Dot)) {
1674 scanner.start = start;
1675 if (scanner.eatWhile(isNumber$1)) {
1676 return scanner.current();
1677 }
1678 return '1';
1679 }
1680 return '';
1681}
1682/**
1683 * Consumes white space characters as string literal from given scanner
1684 */
1685function whiteSpace(scanner) {
1686 const start = scanner.pos;
1687 if (scanner.eatWhile(isSpace)) {
1688 return {
1689 type: 'WhiteSpace',
1690 start,
1691 end: scanner.pos
1692 };
1693 }
1694}
1695/**
1696 * Consumes custom CSS property: --foo-bar
1697 */
1698function customProperty(scanner) {
1699 const start = scanner.pos;
1700 if (scanner.eat(Chars$2.Dash) && scanner.eat(Chars$2.Dash)) {
1701 scanner.start = start;
1702 scanner.eatWhile(isKeyword);
1703 return {
1704 type: 'CustomProperty',
1705 value: scanner.current(),
1706 start,
1707 end: scanner.pos
1708 };
1709 }
1710 scanner.pos = start;
1711}
1712/**
1713 * Consumes bracket from given scanner
1714 */
1715function bracket(scanner) {
1716 const ch = scanner.peek();
1717 if (isBracket$1(ch)) {
1718 return {
1719 type: 'Bracket',
1720 open: ch === Chars$2.RoundBracketOpen,
1721 start: scanner.pos++,
1722 end: scanner.pos
1723 };
1724 }
1725}
1726/**
1727 * Consumes operator from given scanner
1728 */
1729function operator(scanner) {
1730 const op = operatorType(scanner.peek());
1731 if (op) {
1732 return {
1733 type: 'Operator',
1734 operator: op,
1735 start: scanner.pos++,
1736 end: scanner.pos
1737 };
1738 }
1739}
1740/**
1741 * Eats number value from given stream
1742 * @return Returns `true` if number was consumed
1743 */
1744function consumeNumber(stream) {
1745 const start = stream.pos;
1746 stream.eat(Chars$2.Dash);
1747 const afterNegative = stream.pos;
1748 const hasDecimal = stream.eatWhile(isNumber$1);
1749 const prevPos = stream.pos;
1750 if (stream.eat(Chars$2.Dot)) {
1751 // It’s perfectly valid to have numbers like `1.`, which enforces
1752 // value to float unit type
1753 const hasFloat = stream.eatWhile(isNumber$1);
1754 if (!hasDecimal && !hasFloat) {
1755 // Lone dot
1756 stream.pos = prevPos;
1757 }
1758 }
1759 // Edge case: consumed dash only: not a number, bail-out
1760 if (stream.pos === afterNegative) {
1761 stream.pos = start;
1762 }
1763 return stream.pos !== start;
1764}
1765function isIdentPrefix(code) {
1766 return code === Chars$2.At || code === Chars$2.Dollar;
1767}
1768/**
1769 * If given character is an operator, returns it’s type
1770 */
1771function operatorType(ch) {
1772 return (ch === Chars$2.Sibling && OperatorType.Sibling)
1773 || (ch === Chars$2.Excl && OperatorType.Important)
1774 || (ch === Chars$2.Comma && OperatorType.ArgumentDelimiter)
1775 || (ch === Chars$2.Colon && OperatorType.PropertyDelimiter)
1776 || (ch === Chars$2.Dash && OperatorType.ValueDelimiter)
1777 || void 0;
1778}
1779/**
1780 * Check if given code is a hex value (/0-9a-f/)
1781 */
1782function isHex(code) {
1783 return isNumber$1(code) || isAlpha$1(code, 65, 70); // A-F
1784}
1785function isKeyword(code) {
1786 return isAlphaNumericWord(code) || code === Chars$2.Dash;
1787}
1788function isBracket$1(code) {
1789 return code === Chars$2.RoundBracketOpen || code === Chars$2.RoundBracketClose;
1790}
1791function isLiteral$1(code) {
1792 return isAlphaWord(code) || code === Chars$2.Percent || code === Chars$2.Slash;
1793}
1794/**
1795 * Parses given color value from abbreviation into RGBA format
1796 */
1797function parseColor(value, alpha) {
1798 let r = '0';
1799 let g = '0';
1800 let b = '0';
1801 let a = Number(alpha != null && alpha !== '' ? alpha : 1);
1802 if (value === 't') {
1803 a = 0;
1804 }
1805 else {
1806 switch (value.length) {
1807 case 0:
1808 break;
1809 case 1:
1810 r = g = b = value + value;
1811 break;
1812 case 2:
1813 r = g = b = value;
1814 break;
1815 case 3:
1816 r = value[0] + value[0];
1817 g = value[1] + value[1];
1818 b = value[2] + value[2];
1819 break;
1820 default:
1821 value += value;
1822 r = value.slice(0, 2);
1823 g = value.slice(2, 4);
1824 b = value.slice(4, 6);
1825 }
1826 }
1827 return {
1828 r: parseInt(r, 16),
1829 g: parseInt(g, 16),
1830 b: parseInt(b, 16),
1831 a
1832 };
1833}
1834/**
1835 * Check if scanner reader must consume dash after given token.
1836 * Used in cases where user must explicitly separate numeric values
1837 */
1838function shouldConsumeDashAfter(token) {
1839 return token.type === 'ColorValue' || (token.type === 'NumberValue' && !token.unit);
1840}
1841/**
1842 * Merges last adjacent tokens into a single literal.
1843 * This function is used to overcome edge case when function name was parsed
1844 * as a list of separate tokens. For example, a `scale3d()` value will be
1845 * parsed as literal and number tokens (`scale` and `3d`) which is a perfectly
1846 * valid abbreviation but undesired result. This function will detect last adjacent
1847 * literal and number values and combine them into single literal
1848 */
1849function mergeTokens(scanner, tokens) {
1850 let start = 0;
1851 let end = 0;
1852 while (tokens.length) {
1853 const token = last(tokens);
1854 if (token.type === 'Literal' || token.type === 'NumberValue') {
1855 start = token.start;
1856 if (!end) {
1857 end = token.end;
1858 }
1859 tokens.pop();
1860 }
1861 else {
1862 break;
1863 }
1864 }
1865 if (start !== end) {
1866 tokens.push(createLiteral(scanner, start, end));
1867 }
1868}
1869function last(arr) {
1870 return arr[arr.length - 1];
1871}
1872
1873function tokenScanner(tokens) {
1874 return {
1875 tokens,
1876 start: 0,
1877 pos: 0,
1878 size: tokens.length
1879 };
1880}
1881function peek$2(scanner) {
1882 return scanner.tokens[scanner.pos];
1883}
1884function readable(scanner) {
1885 return scanner.pos < scanner.size;
1886}
1887function consume$1(scanner, test) {
1888 if (test(peek$2(scanner))) {
1889 scanner.pos++;
1890 return true;
1891 }
1892 return false;
1893}
1894function error(scanner, message, token = peek$2(scanner)) {
1895 if (token && token.start != null) {
1896 message += ` at ${token.start}`;
1897 }
1898 const err = new Error(message);
1899 err['pos'] = token && token.start;
1900 return err;
1901}
1902
1903function parser(tokens, options = {}) {
1904 const scanner = tokenScanner(tokens);
1905 const result = [];
1906 let property;
1907 while (readable(scanner)) {
1908 if (property = consumeProperty(scanner, options)) {
1909 result.push(property);
1910 }
1911 else if (!consume$1(scanner, isSiblingOperator)) {
1912 throw error(scanner, 'Unexpected token');
1913 }
1914 }
1915 return result;
1916}
1917/**
1918 * Consumes single CSS property
1919 */
1920function consumeProperty(scanner, options) {
1921 let name;
1922 let important = false;
1923 let valueFragment;
1924 const value = [];
1925 const token = peek$2(scanner);
1926 const valueMode = !!options.value;
1927 if (!valueMode && isLiteral(token) && !isFunctionStart(scanner)) {
1928 scanner.pos++;
1929 name = token.value;
1930 // Consume any following value delimiter after property name
1931 consume$1(scanner, isValueDelimiter);
1932 }
1933 // Skip whitespace right after property name, if any
1934 if (valueMode) {
1935 consume$1(scanner, isWhiteSpace$1);
1936 }
1937 while (readable(scanner)) {
1938 if (consume$1(scanner, isImportant)) {
1939 important = true;
1940 }
1941 else if (valueFragment = consumeValue(scanner, valueMode)) {
1942 value.push(valueFragment);
1943 }
1944 else if (!consume$1(scanner, isFragmentDelimiter)) {
1945 break;
1946 }
1947 }
1948 if (name || value.length || important) {
1949 return { name, value, important };
1950 }
1951}
1952/**
1953 * Consumes single value fragment, e.g. all value tokens before comma
1954 */
1955function consumeValue(scanner, inArgument) {
1956 const result = [];
1957 let token;
1958 let args;
1959 while (readable(scanner)) {
1960 token = peek$2(scanner);
1961 if (isValue(token)) {
1962 scanner.pos++;
1963 if (isLiteral(token) && (args = consumeArguments(scanner))) {
1964 result.push({
1965 type: 'FunctionCall',
1966 name: token.value,
1967 arguments: args
1968 });
1969 }
1970 else {
1971 result.push(token);
1972 }
1973 }
1974 else if (isValueDelimiter(token) || (inArgument && isWhiteSpace$1(token))) {
1975 scanner.pos++;
1976 }
1977 else {
1978 break;
1979 }
1980 }
1981 return result.length
1982 ? { type: 'CSSValue', value: result }
1983 : void 0;
1984}
1985function consumeArguments(scanner) {
1986 const start = scanner.pos;
1987 if (consume$1(scanner, isOpenBracket$1)) {
1988 const args = [];
1989 let value;
1990 while (readable(scanner) && !consume$1(scanner, isCloseBracket$1)) {
1991 if (value = consumeValue(scanner, true)) {
1992 args.push(value);
1993 }
1994 else if (!consume$1(scanner, isWhiteSpace$1) && !consume$1(scanner, isArgumentDelimiter)) {
1995 throw error(scanner, 'Unexpected token');
1996 }
1997 }
1998 scanner.start = start;
1999 return args;
2000 }
2001}
2002function isLiteral(token) {
2003 return token && token.type === 'Literal';
2004}
2005function isBracket(token, open) {
2006 return token && token.type === 'Bracket' && (open == null || token.open === open);
2007}
2008function isOpenBracket$1(token) {
2009 return isBracket(token, true);
2010}
2011function isCloseBracket$1(token) {
2012 return isBracket(token, false);
2013}
2014function isWhiteSpace$1(token) {
2015 return token && token.type === 'WhiteSpace';
2016}
2017function isOperator(token, operator) {
2018 return token && token.type === 'Operator' && (!operator || token.operator === operator);
2019}
2020function isSiblingOperator(token) {
2021 return isOperator(token, OperatorType.Sibling);
2022}
2023function isArgumentDelimiter(token) {
2024 return isOperator(token, OperatorType.ArgumentDelimiter);
2025}
2026function isFragmentDelimiter(token) {
2027 return isArgumentDelimiter(token);
2028}
2029function isImportant(token) {
2030 return isOperator(token, OperatorType.Important);
2031}
2032function isValue(token) {
2033 return token.type === 'StringValue'
2034 || token.type === 'ColorValue'
2035 || token.type === 'NumberValue'
2036 || token.type === 'Literal'
2037 || token.type === 'Field'
2038 || token.type === 'CustomProperty';
2039}
2040function isValueDelimiter(token) {
2041 return isOperator(token, OperatorType.PropertyDelimiter)
2042 || isOperator(token, OperatorType.ValueDelimiter);
2043}
2044function isFunctionStart(scanner) {
2045 const t1 = scanner.tokens[scanner.pos];
2046 const t2 = scanner.tokens[scanner.pos + 1];
2047 return t1 && t2 && isLiteral(t1) && t2.type === 'Bracket';
2048}
2049
2050/**
2051 * Parses given abbreviation into property set
2052 */
2053function parse$2(abbr, options) {
2054 try {
2055 const tokens = typeof abbr === 'string' ? tokenize(abbr, options && options.value) : abbr;
2056 return parser(tokens, options);
2057 }
2058 catch (err) {
2059 if (err instanceof ScannerError && typeof abbr === 'string') {
2060 err.message += `\n${abbr}\n${'-'.repeat(err.pos)}^`;
2061 }
2062 throw err;
2063 }
2064}
2065
2066/**
2067 * Merges attributes in current node: de-duplicates attributes with the same name
2068 * and merges class names
2069 */
2070function mergeAttributes(node, config) {
2071 if (!node.attributes) {
2072 return;
2073 }
2074 const attributes = [];
2075 const lookup = {};
2076 for (const attr of node.attributes) {
2077 if (attr.name) {
2078 const attrName = attr.name;
2079 if (attrName in lookup) {
2080 const prev = lookup[attrName];
2081 if (attrName === 'class') {
2082 prev.value = mergeValue(prev.value, attr.value, ' ');
2083 }
2084 else {
2085 mergeDeclarations(prev, attr, config);
2086 }
2087 }
2088 else {
2089 // Create new attribute instance so we can safely modify it later
2090 attributes.push(lookup[attrName] = Object.assign({}, attr));
2091 }
2092 }
2093 else {
2094 attributes.push(attr);
2095 }
2096 }
2097 node.attributes = attributes;
2098}
2099/**
2100 * Merges two token lists into single list. Adjacent strings are merged together
2101 */
2102function mergeValue(prev, next, glue) {
2103 if (prev && next) {
2104 if (prev.length && glue) {
2105 append(prev, glue);
2106 }
2107 for (const t of next) {
2108 append(prev, t);
2109 }
2110 return prev;
2111 }
2112 const result = prev || next;
2113 return result && result.slice();
2114}
2115/**
2116 * Merges data from `src` attribute into `dest` and returns it
2117 */
2118function mergeDeclarations(dest, src, config) {
2119 dest.name = src.name;
2120 if (!config.options['output.reverseAttributes']) {
2121 dest.value = src.value;
2122 }
2123 // Keep high-priority properties
2124 if (!dest.implied) {
2125 dest.implied = src.implied;
2126 }
2127 if (!dest.boolean) {
2128 dest.boolean = src.boolean;
2129 }
2130 if (dest.valueType !== 'expression') {
2131 dest.valueType = src.valueType;
2132 }
2133 return dest;
2134}
2135function append(tokens, value) {
2136 const lastIx = tokens.length - 1;
2137 if (typeof tokens[lastIx] === 'string' && typeof value === 'string') {
2138 tokens[lastIx] += value;
2139 }
2140 else {
2141 tokens.push(value);
2142 }
2143}
2144
2145/**
2146 * Walks over each child node of given markup abbreviation AST node (not including
2147 * given one) and invokes `fn` on each node.
2148 * The `fn` callback accepts context node, list of ancestor nodes and optional
2149 * state object
2150 */
2151function walk$1(node, fn, state) {
2152 const ancestors = [node];
2153 const callback = (ctx) => {
2154 fn(ctx, ancestors, state);
2155 ancestors.push(ctx);
2156 ctx.children.forEach(callback);
2157 ancestors.pop();
2158 };
2159 node.children.forEach(callback);
2160}
2161/**
2162 * Finds first child node that matches given `callback`
2163 */
2164function find$1(node, callback) {
2165 for (let i = 0; i < node.children.length; i++) {
2166 const child = node.children[i];
2167 if (callback(child)) {
2168 return child;
2169 }
2170 const result = find$1(child, callback);
2171 if (result) {
2172 return result;
2173 }
2174 }
2175}
2176/**
2177 * Finds node which is the deepest for in current node or node itself.
2178 */
2179function findDeepest(node) {
2180 let parent;
2181 while (node.children.length) {
2182 parent = node;
2183 node = node.children[node.children.length - 1];
2184 }
2185 return { parent, node };
2186}
2187function isNode(node) {
2188 return node.type === 'AbbreviationNode';
2189}
2190
2191/**
2192 * Finds matching snippet from `registry` and resolves it into a parsed abbreviation.
2193 * Resolved node is then updated or replaced with matched abbreviation tree.
2194 *
2195 * A HTML registry basically contains aliases to another Emmet abbreviations,
2196 * e.g. a predefined set of name, attributes and so on, possibly a complex
2197 * abbreviation with multiple elements. So we have to get snippet, parse it
2198 * and recursively resolve it.
2199 */
2200function resolveSnippets(abbr, config) {
2201 const stack = [];
2202 const reversed = config.options['output.reverseAttributes'];
2203 const resolve = (child) => {
2204 const snippet = child.name && config.snippets[child.name];
2205 // A snippet in stack means circular reference.
2206 // It can be either a user error or a perfectly valid snippet like
2207 // "img": "img[src alt]/", e.g. an element with predefined shape.
2208 // In any case, simply stop parsing and keep element as is
2209 if (!snippet || stack.includes(snippet)) {
2210 return null;
2211 }
2212 const snippetAbbr = parseAbbreviation(snippet, config);
2213 stack.push(snippet);
2214 walkResolve(snippetAbbr, resolve);
2215 stack.pop();
2216 // Add attributes from current node into every top-level node of parsed abbreviation
2217 for (const topNode of snippetAbbr.children) {
2218 if (child.attributes) {
2219 const from = topNode.attributes || [];
2220 const to = child.attributes || [];
2221 topNode.attributes = reversed ? to.concat(from) : from.concat(to);
2222 }
2223 mergeNodes(child, topNode);
2224 }
2225 return snippetAbbr;
2226 };
2227 walkResolve(abbr, resolve);
2228 return abbr;
2229}
2230function walkResolve(node, resolve, config) {
2231 let children = [];
2232 for (const child of node.children) {
2233 const resolved = resolve(child);
2234 if (resolved) {
2235 children = children.concat(resolved.children);
2236 const deepest = findDeepest(resolved);
2237 if (isNode(deepest.node)) {
2238 deepest.node.children = deepest.node.children.concat(walkResolve(child, resolve));
2239 }
2240 }
2241 else {
2242 children.push(child);
2243 child.children = walkResolve(child, resolve);
2244 }
2245 }
2246 return node.children = children;
2247}
2248/**
2249 * Adds data from first node into second node
2250 */
2251function mergeNodes(from, to) {
2252 if (from.selfClosing) {
2253 to.selfClosing = true;
2254 }
2255 if (from.value != null) {
2256 to.value = from.value;
2257 }
2258 if (from.repeat) {
2259 to.repeat = from.repeat;
2260 }
2261}
2262
2263const expressionStart = '{';
2264const expressionEnd = '}';
2265function createOutputStream(options, level = 0) {
2266 return {
2267 options,
2268 value: '',
2269 level,
2270 offset: 0,
2271 line: 0,
2272 column: 0
2273 };
2274}
2275/**
2276 * Pushes plain string into output stream without newline processing
2277 */
2278function push(stream, text) {
2279 const processText = stream.options['output.text'];
2280 _push(stream, processText(text, stream.offset, stream.line, stream.column));
2281}
2282/**
2283 * Pushes given string with possible newline formatting into output
2284 */
2285function pushString(stream, value) {
2286 // If given value contains newlines, we should push content line-by-line and
2287 // use `pushNewline()` to maintain proper line/column state
2288 const lines = splitByLines$1(value);
2289 for (let i = 0, il = lines.length - 1; i <= il; i++) {
2290 push(stream, lines[i]);
2291 if (i !== il) {
2292 pushNewline(stream, true);
2293 }
2294 }
2295}
2296/**
2297 * Pushes new line into given output stream
2298 */
2299function pushNewline(stream, indent) {
2300 const baseIndent = stream.options['output.baseIndent'];
2301 const newline = stream.options['output.newline'];
2302 push(stream, newline + baseIndent);
2303 stream.line++;
2304 stream.column = baseIndent.length;
2305 if (indent) {
2306 pushIndent(stream, indent === true ? stream.level : indent);
2307 }
2308}
2309/**
2310 * Adds indentation of `size` to current output stream
2311 */
2312function pushIndent(stream, size = stream.level) {
2313 const indent = stream.options['output.indent'];
2314 push(stream, indent.repeat(Math.max(size, 0)));
2315}
2316/**
2317 * Pushes field/tabstop into output stream
2318 */
2319function pushField(stream, index, placeholder) {
2320 const field = stream.options['output.field'];
2321 // NB: use `_push` instead of `push` to skip text processing
2322 _push(stream, field(index, placeholder, stream.offset, stream.line, stream.column));
2323}
2324/**
2325 * Returns given tag name formatted according to given config
2326 */
2327function tagName(name, config) {
2328 return strCase(name, config.options['output.tagCase']);
2329}
2330/**
2331 * Returns given attribute name formatted according to given config
2332 */
2333function attrName(name, config) {
2334 return strCase(name, config.options['output.attributeCase']);
2335}
2336/**
2337 * Returns character for quoting value of given attribute
2338 */
2339function attrQuote(attr, config, isOpen) {
2340 if (attr.valueType === 'expression') {
2341 return isOpen ? expressionStart : expressionEnd;
2342 }
2343 return config.options['output.attributeQuotes'] === 'single' ? '\'' : '"';
2344}
2345/**
2346 * Check if given attribute is boolean
2347 */
2348function isBooleanAttribute(attr, config) {
2349 return attr.boolean
2350 || config.options['output.booleanAttributes'].includes((attr.name || '').toLowerCase());
2351}
2352/**
2353 * Returns a token for self-closing tag, depending on current options
2354 */
2355function selfClose(config) {
2356 switch (config.options['output.selfClosingStyle']) {
2357 case 'xhtml': return ' /';
2358 case 'xml': return '/';
2359 default: return '';
2360 }
2361}
2362/**
2363 * Check if given tag name belongs to inline-level element
2364 * @param node Parsed node or tag name
2365 */
2366function isInline(node, config) {
2367 if (typeof node === 'string') {
2368 return config.options.inlineElements.includes(node.toLowerCase());
2369 }
2370 // inline node is a node either with inline-level name or text-only node
2371 return node.name ? isInline(node.name, config) : Boolean(node.value && !node.attributes);
2372}
2373/**
2374 * Splits given text by lines
2375 */
2376function splitByLines$1(text) {
2377 return text.split(/\r\n|\r|\n/g);
2378}
2379/**
2380 * Pushes raw string into output stream without any processing
2381 */
2382function _push(stream, text) {
2383 stream.value += text;
2384 stream.offset += text.length;
2385 stream.column += text.length;
2386}
2387function strCase(str, type) {
2388 if (type) {
2389 return type === 'upper' ? str.toUpperCase() : str.toLowerCase();
2390 }
2391 return str;
2392}
2393
2394const elementMap = {
2395 p: 'span',
2396 ul: 'li',
2397 ol: 'li',
2398 table: 'tr',
2399 tr: 'td',
2400 tbody: 'tr',
2401 thead: 'tr',
2402 tfoot: 'tr',
2403 colgroup: 'col',
2404 select: 'option',
2405 optgroup: 'option',
2406 audio: 'source',
2407 video: 'source',
2408 object: 'param',
2409 map: 'area'
2410};
2411function implicitTag(node, ancestors, config) {
2412 if (!node.name && node.attributes) {
2413 resolveImplicitTag(node, ancestors, config);
2414 }
2415}
2416function resolveImplicitTag(node, ancestors, config) {
2417 const parent = getParentElement(ancestors);
2418 const contextName = config.context ? config.context.name : '';
2419 const parentName = lowercase(parent ? parent.name : contextName);
2420 node.name = elementMap[parentName]
2421 || (isInline(parentName, config) ? 'span' : 'div');
2422}
2423function lowercase(str) {
2424 return (str || '').toLowerCase();
2425}
2426/**
2427 * Returns closest element node from given ancestors list
2428 */
2429function getParentElement(ancestors) {
2430 for (let i = ancestors.length - 1; i >= 0; i--) {
2431 const elem = ancestors[i];
2432 if (isNode(elem)) {
2433 return elem;
2434 }
2435 }
2436}
2437
2438var latin = {
2439 "common": ["lorem", "ipsum", "dolor", "sit", "amet", "consectetur", "adipisicing", "elit"],
2440 "words": ["exercitationem", "perferendis", "perspiciatis", "laborum", "eveniet",
2441 "sunt", "iure", "nam", "nobis", "eum", "cum", "officiis", "excepturi",
2442 "odio", "consectetur", "quasi", "aut", "quisquam", "vel", "eligendi",
2443 "itaque", "non", "odit", "tempore", "quaerat", "dignissimos",
2444 "facilis", "neque", "nihil", "expedita", "vitae", "vero", "ipsum",
2445 "nisi", "animi", "cumque", "pariatur", "velit", "modi", "natus",
2446 "iusto", "eaque", "sequi", "illo", "sed", "ex", "et", "voluptatibus",
2447 "tempora", "veritatis", "ratione", "assumenda", "incidunt", "nostrum",
2448 "placeat", "aliquid", "fuga", "provident", "praesentium", "rem",
2449 "necessitatibus", "suscipit", "adipisci", "quidem", "possimus",
2450 "voluptas", "debitis", "sint", "accusantium", "unde", "sapiente",
2451 "voluptate", "qui", "aspernatur", "laudantium", "soluta", "amet",
2452 "quo", "aliquam", "saepe", "culpa", "libero", "ipsa", "dicta",
2453 "reiciendis", "nesciunt", "doloribus", "autem", "impedit", "minima",
2454 "maiores", "repudiandae", "ipsam", "obcaecati", "ullam", "enim",
2455 "totam", "delectus", "ducimus", "quis", "voluptates", "dolores",
2456 "molestiae", "harum", "dolorem", "quia", "voluptatem", "molestias",
2457 "magni", "distinctio", "omnis", "illum", "dolorum", "voluptatum", "ea",
2458 "quas", "quam", "corporis", "quae", "blanditiis", "atque", "deserunt",
2459 "laboriosam", "earum", "consequuntur", "hic", "cupiditate",
2460 "quibusdam", "accusamus", "ut", "rerum", "error", "minus", "eius",
2461 "ab", "ad", "nemo", "fugit", "officia", "at", "in", "id", "quos",
2462 "reprehenderit", "numquam", "iste", "fugiat", "sit", "inventore",
2463 "beatae", "repellendus", "magnam", "recusandae", "quod", "explicabo",
2464 "doloremque", "aperiam", "consequatur", "asperiores", "commodi",
2465 "optio", "dolor", "labore", "temporibus", "repellat", "veniam",
2466 "architecto", "est", "esse", "mollitia", "nulla", "a", "similique",
2467 "eos", "alias", "dolore", "tenetur", "deleniti", "porro", "facere",
2468 "maxime", "corrupti"]
2469};
2470
2471var ru = {
2472 "common": ["далеко-далеко", "за", "словесными", "горами", "в стране", "гласных", "и согласных", "живут", "рыбные", "тексты"],
2473 "words": ["вдали", "от всех", "они", "буквенных", "домах", "на берегу", "семантика",
2474 "большого", "языкового", "океана", "маленький", "ручеек", "даль",
2475 "журчит", "по всей", "обеспечивает", "ее","всеми", "необходимыми",
2476 "правилами", "эта", "парадигматическая", "страна", "которой", "жаренные",
2477 "предложения", "залетают", "прямо", "рот", "даже", "всемогущая",
2478 "пунктуация", "не", "имеет", "власти", "над", "рыбными", "текстами",
2479 "ведущими", "безорфографичный", "образ", "жизни", "однажды", "одна",
2480 "маленькая", "строчка","рыбного", "текста", "имени", "lorem", "ipsum",
2481 "решила", "выйти", "большой", "мир", "грамматики", "великий", "оксмокс",
2482 "предупреждал", "о", "злых", "запятых", "диких", "знаках", "вопроса",
2483 "коварных", "точках", "запятой", "но", "текст", "дал", "сбить",
2484 "себя", "толку", "он", "собрал", "семь", "своих", "заглавных", "букв",
2485 "подпоясал", "инициал", "за", "пояс", "пустился", "дорогу",
2486 "взобравшись", "первую", "вершину", "курсивных", "гор", "бросил",
2487 "последний", "взгляд", "назад", "силуэт", "своего", "родного", "города",
2488 "буквоград", "заголовок", "деревни", "алфавит", "подзаголовок", "своего",
2489 "переулка", "грустный", "реторический", "вопрос", "скатился", "его",
2490 "щеке", "продолжил", "свой", "путь", "дороге", "встретил", "рукопись",
2491 "она", "предупредила", "моей", "все", "переписывается", "несколько",
2492 "раз", "единственное", "что", "меня", "осталось", "это", "приставка",
2493 "возвращайся", "ты", "лучше", "свою", "безопасную", "страну", "послушавшись",
2494 "рукописи", "наш", "продолжил", "свой", "путь", "вскоре", "ему",
2495 "повстречался", "коварный", "составитель", "рекламных", "текстов",
2496 "напоивший", "языком", "речью", "заманивший", "свое", "агентство",
2497 "которое", "использовало", "снова", "снова", "своих", "проектах",
2498 "если", "переписали", "то", "живет", "там", "до", "сих", "пор"]
2499};
2500
2501var sp = {
2502 "common": ["mujer", "uno", "dolor", "más", "de", "poder", "mismo", "si"],
2503 "words": ["ejercicio", "preferencia", "perspicacia", "laboral", "paño",
2504 "suntuoso", "molde", "namibia", "planeador", "mirar", "demás", "oficinista", "excepción",
2505 "odio", "consecuencia", "casi", "auto", "chicharra", "velo", "elixir",
2506 "ataque", "no", "odio", "temporal", "cuórum", "dignísimo",
2507 "facilismo", "letra", "nihilista", "expedición", "alma", "alveolar", "aparte",
2508 "león", "animal", "como", "paria", "belleza", "modo", "natividad",
2509 "justo", "ataque", "séquito", "pillo", "sed", "ex", "y", "voluminoso",
2510 "temporalidad", "verdades", "racional", "asunción", "incidente", "marejada",
2511 "placenta", "amanecer", "fuga", "previsor", "presentación", "lejos",
2512 "necesariamente", "sospechoso", "adiposidad", "quindío", "pócima",
2513 "voluble", "débito", "sintió", "accesorio", "falda", "sapiencia",
2514 "volutas", "queso", "permacultura", "laudo", "soluciones", "entero",
2515 "pan", "litro", "tonelada", "culpa", "libertario", "mosca", "dictado",
2516 "reincidente", "nascimiento", "dolor", "escolar", "impedimento", "mínima",
2517 "mayores", "repugnante", "dulce", "obcecado", "montaña", "enigma",
2518 "total", "deletéreo", "décima", "cábala", "fotografía", "dolores",
2519 "molesto", "olvido", "paciencia", "resiliencia", "voluntad", "molestias",
2520 "magnífico", "distinción", "ovni", "marejada", "cerro", "torre", "y",
2521 "abogada", "manantial", "corporal", "agua", "crepúsculo", "ataque", "desierto",
2522 "laboriosamente", "angustia", "afortunado", "alma", "encefalograma",
2523 "materialidad", "cosas", "o", "renuncia", "error", "menos", "conejo",
2524 "abadía", "analfabeto", "remo", "fugacidad", "oficio", "en", "almácigo", "vos", "pan",
2525 "represión", "números", "triste", "refugiado", "trote", "inventor",
2526 "corchea", "repelente", "magma", "recusado", "patrón", "explícito",
2527 "paloma", "síndrome", "inmune", "autoinmune", "comodidad",
2528 "ley", "vietnamita", "demonio", "tasmania", "repeler", "apéndice",
2529 "arquitecto", "columna", "yugo", "computador", "mula", "a", "propósito",
2530 "fantasía", "alias", "rayo", "tenedor", "deleznable", "ventana", "cara",
2531 "anemia", "corrupto"]
2532};
2533
2534const vocabularies = { ru, sp, latin };
2535const reLorem = /^lorem([a-z]*)(\d*)(-\d*)?$/i;
2536function lorem(node, ancestors, config) {
2537 let m;
2538 if (node.name && (m = node.name.match(reLorem))) {
2539 const db = vocabularies[m[1]] || vocabularies.latin;
2540 const minWordCount = m[2] ? Math.max(1, Number(m[2])) : 30;
2541 const maxWordCount = m[3] ? Math.max(minWordCount, Number(m[3].slice(1))) : minWordCount;
2542 const wordCount = rand(minWordCount, maxWordCount);
2543 const repeat = node.repeat || findRepeater(ancestors);
2544 node.name = node.attributes = void 0;
2545 node.value = [paragraph(db, wordCount, !repeat || repeat.value === 0)];
2546 if (node.repeat && ancestors.length > 1) {
2547 resolveImplicitTag(node, ancestors, config);
2548 }
2549 }
2550}
2551/**
2552 * Returns random integer between <code>from</code> and <code>to</code> values
2553 */
2554function rand(from, to) {
2555 return Math.floor(Math.random() * (to - from) + from);
2556}
2557function sample(arr, count) {
2558 const len = arr.length;
2559 const iterations = Math.min(len, count);
2560 const result = [];
2561 while (result.length < iterations) {
2562 const str = arr[rand(0, len)];
2563 if (!result.includes(str)) {
2564 result.push(str);
2565 }
2566 }
2567 return result;
2568}
2569function choice(val) {
2570 return val[rand(0, val.length - 1)];
2571}
2572function sentence(words, end) {
2573 if (words.length) {
2574 words = [capitalize(words[0])].concat(words.slice(1));
2575 }
2576 return words.join(' ') + (end || choice('?!...')); // more dots than question marks
2577}
2578function capitalize(word) {
2579 return word[0].toUpperCase() + word.slice(1);
2580}
2581/**
2582 * Insert commas at randomly selected words. This function modifies values
2583 * inside `words` array
2584 */
2585function insertCommas(words) {
2586 if (words.length < 2) {
2587 return words;
2588 }
2589 words = words.slice();
2590 const len = words.length;
2591 const hasComma = /,$/;
2592 let totalCommas = 0;
2593 if (len > 3 && len <= 6) {
2594 totalCommas = rand(0, 1);
2595 }
2596 else if (len > 6 && len <= 12) {
2597 totalCommas = rand(0, 2);
2598 }
2599 else {
2600 totalCommas = rand(1, 4);
2601 }
2602 for (let i = 0, pos; i < totalCommas; i++) {
2603 pos = rand(0, len - 2);
2604 if (!hasComma.test(words[pos])) {
2605 words[pos] += ',';
2606 }
2607 }
2608 return words;
2609}
2610/**
2611 * Generate a paragraph of "Lorem ipsum" text
2612 * @param dict Words dictionary
2613 * @param wordCount Words count in paragraph
2614 * @param startWithCommon Should paragraph start with common "lorem ipsum" sentence.
2615 */
2616function paragraph(dict, wordCount, startWithCommon) {
2617 const result = [];
2618 let totalWords = 0;
2619 let words;
2620 if (startWithCommon && dict.common) {
2621 words = dict.common.slice(0, wordCount);
2622 totalWords += words.length;
2623 result.push(sentence(insertCommas(words), '.'));
2624 }
2625 while (totalWords < wordCount) {
2626 words = sample(dict.words, Math.min(rand(2, 30), wordCount - totalWords));
2627 totalWords += words.length;
2628 result.push(sentence(insertCommas(words)));
2629 }
2630 return result.join(' ');
2631}
2632function findRepeater(ancestors) {
2633 for (let i = ancestors.length - 1; i >= 0; i--) {
2634 const element = ancestors[i];
2635 if (element.type === 'AbbreviationNode' && element.repeat) {
2636 return element.repeat;
2637 }
2638 }
2639}
2640
2641/**
2642 * XSL transformer: removes `select` attributes from certain nodes that contain
2643 * children
2644 */
2645function xsl(node) {
2646 if (matchesName(node.name) && node.attributes && (node.children.length || node.value)) {
2647 node.attributes = node.attributes.filter(isAllowed);
2648 }
2649}
2650function isAllowed(attr) {
2651 return attr.name !== 'select';
2652}
2653function matchesName(name) {
2654 return name === 'xsl:variable' || name === 'xsl:with-param';
2655}
2656
2657const reElement = /^(-+)([a-z0-9]+[a-z0-9-]*)/i;
2658const reModifier = /^(_+)([a-z0-9]+[a-z0-9-_]*)/i;
2659const blockCandidates1 = (className) => /^[a-z]\-/i.test(className);
2660const blockCandidates2 = (className) => /^[a-z]/i.test(className);
2661function bem(node, ancestors, config) {
2662 expandClassNames(node);
2663 expandShortNotation(node, ancestors, config);
2664}
2665/**
2666 * Expands existing class names in BEM notation in given `node`.
2667 * For example, if node contains `b__el_mod` class name, this method ensures
2668 * that element contains `b__el` class as well
2669 */
2670function expandClassNames(node) {
2671 const data = getBEMData(node);
2672 const classNames = [];
2673 for (const cl of data.classNames) {
2674 // remove all modifiers and element prefixes from class name to get a base element name
2675 const ix = cl.indexOf('_');
2676 if (ix > 0 && !cl.startsWith('-')) {
2677 classNames.push(cl.slice(0, ix));
2678 classNames.push(cl.slice(ix));
2679 }
2680 else {
2681 classNames.push(cl);
2682 }
2683 }
2684 if (classNames.length) {
2685 data.classNames = classNames.filter(uniqueClass);
2686 data.block = findBlockName(data.classNames);
2687 updateClass(node, data.classNames.join(' '));
2688 }
2689}
2690/**
2691 * Expands short BEM notation, e.g. `-element` and `_modifier`
2692 */
2693function expandShortNotation(node, ancestors, config) {
2694 const data = getBEMData(node);
2695 const classNames = [];
2696 const { options } = config;
2697 const path = ancestors.slice(1).concat(node);
2698 for (let cl of data.classNames) {
2699 let prefix = '';
2700 let m;
2701 const originalClass = cl;
2702 // parse element definition (could be only one)
2703 if (m = cl.match(reElement)) {
2704 prefix = getBlockName(path, m[1].length, config.context) + options['bem.element'] + m[2];
2705 classNames.push(prefix);
2706 cl = cl.slice(m[0].length);
2707 }
2708 // parse modifiers definitions
2709 if (m = cl.match(reModifier)) {
2710 if (!prefix) {
2711 prefix = getBlockName(path, m[1].length);
2712 classNames.push(prefix);
2713 }
2714 classNames.push(`${prefix}${options['bem.modifier']}${m[2]}`);
2715 cl = cl.slice(m[0].length);
2716 }
2717 if (cl === originalClass) {
2718 // class name wasn’t modified: it’s not a BEM-specific class,
2719 // add it as-is into output
2720 classNames.push(originalClass);
2721 }
2722 }
2723 const arrClassNames = classNames.filter(uniqueClass);
2724 if (arrClassNames.length) {
2725 updateClass(node, arrClassNames.join(' '));
2726 }
2727}
2728/**
2729 * Returns BEM data from given abbreviation node
2730 */
2731function getBEMData(node) {
2732 if (!node._bem) {
2733 let classValue = '';
2734 if (node.attributes) {
2735 for (const attr of node.attributes) {
2736 if (attr.name === 'class' && attr.value) {
2737 classValue = stringifyValue(attr.value);
2738 break;
2739 }
2740 }
2741 }
2742 node._bem = parseBEM(classValue);
2743 }
2744 return node._bem;
2745}
2746function getBEMDataFromContext(context) {
2747 if (!context._bem) {
2748 context._bem = parseBEM(context.attributes && context.attributes.class || '');
2749 }
2750 return context._bem;
2751}
2752/**
2753 * Parses BEM data from given class name
2754 */
2755function parseBEM(classValue) {
2756 const classNames = classValue ? classValue.split(/\s+/) : [];
2757 return {
2758 classNames,
2759 block: findBlockName(classNames)
2760 };
2761}
2762/**
2763 * Returns block name for given `node` by `prefix`, which tells the depth of
2764 * of parent node lookup
2765 */
2766function getBlockName(ancestors, depth = 0, context) {
2767 const maxParentIx = 0;
2768 let parentIx = Math.max(ancestors.length - depth, maxParentIx);
2769 do {
2770 const parent = ancestors[parentIx];
2771 if (parent) {
2772 const data = getBEMData(parent);
2773 if (data.block) {
2774 return data.block;
2775 }
2776 }
2777 } while (maxParentIx < parentIx--);
2778 if (context) {
2779 const data = getBEMDataFromContext(context);
2780 if (data.block) {
2781 return data.block;
2782 }
2783 }
2784 return '';
2785}
2786function findBlockName(classNames) {
2787 return find(classNames, blockCandidates1)
2788 || find(classNames, blockCandidates2)
2789 || void 0;
2790}
2791/**
2792 * Finds class name from given list which may be used as block name
2793 */
2794function find(classNames, filter) {
2795 for (const cl of classNames) {
2796 if (reElement.test(cl) || reModifier.test(cl)) {
2797 break;
2798 }
2799 if (filter(cl)) {
2800 return cl;
2801 }
2802 }
2803}
2804function updateClass(node, value) {
2805 for (const attr of node.attributes) {
2806 if (attr.name === 'class') {
2807 attr.value = [value];
2808 break;
2809 }
2810 }
2811}
2812function stringifyValue(value) {
2813 let result = '';
2814 for (const t of value) {
2815 result += typeof t === 'string' ? t : t.name;
2816 }
2817 return result;
2818}
2819function uniqueClass(item, ix, arr) {
2820 return !!item && arr.indexOf(item) === ix;
2821}
2822
2823/**
2824 * Preprocessor of `<label>` element: if it contains `<input>`, remove `for` attribute
2825 * and `id` from input
2826 */
2827function label(node) {
2828 if (node.name === 'label') {
2829 const input = find$1(node, n => (n.name === 'input' || n.name === 'textarea'));
2830 if (input) {
2831 // Remove empty `for` attribute
2832 if (node.attributes) {
2833 node.attributes = node.attributes.filter(attr => {
2834 return !(attr.name === 'for' && isEmptyAttribute(attr));
2835 });
2836 }
2837 // Remove empty `id` attribute
2838 if (input.attributes) {
2839 input.attributes = input.attributes.filter(attr => {
2840 return !(attr.name === 'id' && isEmptyAttribute(attr));
2841 });
2842 }
2843 }
2844 }
2845}
2846function isEmptyAttribute(attr) {
2847 if (!attr.value) {
2848 return true;
2849 }
2850 if (attr.value.length === 1) {
2851 const token = attr.value[0];
2852 if (token && typeof token !== 'string' && !token.name) {
2853 // Attribute contains field
2854 return true;
2855 }
2856 }
2857 return false;
2858}
2859
2860function walk(abbr, visitor, state) {
2861 const callback = (ctx, index, items) => {
2862 const { parent, current } = state;
2863 state.parent = current;
2864 state.current = ctx;
2865 visitor(ctx, index, items, state, next);
2866 state.current = current;
2867 state.parent = parent;
2868 };
2869 const next = (node, index, items) => {
2870 state.ancestors.push(state.current);
2871 callback(node, index, items);
2872 state.ancestors.pop();
2873 };
2874 abbr.children.forEach(callback);
2875}
2876function createWalkState(config) {
2877 return {
2878 // @ts-ignore: Will set value in iterator
2879 current: null,
2880 parent: void 0,
2881 ancestors: [],
2882 config,
2883 field: 1,
2884 out: createOutputStream(config.options)
2885 };
2886}
2887
2888const caret = [{ type: 'Field', index: 0, name: '' }];
2889/**
2890 * Check if given node is a snippet: a node without name and attributes
2891 */
2892function isSnippet(node) {
2893 return node ? !node.name && !node.attributes : false;
2894}
2895/**
2896 * Check if given node is inline-level element, e.g. element with explicitly
2897 * defined node name
2898 */
2899function isInlineElement(node, config) {
2900 return node ? isInline(node, config) : false;
2901}
2902/**
2903 * Check if given value token is a field
2904 */
2905function isField(token) {
2906 return typeof token === 'object' && token.type === 'Field';
2907}
2908function pushTokens(tokens, state) {
2909 const { out } = state;
2910 let largestIndex = -1;
2911 for (const t of tokens) {
2912 if (typeof t === 'string') {
2913 pushString(out, t);
2914 }
2915 else {
2916 pushField(out, state.field + t.index, t.name);
2917 if (t.index > largestIndex) {
2918 largestIndex = t.index;
2919 }
2920 }
2921 }
2922 if (largestIndex !== -1) {
2923 state.field += largestIndex + 1;
2924 }
2925}
2926/**
2927 * Splits given value token by lines: returns array where each entry is a token list
2928 * for a single line
2929 */
2930function splitByLines(tokens) {
2931 const result = [];
2932 let line = [];
2933 for (const t of tokens) {
2934 if (typeof t === 'string') {
2935 const lines = t.split(/\r\n?|\n/g);
2936 line.push(lines.shift() || '');
2937 while (lines.length) {
2938 result.push(line);
2939 line = [lines.shift() || ''];
2940 }
2941 }
2942 else {
2943 line.push(t);
2944 }
2945 }
2946 line.length && result.push(line);
2947 return result;
2948}
2949/**
2950 * Check if given attribute should be outputted
2951 */
2952function shouldOutputAttribute(attr) {
2953 // In case if attribute is implied, check if it has a defined value:
2954 // either non-empty value or quoted empty value
2955 return !attr.implied || attr.valueType !== 'raw' || (!!attr.value && attr.value.length > 0);
2956}
2957
2958var TemplateChars;
2959(function (TemplateChars) {
2960 /** `[` character */
2961 TemplateChars[TemplateChars["Start"] = 91] = "Start";
2962 /** `]` character */
2963 TemplateChars[TemplateChars["End"] = 93] = "End";
2964 /* `_` character */
2965 TemplateChars[TemplateChars["Underscore"] = 95] = "Underscore";
2966 /* `-` character */
2967 TemplateChars[TemplateChars["Dash"] = 45] = "Dash";
2968})(TemplateChars || (TemplateChars = {}));
2969/**
2970 * Splits given string into template tokens.
2971 * Template is a string which contains placeholders which are uppercase names
2972 * between `[` and `]`, for example: `[PLACEHOLDER]`.
2973 * Unlike other templates, a placeholder may contain extra characters before and
2974 * after name: `[%PLACEHOLDER.]`. If data for `PLACEHOLDER` is defined, it will
2975 * be outputted with with these extra character, otherwise will be completely omitted.
2976 */
2977function template(text) {
2978 const tokens = [];
2979 const scanner = { pos: 0, text };
2980 let placeholder;
2981 let offset = scanner.pos;
2982 let pos = scanner.pos;
2983 while (scanner.pos < scanner.text.length) {
2984 pos = scanner.pos;
2985 if (placeholder = consumePlaceholder(scanner)) {
2986 if (offset !== scanner.pos) {
2987 tokens.push(text.slice(offset, pos));
2988 }
2989 tokens.push(placeholder);
2990 offset = scanner.pos;
2991 }
2992 else {
2993 scanner.pos++;
2994 }
2995 }
2996 if (offset !== scanner.pos) {
2997 tokens.push(text.slice(offset));
2998 }
2999 return tokens;
3000}
3001/**
3002 * Consumes placeholder like `[#ID]` from given scanner
3003 */
3004function consumePlaceholder(scanner) {
3005 if (peek$1(scanner) === TemplateChars.Start) {
3006 const start = ++scanner.pos;
3007 let namePos = start;
3008 let afterPos = start;
3009 let stack = 1;
3010 while (scanner.pos < scanner.text.length) {
3011 const code = peek$1(scanner);
3012 if (isTokenStart(code)) {
3013 namePos = scanner.pos;
3014 while (isToken(peek$1(scanner))) {
3015 scanner.pos++;
3016 }
3017 afterPos = scanner.pos;
3018 }
3019 else {
3020 if (code === TemplateChars.Start) {
3021 stack++;
3022 }
3023 else if (code === TemplateChars.End) {
3024 if (--stack === 0) {
3025 return {
3026 before: scanner.text.slice(start, namePos),
3027 after: scanner.text.slice(afterPos, scanner.pos++),
3028 name: scanner.text.slice(namePos, afterPos)
3029 };
3030 }
3031 }
3032 scanner.pos++;
3033 }
3034 }
3035 }
3036}
3037function peek$1(scanner, pos = scanner.pos) {
3038 return scanner.text.charCodeAt(pos);
3039}
3040function isTokenStart(code) {
3041 return code >= 65 && code <= 90; // A-Z
3042}
3043function isToken(code) {
3044 return isTokenStart(code)
3045 || (code > 47 && code < 58) /* 0-9 */
3046 || code === TemplateChars.Underscore
3047 || code === TemplateChars.Dash;
3048}
3049
3050function createCommentState(config) {
3051 const { options } = config;
3052 return {
3053 enabled: options['comment.enabled'],
3054 trigger: options['comment.trigger'],
3055 before: options['comment.before'] ? template(options['comment.before']) : void 0,
3056 after: options['comment.after'] ? template(options['comment.after']) : void 0
3057 };
3058}
3059/**
3060 * Adds comment prefix for given node, if required
3061 */
3062function commentNodeBefore(node, state) {
3063 if (shouldComment(node, state) && state.comment.before) {
3064 output(node, state.comment.before, state);
3065 }
3066}
3067/**
3068 * Adds comment suffix for given node, if required
3069 */
3070function commentNodeAfter(node, state) {
3071 if (shouldComment(node, state) && state.comment.after) {
3072 output(node, state.comment.after, state);
3073 }
3074}
3075/**
3076 * Check if given node should be commented
3077 */
3078function shouldComment(node, state) {
3079 const { comment } = state;
3080 if (!comment.enabled || !comment.trigger || !node.name || !node.attributes) {
3081 return false;
3082 }
3083 for (const attr of node.attributes) {
3084 if (attr.name && comment.trigger.includes(attr.name)) {
3085 return true;
3086 }
3087 }
3088 return false;
3089}
3090/**
3091 * Pushes given template tokens into output stream
3092 */
3093function output(node, tokens, state) {
3094 const attrs = {};
3095 const { out } = state;
3096 // Collect attributes payload
3097 for (const attr of node.attributes) {
3098 if (attr.name && attr.value) {
3099 attrs[attr.name.toUpperCase()] = attr.value;
3100 }
3101 }
3102 // Output parsed tokens
3103 for (const token of tokens) {
3104 if (typeof token === 'string') {
3105 pushString(out, token);
3106 }
3107 else if (attrs[token.name]) {
3108 pushString(out, token.before);
3109 pushTokens(attrs[token.name], state);
3110 pushString(out, token.after);
3111 }
3112 }
3113}
3114
3115const htmlTagRegex = /^<([\w\-:]+)[\s>]/;
3116const reservedKeywords = new Set([
3117 'for', 'while', 'of', 'async', 'await', 'const', 'let', 'var', 'continue',
3118 'break', 'debugger', 'do', 'export', 'import', 'in', 'instanceof', 'new', 'return',
3119 'switch', 'this', 'throw', 'try', 'catch', 'typeof', 'void', 'with', 'yield'
3120]);
3121function html(abbr, config) {
3122 const state = createWalkState(config);
3123 state.comment = createCommentState(config);
3124 walk(abbr, element$1, state);
3125 return state.out.value;
3126}
3127/**
3128 * Outputs `node` content to output stream of `state`
3129 * @param node Context node
3130 * @param index Index of `node` in `items`
3131 * @param items List of `node`’s siblings
3132 * @param state Current walk state
3133 */
3134function element$1(node, index, items, state, next) {
3135 const { out, config } = state;
3136 const format = shouldFormat$1(node, index, items, state);
3137 // Pick offset level for current node
3138 const level = getIndent(state);
3139 out.level += level;
3140 format && pushNewline(out, true);
3141 if (node.name) {
3142 const name = tagName(node.name, config);
3143 commentNodeBefore(node, state);
3144 pushString(out, `<${name}`);
3145 if (node.attributes) {
3146 for (const attr of node.attributes) {
3147 if (shouldOutputAttribute(attr)) {
3148 pushAttribute(attr, state);
3149 }
3150 }
3151 }
3152 if (node.selfClosing && !node.children.length && !node.value) {
3153 pushString(out, `${selfClose(config)}>`);
3154 }
3155 else {
3156 pushString(out, '>');
3157 if (!pushSnippet(node, state, next)) {
3158 if (node.value) {
3159 const innerFormat = node.value.some(hasNewline) || startsWithBlockTag(node.value, config);
3160 innerFormat && pushNewline(state.out, ++out.level);
3161 pushTokens(node.value, state);
3162 innerFormat && pushNewline(state.out, --out.level);
3163 }
3164 node.children.forEach(next);
3165 if (!node.value && !node.children.length) {
3166 const innerFormat = config.options['output.formatLeafNode']
3167 || config.options['output.formatForce'].includes(node.name);
3168 innerFormat && pushNewline(state.out, ++out.level);
3169 pushTokens(caret, state);
3170 innerFormat && pushNewline(state.out, --out.level);
3171 }
3172 }
3173 pushString(out, `</${name}>`);
3174 commentNodeAfter(node, state);
3175 }
3176 }
3177 else if (!pushSnippet(node, state, next) && node.value) {
3178 // A text-only node (snippet)
3179 pushTokens(node.value, state);
3180 node.children.forEach(next);
3181 }
3182 if (format && index === items.length - 1 && state.parent) {
3183 const offset = isSnippet(state.parent) ? 0 : 1;
3184 pushNewline(out, out.level - offset);
3185 }
3186 out.level -= level;
3187}
3188/**
3189 * Outputs given attribute’s content into output stream
3190 */
3191function pushAttribute(attr, state) {
3192 const { out, config } = state;
3193 if (attr.name) {
3194 const attributes = config.options['markup.attributes'];
3195 const valuePrefix = config.options['markup.valuePrefix'];
3196 let { name, value } = attr;
3197 let lQuote = attrQuote(attr, config, true);
3198 let rQuote = attrQuote(attr, config);
3199 if (attributes) {
3200 name = getMultiValue(name, attributes, attr.multiple) || name;
3201 }
3202 name = attrName(name, config);
3203 if (config.options['jsx.enabled'] && attr.multiple) {
3204 lQuote = expressionStart;
3205 rQuote = expressionEnd;
3206 }
3207 const prefix = valuePrefix
3208 ? getMultiValue(attr.name, valuePrefix, attr.multiple)
3209 : null;
3210 if (prefix && (value === null || value === void 0 ? void 0 : value.length) === 1 && typeof value[0] === 'string') {
3211 // Add given prefix in object notation
3212 const val = value[0];
3213 value = [isPropKey(val) ? `${prefix}.${val}` : `${prefix}['${val}']`];
3214 if (config.options['jsx.enabled']) {
3215 lQuote = expressionStart;
3216 rQuote = expressionEnd;
3217 }
3218 }
3219 if (isBooleanAttribute(attr, config) && !value) {
3220 // If attribute value is omitted and it’s a boolean value, check for
3221 // `compactBoolean` option: if it’s disabled, set value to attribute name
3222 // (XML style)
3223 if (!config.options['output.compactBoolean']) {
3224 value = [name];
3225 }
3226 }
3227 else if (!value) {
3228 value = caret;
3229 }
3230 pushString(out, ' ' + name);
3231 if (value) {
3232 pushString(out, '=' + lQuote);
3233 pushTokens(value, state);
3234 pushString(out, rQuote);
3235 }
3236 else if (config.options['output.selfClosingStyle'] !== 'html') {
3237 pushString(out, '=' + lQuote + rQuote);
3238 }
3239 }
3240}
3241function pushSnippet(node, state, next) {
3242 if (node.value && node.children.length) {
3243 // We have a value and child nodes. In case if value contains fields,
3244 // we should output children as a content of first field
3245 const fieldIx = node.value.findIndex(isField);
3246 if (fieldIx !== -1) {
3247 pushTokens(node.value.slice(0, fieldIx), state);
3248 const line = state.out.line;
3249 let pos = fieldIx + 1;
3250 node.children.forEach(next);
3251 // If there was a line change, trim leading whitespace for better result
3252 if (state.out.line !== line && typeof node.value[pos] === 'string') {
3253 pushString(state.out, node.value[pos++].trimLeft());
3254 }
3255 pushTokens(node.value.slice(pos), state);
3256 return true;
3257 }
3258 }
3259 return false;
3260}
3261/**
3262 * Check if given node should be formatted in its parent context
3263 */
3264function shouldFormat$1(node, index, items, state) {
3265 const { config, parent } = state;
3266 if (!config.options['output.format']) {
3267 return false;
3268 }
3269 if (index === 0 && !parent) {
3270 // Do not format very first node
3271 return false;
3272 }
3273 // Do not format single child of snippet
3274 if (parent && isSnippet(parent) && items.length === 1) {
3275 return false;
3276 }
3277 /**
3278 * Adjacent text-only/snippet nodes
3279 */
3280 if (isSnippet(node)) {
3281 // Adjacent text-only/snippet nodes
3282 const format = isSnippet(items[index - 1]) || isSnippet(items[index + 1])
3283 // Has newlines: looks like wrapping code fragment
3284 || node.value.some(hasNewline)
3285 // Format as wrapper: contains children which will be outputted as field content
3286 || (node.value.some(isField) && node.children.length);
3287 if (format) {
3288 return true;
3289 }
3290 }
3291 if (isInline(node, config)) {
3292 // Check if inline node is the next sibling of block-level node
3293 if (index === 0) {
3294 // First node in parent: format if it’s followed by a block-level element
3295 for (let i = 0; i < items.length; i++) {
3296 if (!isInline(items[i], config)) {
3297 return true;
3298 }
3299 }
3300 }
3301 else if (!isInline(items[index - 1], config)) {
3302 // Node is right after block-level element
3303 return true;
3304 }
3305 if (config.options['output.inlineBreak']) {
3306 // check for adjacent inline elements before and after current element
3307 let adjacentInline = 1;
3308 let before = index;
3309 let after = index;
3310 while (isInlineElement(items[--before], config)) {
3311 adjacentInline++;
3312 }
3313 while (isInlineElement(items[++after], config)) {
3314 adjacentInline++;
3315 }
3316 if (adjacentInline >= config.options['output.inlineBreak']) {
3317 return true;
3318 }
3319 }
3320 // Edge case: inline node contains node that should receive formatting
3321 for (let i = 0, il = node.children.length; i < il; i++) {
3322 if (shouldFormat$1(node.children[i], i, node.children, state)) {
3323 return true;
3324 }
3325 }
3326 return false;
3327 }
3328 return true;
3329}
3330/**
3331 * Returns indentation offset for given node
3332 */
3333function getIndent(state) {
3334 const { config, parent } = state;
3335 if (!parent || isSnippet(parent) || (parent.name && config.options['output.formatSkip'].includes(parent.name))) {
3336 return 0;
3337 }
3338 return 1;
3339}
3340/**
3341 * Check if given node value contains newlines
3342 */
3343function hasNewline(value) {
3344 return typeof value === 'string' && /\r|\n/.test(value);
3345}
3346/**
3347 * Check if given node value starts with block-level tag
3348 */
3349function startsWithBlockTag(value, config) {
3350 if (value.length && typeof value[0] === 'string') {
3351 const matches = htmlTagRegex.exec(value[0]);
3352 if ((matches === null || matches === void 0 ? void 0 : matches.length) && !config.options['inlineElements'].includes(matches[1].toLowerCase())) {
3353 return true;
3354 }
3355 }
3356 return false;
3357}
3358function getMultiValue(key, data, multiple) {
3359 return (multiple && data[`${key}*`]) || data[key];
3360}
3361function isPropKey(name) {
3362 return !reservedKeywords.has(name) && /^[a-zA-Z_$][\w_$]*$/.test(name);
3363}
3364
3365function indentFormat(abbr, config, options) {
3366 const state = createWalkState(config);
3367 state.options = options || {};
3368 walk(abbr, element, state);
3369 return state.out.value;
3370}
3371/**
3372 * Outputs `node` content to output stream of `state`
3373 * @param node Context node
3374 * @param index Index of `node` in `items`
3375 * @param items List of `node`’s siblings
3376 * @param state Current walk state
3377 */
3378function element(node, index, items, state, next) {
3379 const { out, options } = state;
3380 const { primary, secondary } = collectAttributes(node);
3381 // Pick offset level for current node
3382 const level = state.parent ? 1 : 0;
3383 out.level += level;
3384 // Do not indent top-level elements
3385 if (shouldFormat(node, index, items, state)) {
3386 pushNewline(out, true);
3387 }
3388 if (node.name && (node.name !== 'div' || !primary.length)) {
3389 pushString(out, (options.beforeName || '') + node.name + (options.afterName || ''));
3390 }
3391 pushPrimaryAttributes(primary, state);
3392 pushSecondaryAttributes(secondary.filter(shouldOutputAttribute), state);
3393 if (node.selfClosing && !node.value && !node.children.length) {
3394 if (state.options.selfClose) {
3395 pushString(out, state.options.selfClose);
3396 }
3397 }
3398 else {
3399 pushValue(node, state);
3400 node.children.forEach(next);
3401 }
3402 out.level -= level;
3403}
3404/**
3405 * From given node, collects all attributes as `primary` (id, class) and
3406 * `secondary` (all the rest) lists. In most indent-based syntaxes, primary attribute
3407 * has special syntax
3408 */
3409function collectAttributes(node) {
3410 const primary = [];
3411 const secondary = [];
3412 if (node.attributes) {
3413 for (const attr of node.attributes) {
3414 if (isPrimaryAttribute(attr)) {
3415 primary.push(attr);
3416 }
3417 else {
3418 secondary.push(attr);
3419 }
3420 }
3421 }
3422 return { primary, secondary };
3423}
3424/**
3425 * Outputs given attributes as primary into output stream
3426 */
3427function pushPrimaryAttributes(attrs, state) {
3428 for (const attr of attrs) {
3429 if (attr.value) {
3430 if (attr.name === 'class') {
3431 pushString(state.out, '.');
3432 // All whitespace characters must be replaced with dots in class names
3433 const tokens = attr.value.map(t => typeof t === 'string' ? t.replace(/\s+/g, '.') : t);
3434 pushTokens(tokens, state);
3435 }
3436 else {
3437 // ID attribute
3438 pushString(state.out, '#');
3439 pushTokens(attr.value, state);
3440 }
3441 }
3442 }
3443}
3444/**
3445 * Outputs given attributes as secondary into output stream
3446 */
3447function pushSecondaryAttributes(attrs, state) {
3448 if (attrs.length) {
3449 const { out, config, options } = state;
3450 options.beforeAttribute && pushString(out, options.beforeAttribute);
3451 for (let i = 0; i < attrs.length; i++) {
3452 const attr = attrs[i];
3453 pushString(out, attrName(attr.name || '', config));
3454 if (isBooleanAttribute(attr, config) && !attr.value) {
3455 if (!config.options['output.compactBoolean'] && options.booleanValue) {
3456 pushString(out, '=' + options.booleanValue);
3457 }
3458 }
3459 else {
3460 pushString(out, '=' + attrQuote(attr, config, true));
3461 pushTokens(attr.value || caret, state);
3462 pushString(out, attrQuote(attr, config));
3463 }
3464 if (i !== attrs.length - 1 && options.glueAttribute) {
3465 pushString(out, options.glueAttribute);
3466 }
3467 }
3468 options.afterAttribute && pushString(out, options.afterAttribute);
3469 }
3470}
3471/**
3472 * Outputs given node value into state output stream
3473 */
3474function pushValue(node, state) {
3475 // We should either output value or add caret but for leaf nodes only (no children)
3476 if (!node.value && node.children.length) {
3477 return;
3478 }
3479 const value = node.value || caret;
3480 const lines = splitByLines(value);
3481 const { out, options } = state;
3482 if (lines.length === 1) {
3483 if (node.name || node.attributes) {
3484 push(out, ' ');
3485 }
3486 pushTokens(value, state);
3487 }
3488 else {
3489 // We should format multi-line value with terminating `|` character
3490 // and same line length
3491 const lineLengths = [];
3492 let maxLength = 0;
3493 // Calculate lengths of all lines and max line length
3494 for (const line of lines) {
3495 const len = valueLength(line);
3496 lineLengths.push(len);
3497 if (len > maxLength) {
3498 maxLength = len;
3499 }
3500 }
3501 // Output each line, padded to max length
3502 out.level++;
3503 for (let i = 0; i < lines.length; i++) {
3504 pushNewline(out, true);
3505 options.beforeTextLine && push(out, options.beforeTextLine);
3506 pushTokens(lines[i], state);
3507 if (options.afterTextLine) {
3508 push(out, ' '.repeat(maxLength - lineLengths[i]));
3509 push(out, options.afterTextLine);
3510 }
3511 }
3512 out.level--;
3513 }
3514}
3515function isPrimaryAttribute(attr) {
3516 return attr.name === 'class' || attr.name === 'id';
3517}
3518/**
3519 * Calculates string length from given tokens
3520 */
3521function valueLength(tokens) {
3522 let len = 0;
3523 for (const token of tokens) {
3524 len += typeof token === 'string' ? token.length : token.name.length;
3525 }
3526 return len;
3527}
3528function shouldFormat(node, index, items, state) {
3529 // Do not format first top-level element or snippets
3530 if (!state.parent && index === 0) {
3531 return false;
3532 }
3533 return !isSnippet(node);
3534}
3535
3536function haml(abbr, config) {
3537 return indentFormat(abbr, config, {
3538 beforeName: '%',
3539 beforeAttribute: '(',
3540 afterAttribute: ')',
3541 glueAttribute: ' ',
3542 afterTextLine: ' |',
3543 booleanValue: 'true',
3544 selfClose: '/'
3545 });
3546}
3547
3548function slim(abbr, config) {
3549 return indentFormat(abbr, config, {
3550 beforeAttribute: ' ',
3551 glueAttribute: ' ',
3552 beforeTextLine: '| ',
3553 selfClose: '/'
3554 });
3555}
3556
3557function pug(abbr, config) {
3558 return indentFormat(abbr, config, {
3559 beforeAttribute: '(',
3560 afterAttribute: ')',
3561 glueAttribute: ', ',
3562 beforeTextLine: '| ',
3563 selfClose: config.options['output.selfClosingStyle'] === 'xml' ? '/' : ''
3564 });
3565}
3566
3567const formatters = { html, haml, slim, pug };
3568/**
3569 * Parses given Emmet abbreviation into a final abbreviation tree with all
3570 * required transformations applied
3571 */
3572function parse$1(abbr, config) {
3573 let oldTextValue;
3574 if (typeof abbr === 'string') {
3575 const parseOpt = Object.assign({}, config);
3576 if (config.options['jsx.enabled']) {
3577 parseOpt.jsx = true;
3578 }
3579 if (config.options['markup.href']) {
3580 parseOpt.href = true;
3581 }
3582 abbr = parseAbbreviation(abbr, parseOpt);
3583 // remove text field before snippets(abbr, config) call
3584 // as abbreviation(abbr, parseOpt) already handled it
3585 oldTextValue = config.text;
3586 config.text = undefined;
3587 }
3588 // Run abbreviation resolve in two passes:
3589 // 1. Map each node to snippets, which are abbreviations as well. A single snippet
3590 // may produce multiple nodes
3591 // 2. Transform every resolved node
3592 abbr = resolveSnippets(abbr, config);
3593 walk$1(abbr, transform, config);
3594 config.text = oldTextValue !== null && oldTextValue !== void 0 ? oldTextValue : config.text;
3595 return abbr;
3596}
3597/**
3598 * Converts given abbreviation to string according to provided `config`
3599 */
3600function stringify(abbr, config) {
3601 const formatter = formatters[config.syntax] || html;
3602 return formatter(abbr, config);
3603}
3604/**
3605 * Modifies given node and prepares it for output
3606 */
3607function transform(node, ancestors, config) {
3608 implicitTag(node, ancestors, config);
3609 mergeAttributes(node, config);
3610 lorem(node, ancestors, config);
3611 if (config.syntax === 'xsl') {
3612 xsl(node);
3613 }
3614 if (config.type === 'markup') {
3615 label(node);
3616 }
3617 if (config.options['bem.enabled']) {
3618 bem(node, ancestors, config);
3619 }
3620}
3621
3622var CSSSnippetType;
3623(function (CSSSnippetType) {
3624 CSSSnippetType["Raw"] = "Raw";
3625 CSSSnippetType["Property"] = "Property";
3626})(CSSSnippetType || (CSSSnippetType = {}));
3627const reProperty = /^([a-z-]+)(?:\s*:\s*([^\n\r;]+?);*)?$/;
3628const opt = { value: true };
3629/**
3630 * Creates structure for holding resolved CSS snippet
3631 */
3632function createSnippet(key, value) {
3633 // A snippet could be a raw text snippet (e.g. arbitrary text string) or a
3634 // CSS property with possible values separated by `|`.
3635 // In latter case, we have to parse snippet as CSS abbreviation
3636 const m = value.match(reProperty);
3637 if (m) {
3638 const keywords = {};
3639 const parsed = m[2] ? m[2].split('|').map(parseValue) : [];
3640 for (const item of parsed) {
3641 for (const cssVal of item) {
3642 collectKeywords(cssVal, keywords);
3643 }
3644 }
3645 return {
3646 type: CSSSnippetType.Property,
3647 key,
3648 property: m[1],
3649 value: parsed,
3650 keywords,
3651 dependencies: []
3652 };
3653 }
3654 return { type: CSSSnippetType.Raw, key, value };
3655}
3656/**
3657 * Nests more specific CSS properties into shorthand ones, e.g.
3658 * `background-position-x` -> `background-position` -> `background`
3659 */
3660function nest(snippets) {
3661 snippets = snippets.slice().sort(snippetsSort);
3662 const stack = [];
3663 let prev;
3664 // For sorted list of CSS properties, create dependency graph where each
3665 // shorthand property contains its more specific one, e.g.
3666 // background -> background-position -> background-position-x
3667 for (const cur of snippets.filter(isProperty)) {
3668 // Check if current property belongs to one from parent stack.
3669 // Since `snippets` array is sorted, items are perfectly aligned
3670 // from shorthands to more specific variants
3671 while (stack.length) {
3672 prev = stack[stack.length - 1];
3673 if (cur.property.startsWith(prev.property)
3674 && cur.property.charCodeAt(prev.property.length) === 45 /* - */) {
3675 prev.dependencies.push(cur);
3676 stack.push(cur);
3677 break;
3678 }
3679 stack.pop();
3680 }
3681 if (!stack.length) {
3682 stack.push(cur);
3683 }
3684 }
3685 return snippets;
3686}
3687/**
3688 * A sorting function for array of snippets
3689 */
3690function snippetsSort(a, b) {
3691 if (a.key === b.key) {
3692 return 0;
3693 }
3694 return a.key < b.key ? -1 : 1;
3695}
3696function parseValue(value) {
3697 return parse$2(value.trim(), opt)[0].value;
3698}
3699function isProperty(snippet) {
3700 return snippet.type === CSSSnippetType.Property;
3701}
3702function collectKeywords(cssVal, dest) {
3703 for (const v of cssVal.value) {
3704 if (v.type === 'Literal') {
3705 dest[v.value] = v;
3706 }
3707 else if (v.type === 'FunctionCall') {
3708 dest[v.name] = v;
3709 }
3710 else if (v.type === 'Field') {
3711 // Create literal from field, if available
3712 const value = v.name.trim();
3713 if (value) {
3714 dest[value] = { type: 'Literal', value };
3715 }
3716 }
3717 }
3718}
3719
3720/**
3721 * Calculates how close `str1` matches `str2` using fuzzy match.
3722 * How matching works:
3723 * – first characters of both `str1` and `str2` *must* match
3724 * – `str1` length larger than `str2` length is allowed only when `unmatched` is true
3725 * – ideal match is when `str1` equals to `str2` (score: 1)
3726 * – next best match is `str2` starts with `str1` (score: 1 × percent of matched characters)
3727 * – other scores depend on how close characters of `str1` to the beginning of `str2`
3728 * @param partialMatch Allow length `str1` to be greater than `str2` length
3729 */
3730function scoreMatch(str1, str2, partialMatch = false) {
3731 str1 = str1.toLowerCase();
3732 str2 = str2.toLowerCase();
3733 if (str1 === str2) {
3734 return 1;
3735 }
3736 // Both strings MUST start with the same character
3737 if (!str1 || !str2 || str1.charCodeAt(0) !== str2.charCodeAt(0)) {
3738 return 0;
3739 }
3740 const str1Len = str1.length;
3741 const str2Len = str2.length;
3742 if (!partialMatch && str1Len > str2Len) {
3743 return 0;
3744 }
3745 // Characters from `str1` which are closer to the beginning of a `str2` should
3746 // have higher score.
3747 // For example, if `str2` is `abcde`, it’s max score is:
3748 // 5 + 4 + 3 + 2 + 1 = 15 (sum of character positions in reverse order)
3749 // Matching `abd` against `abcde` should produce:
3750 // 5 + 4 + 2 = 11
3751 // Acronym bonus for match right after `-`. Matching `abd` against `abc-de`
3752 // should produce:
3753 // 6 + 5 + 4 (use `d` position in `abd`, not in abc-de`)
3754 const minLength = Math.min(str1Len, str2Len);
3755 const maxLength = Math.max(str1Len, str2Len);
3756 let i = 1;
3757 let j = 1;
3758 let score = maxLength;
3759 let ch1 = 0;
3760 let ch2 = 0;
3761 let found = false;
3762 let acronym = false;
3763 while (i < str1Len) {
3764 ch1 = str1.charCodeAt(i);
3765 found = false;
3766 acronym = false;
3767 while (j < str2Len) {
3768 ch2 = str2.charCodeAt(j);
3769 if (ch1 === ch2) {
3770 found = true;
3771 score += maxLength - (acronym ? i : j);
3772 break;
3773 }
3774 // add acronym bonus for exactly next match after unmatched `-`
3775 acronym = ch2 === 45 /* - */;
3776 j++;
3777 }
3778 if (!found) {
3779 if (!partialMatch) {
3780 return 0;
3781 }
3782 break;
3783 }
3784 i++;
3785 }
3786 const matchRatio = i / maxLength;
3787 const delta = maxLength - minLength;
3788 const maxScore = sum(maxLength) - sum(delta);
3789 return (score * matchRatio) / maxScore;
3790}
3791/**
3792 * Calculates sum of first `n` numbers, e.g. 1+2+3+...n
3793 */
3794function sum(n) {
3795 return n * (n + 1) / 2;
3796}
3797
3798function color(token, shortHex) {
3799 if (!token.r && !token.g && !token.b && !token.a) {
3800 return 'transparent';
3801 }
3802 else if (token.a === 1) {
3803 return asHex(token, shortHex);
3804 }
3805 return asRGB(token);
3806}
3807/**
3808 * Output given color as hex value
3809 * @param short Produce short value (e.g. #fff instead of #ffffff), if possible
3810 */
3811function asHex(token, short) {
3812 const fn = (short && isShortHex(token.r) && isShortHex(token.g) && isShortHex(token.b))
3813 ? toShortHex : toHex;
3814 return '#' + fn(token.r) + fn(token.g) + fn(token.b);
3815}
3816/**
3817 * Output current color as `rgba?(...)` CSS color
3818 */
3819function asRGB(token) {
3820 const values = [token.r, token.g, token.b];
3821 if (token.a !== 1) {
3822 values.push(frac(token.a, 8));
3823 }
3824 return `${values.length === 3 ? 'rgb' : 'rgba'}(${values.join(', ')})`;
3825}
3826function frac(num, digits = 4) {
3827 return num.toFixed(digits).replace(/\.?0+$/, '');
3828}
3829function isShortHex(hex) {
3830 return !(hex % 17);
3831}
3832function toShortHex(num) {
3833 return (num >> 4).toString(16);
3834}
3835function toHex(num) {
3836 return pad(num.toString(16), 2);
3837}
3838function pad(value, len) {
3839 while (value.length < len) {
3840 value = '0' + value;
3841 }
3842 return value;
3843}
3844
3845const CSSAbbreviationScope = {
3846 /** Include all possible snippets in match */
3847 Global: '@@global',
3848 /** Include raw snippets only (e.g. no properties) in abbreviation match */
3849 Section: '@@section',
3850 /** Include properties only in abbreviation match */
3851 Property: '@@property',
3852 /** Resolve abbreviation in context of CSS property value */
3853 Value: '@@value',
3854};
3855function css(abbr, config) {
3856 var _a;
3857 const out = createOutputStream(config.options);
3858 const format = config.options['output.format'];
3859 if (((_a = config.context) === null || _a === void 0 ? void 0 : _a.name) === CSSAbbreviationScope.Section) {
3860 // For section context, filter out unmatched snippets
3861 abbr = abbr.filter(node => node.snippet);
3862 }
3863 for (let i = 0; i < abbr.length; i++) {
3864 if (format && i !== 0) {
3865 pushNewline(out, true);
3866 }
3867 property(abbr[i], out, config);
3868 }
3869 return out.value;
3870}
3871/**
3872 * Outputs given abbreviation node into output stream
3873 */
3874function property(node, out, config) {
3875 const isJSON = config.options['stylesheet.json'];
3876 if (node.name) {
3877 // It’s a CSS property
3878 const name = isJSON ? toCamelCase(node.name) : node.name;
3879 pushString(out, name + config.options['stylesheet.between']);
3880 if (node.value.length) {
3881 propertyValue(node, out, config);
3882 }
3883 else {
3884 pushField(out, 0, '');
3885 }
3886 if (isJSON) {
3887 // For CSS-in-JS, always finalize property with comma
3888 // NB: seems like `important` is not available in CSS-in-JS syntaxes
3889 push(out, ',');
3890 }
3891 else {
3892 outputImportant(node, out, true);
3893 push(out, config.options['stylesheet.after']);
3894 }
3895 }
3896 else {
3897 // It’s a regular snippet, output plain tokens without any additional formatting
3898 for (const cssVal of node.value) {
3899 for (const v of cssVal.value) {
3900 outputToken(v, out, config);
3901 }
3902 }
3903 outputImportant(node, out, node.value.length > 0);
3904 }
3905}
3906function propertyValue(node, out, config) {
3907 const isJSON = config.options['stylesheet.json'];
3908 const num = isJSON ? getSingleNumeric(node) : null;
3909 if (num && (!num.unit || num.unit === 'px')) {
3910 // For CSS-in-JS, if property contains single numeric value, output it
3911 // as JS number
3912 push(out, String(num.value));
3913 }
3914 else {
3915 const quote = getQuote(config);
3916 isJSON && push(out, quote);
3917 for (let i = 0; i < node.value.length; i++) {
3918 if (i !== 0) {
3919 push(out, ', ');
3920 }
3921 outputValue(node.value[i], out, config);
3922 }
3923 isJSON && push(out, quote);
3924 }
3925}
3926function outputImportant(node, out, separator) {
3927 if (node.important) {
3928 if (separator) {
3929 push(out, ' ');
3930 }
3931 push(out, '!important');
3932 }
3933}
3934function outputValue(value, out, config) {
3935 for (let i = 0, prevEnd = -1; i < value.value.length; i++) {
3936 const token = value.value[i];
3937 // Handle edge case: a field is written close to previous token like this: `foo${bar}`.
3938 // We should not add delimiter here
3939 if (i !== 0 && (token.type !== 'Field' || token.start !== prevEnd)) {
3940 push(out, ' ');
3941 }
3942 outputToken(token, out, config);
3943 prevEnd = token['end'];
3944 }
3945}
3946function outputToken(token, out, config) {
3947 if (token.type === 'ColorValue') {
3948 push(out, color(token, config.options['stylesheet.shortHex']));
3949 }
3950 else if (token.type === 'Literal' || token.type === 'CustomProperty') {
3951 pushString(out, token.value);
3952 }
3953 else if (token.type === 'NumberValue') {
3954 pushString(out, frac(token.value, 4) + token.unit);
3955 }
3956 else if (token.type === 'StringValue') {
3957 const quote = token.quote === 'double' ? '"' : '\'';
3958 pushString(out, quote + token.value + quote);
3959 }
3960 else if (token.type === 'Field') {
3961 pushField(out, token.index, token.name);
3962 }
3963 else if (token.type === 'FunctionCall') {
3964 push(out, token.name + '(');
3965 for (let i = 0; i < token.arguments.length; i++) {
3966 if (i) {
3967 push(out, ', ');
3968 }
3969 outputValue(token.arguments[i], out, config);
3970 }
3971 push(out, ')');
3972 }
3973}
3974/**
3975 * If value of given property is a single numeric value, returns this token
3976 */
3977function getSingleNumeric(node) {
3978 if (node.value.length === 1) {
3979 const cssVal = node.value[0];
3980 if (cssVal.value.length === 1 && cssVal.value[0].type === 'NumberValue') {
3981 return cssVal.value[0];
3982 }
3983 }
3984}
3985/**
3986 * Converts kebab-case string to camelCase
3987 */
3988function toCamelCase(str) {
3989 return str.replace(/\-(\w)/g, (_, letter) => letter.toUpperCase());
3990}
3991function getQuote(config) {
3992 return config.options['stylesheet.jsonDoubleQuotes'] ? '"' : '\'';
3993}
3994
3995const gradientName = 'lg';
3996/**
3997 * Parses given Emmet abbreviation into a final abbreviation tree with all
3998 * required transformations applied
3999 */
4000function parse(abbr, config) {
4001 var _a;
4002 const snippets = ((_a = config.cache) === null || _a === void 0 ? void 0 : _a.stylesheetSnippets) || convertSnippets(config.snippets);
4003 if (config.cache) {
4004 config.cache.stylesheetSnippets = snippets;
4005 }
4006 if (typeof abbr === 'string') {
4007 abbr = parse$2(abbr, { value: isValueScope(config) });
4008 }
4009 const filteredSnippets = getSnippetsForScope(snippets, config);
4010 for (const node of abbr) {
4011 resolveNode(node, filteredSnippets, config);
4012 }
4013 return abbr;
4014}
4015/**
4016 * Converts given raw snippets into internal snippets representation
4017 */
4018function convertSnippets(snippets) {
4019 const result = [];
4020 for (const key of Object.keys(snippets)) {
4021 result.push(createSnippet(key, snippets[key]));
4022 }
4023 return nest(result);
4024}
4025/**
4026 * Resolves given node: finds matched CSS snippets using fuzzy match and resolves
4027 * keyword aliases from node value
4028 */
4029function resolveNode(node, snippets, config) {
4030 if (!resolveGradient(node, config)) {
4031 const score = config.options['stylesheet.fuzzySearchMinScore'];
4032 if (isValueScope(config)) {
4033 // Resolve as value of given CSS property
4034 const propName = config.context.name;
4035 const snippet = snippets.find(s => s.type === CSSSnippetType.Property && s.property === propName);
4036 resolveValueKeywords(node, config, snippet, score);
4037 node.snippet = snippet;
4038 }
4039 else if (node.name) {
4040 const snippet = findBestMatch(node.name, snippets, score, true);
4041 node.snippet = snippet;
4042 if (snippet) {
4043 if (snippet.type === CSSSnippetType.Property) {
4044 resolveAsProperty(node, snippet, config);
4045 }
4046 else {
4047 resolveAsSnippet(node, snippet);
4048 }
4049 }
4050 }
4051 }
4052 if (node.name || config.context) {
4053 // Resolve numeric values for CSS properties only
4054 resolveNumericValue(node, config);
4055 }
4056 return node;
4057}
4058/**
4059 * Resolves CSS gradient shortcut from given property, if possible
4060 */
4061function resolveGradient(node, config) {
4062 let gradientFn = null;
4063 const cssVal = node.value.length === 1 ? node.value[0] : null;
4064 if (cssVal && cssVal.value.length === 1) {
4065 const v = cssVal.value[0];
4066 if (v.type === 'FunctionCall' && v.name === gradientName) {
4067 gradientFn = v;
4068 }
4069 }
4070 if (gradientFn || node.name === gradientName) {
4071 if (!gradientFn) {
4072 gradientFn = {
4073 type: 'FunctionCall',
4074 name: 'linear-gradient',
4075 arguments: [cssValue(field(0, ''))]
4076 };
4077 }
4078 else {
4079 gradientFn = Object.assign(Object.assign({}, gradientFn), { name: 'linear-gradient' });
4080 }
4081 if (!config.context) {
4082 node.name = 'background-image';
4083 }
4084 node.value = [cssValue(gradientFn)];
4085 return true;
4086 }
4087 return false;
4088}
4089/**
4090 * Resolves given parsed abbreviation node as CSS property
4091 */
4092function resolveAsProperty(node, snippet, config) {
4093 const abbr = node.name;
4094 // Check for unmatched part of abbreviation
4095 // For example, in `dib` abbreviation the matched part is `d` and `ib` should
4096 // be considered as inline value. If unmatched fragment exists, we should check
4097 // if it matches actual value of snippet. If either explicit value is specified
4098 // or unmatched fragment did not resolve to to a keyword, we should consider
4099 // matched snippet as invalid
4100 const inlineValue = getUnmatchedPart(abbr, snippet.key);
4101 if (inlineValue) {
4102 if (node.value.length) {
4103 // Already have value: unmatched part indicates matched snippet is invalid
4104 return node;
4105 }
4106 const kw = resolveKeyword(inlineValue, config, snippet);
4107 if (!kw) {
4108 return node;
4109 }
4110 node.value.push(cssValue(kw));
4111 }
4112 node.name = snippet.property;
4113 if (node.value.length) {
4114 // Replace keyword alias from current abbreviation node with matched keyword
4115 resolveValueKeywords(node, config, snippet);
4116 }
4117 else if (snippet.value.length) {
4118 const defaultValue = snippet.value[0];
4119 // https://github.com/emmetio/emmet/issues/558
4120 // We should auto-select inserted value only if there’s multiple value
4121 // choice
4122 node.value = snippet.value.length === 1 || defaultValue.some(hasField)
4123 ? defaultValue
4124 : defaultValue.map(n => wrapWithField(n, config));
4125 }
4126 return node;
4127}
4128function resolveValueKeywords(node, config, snippet, minScore) {
4129 for (const cssVal of node.value) {
4130 const value = [];
4131 for (const token of cssVal.value) {
4132 if (token.type === 'Literal') {
4133 value.push(resolveKeyword(token.value, config, snippet, minScore) || token);
4134 }
4135 else if (token.type === 'FunctionCall') {
4136 // For function calls, we should find matching function call
4137 // and merge arguments
4138 const match = resolveKeyword(token.name, config, snippet, minScore);
4139 if (match && match.type === 'FunctionCall') {
4140 value.push(Object.assign(Object.assign({}, match), { arguments: token.arguments.concat(match.arguments.slice(token.arguments.length)) }));
4141 }
4142 else {
4143 value.push(token);
4144 }
4145 }
4146 else {
4147 value.push(token);
4148 }
4149 }
4150 cssVal.value = value;
4151 }
4152}
4153/**
4154 * Resolves given parsed abbreviation node as a snippet: a plain code chunk
4155 */
4156function resolveAsSnippet(node, snippet) {
4157 // When resolving snippets, we have to do the following:
4158 // 1. Replace field placeholders with actual field tokens.
4159 // 2. If input values given, put them instead of fields
4160 let offset = 0;
4161 let m;
4162 const reField = /\$\{(\d+)(:[^}]+)?\}/g;
4163 const inputValue = node.value[0];
4164 const outputValue = [];
4165 while (m = reField.exec(snippet.value)) {
4166 if (offset !== m.index) {
4167 outputValue.push(literal(snippet.value.slice(offset, m.index)));
4168 }
4169 offset = m.index + m[0].length;
4170 if (inputValue && inputValue.value.length) {
4171 outputValue.push(inputValue.value.shift());
4172 }
4173 else {
4174 outputValue.push(field(Number(m[1]), m[2] ? m[2].slice(1) : ''));
4175 }
4176 }
4177 const tail = snippet.value.slice(offset);
4178 if (tail) {
4179 outputValue.push(literal(tail));
4180 }
4181 node.name = void 0;
4182 node.value = [cssValue(...outputValue)];
4183 return node;
4184}
4185/**
4186 * Finds best matching item from `items` array
4187 * @param abbr Abbreviation to match
4188 * @param items List of items for match
4189 * @param minScore The minimum score the best matched item should have to be a valid match.
4190 */
4191function findBestMatch(abbr, items, minScore = 0, partialMatch = false) {
4192 let matchedItem = null;
4193 let maxScore = 0;
4194 for (const item of items) {
4195 const score = scoreMatch(abbr, getScoringPart(item), partialMatch);
4196 if (score === 1) {
4197 // direct hit, no need to look further
4198 return item;
4199 }
4200 if (score && score >= maxScore) {
4201 maxScore = score;
4202 matchedItem = item;
4203 }
4204 }
4205 return maxScore >= minScore ? matchedItem : null;
4206}
4207function getScoringPart(item) {
4208 return typeof item === 'string' ? item : item.key;
4209}
4210/**
4211 * Returns a part of `abbr` that wasn’t directly matched against `str`.
4212 * For example, if abbreviation `poas` is matched against `position`,
4213 * the unmatched part will be `as` since `a` wasn’t found in string stream
4214 */
4215function getUnmatchedPart(abbr, str) {
4216 for (let i = 0, lastPos = 0; i < abbr.length; i++) {
4217 lastPos = str.indexOf(abbr[i], lastPos);
4218 if (lastPos === -1) {
4219 return abbr.slice(i);
4220 }
4221 lastPos++;
4222 }
4223 return '';
4224}
4225/**
4226 * Resolves given keyword shorthand into matched snippet keyword or global keyword,
4227 * if possible
4228 */
4229function resolveKeyword(kw, config, snippet, minScore) {
4230 let ref;
4231 if (snippet) {
4232 if (ref = findBestMatch(kw, Object.keys(snippet.keywords), minScore)) {
4233 return snippet.keywords[ref];
4234 }
4235 for (const dep of snippet.dependencies) {
4236 if (ref = findBestMatch(kw, Object.keys(dep.keywords), minScore)) {
4237 return dep.keywords[ref];
4238 }
4239 }
4240 }
4241 if (ref = findBestMatch(kw, config.options['stylesheet.keywords'], minScore)) {
4242 return literal(ref);
4243 }
4244 return null;
4245}
4246/**
4247 * Resolves numeric values in given abbreviation node
4248 */
4249function resolveNumericValue(node, config) {
4250 const aliases = config.options['stylesheet.unitAliases'];
4251 const unitless = config.options['stylesheet.unitless'];
4252 for (const v of node.value) {
4253 for (const t of v.value) {
4254 if (t.type === 'NumberValue') {
4255 if (t.unit) {
4256 t.unit = aliases[t.unit] || t.unit;
4257 }
4258 else if (t.value !== 0 && !unitless.includes(node.name)) {
4259 t.unit = t.rawValue.includes('.')
4260 ? config.options['stylesheet.floatUnit']
4261 : config.options['stylesheet.intUnit'];
4262 }
4263 }
4264 }
4265 }
4266}
4267/**
4268 * Constructs CSS value token
4269 */
4270function cssValue(...args) {
4271 return {
4272 type: 'CSSValue',
4273 value: args
4274 };
4275}
4276/**
4277 * Constructs literal token
4278 */
4279function literal(value) {
4280 return { type: 'Literal', value };
4281}
4282/**
4283 * Constructs field token
4284 */
4285function field(index, name) {
4286 return { type: 'Field', index, name };
4287}
4288/**
4289 * Check if given value contains fields
4290 */
4291function hasField(value) {
4292 for (const v of value.value) {
4293 if (v.type === 'Field' || (v.type === 'FunctionCall' && v.arguments.some(hasField))) {
4294 return true;
4295 }
4296 }
4297 return false;
4298}
4299/**
4300 * Wraps tokens of given abbreviation with fields
4301 */
4302function wrapWithField(node, config, state = { index: 1 }) {
4303 let value = [];
4304 for (const v of node.value) {
4305 switch (v.type) {
4306 case 'ColorValue':
4307 value.push(field(state.index++, color(v, config.options['stylesheet.shortHex'])));
4308 break;
4309 case 'Literal':
4310 value.push(field(state.index++, v.value));
4311 break;
4312 case 'NumberValue':
4313 value.push(field(state.index++, `${v.value}${v.unit}`));
4314 break;
4315 case 'StringValue':
4316 const q = v.quote === 'single' ? '\'' : '"';
4317 value.push(field(state.index++, q + v.value + q));
4318 break;
4319 case 'FunctionCall':
4320 value.push(field(state.index++, v.name), literal('('));
4321 for (let i = 0, il = v.arguments.length; i < il; i++) {
4322 value = value.concat(wrapWithField(v.arguments[i], config, state).value);
4323 if (i !== il - 1) {
4324 value.push(literal(', '));
4325 }
4326 }
4327 value.push(literal(')'));
4328 break;
4329 default:
4330 value.push(v);
4331 }
4332 }
4333 return Object.assign(Object.assign({}, node), { value });
4334}
4335/**
4336 * Check if abbreviation should be expanded in CSS value context
4337 */
4338function isValueScope(config) {
4339 if (config.context) {
4340 return config.context.name === CSSAbbreviationScope.Value || !config.context.name.startsWith('@@');
4341 }
4342 return false;
4343}
4344/**
4345 * Returns snippets for given scope
4346 */
4347function getSnippetsForScope(snippets, config) {
4348 if (config.context) {
4349 if (config.context.name === CSSAbbreviationScope.Section) {
4350 return snippets.filter(s => s.type === CSSSnippetType.Raw);
4351 }
4352 if (config.context.name === CSSAbbreviationScope.Property) {
4353 return snippets.filter(s => s.type === CSSSnippetType.Property);
4354 }
4355 }
4356 return snippets;
4357}
4358
4359var markupSnippets = {
4360 "a": "a[href]",
4361 "a:blank": "a[href='http://${0}' target='_blank' rel='noopener noreferrer']",
4362 "a:link": "a[href='http://${0}']",
4363 "a:mail": "a[href='mailto:${0}']",
4364 "a:tel": "a[href='tel:+${0}']",
4365 "abbr": "abbr[title]",
4366 "acr|acronym": "acronym[title]",
4367 "base": "base[href]/",
4368 "basefont": "basefont/",
4369 "br": "br/",
4370 "frame": "frame/",
4371 "hr": "hr/",
4372 "bdo": "bdo[dir]",
4373 "bdo:r": "bdo[dir=rtl]",
4374 "bdo:l": "bdo[dir=ltr]",
4375 "col": "col/",
4376 "link": "link[rel=stylesheet href]/",
4377 "link:css": "link[href='${1:style}.css']",
4378 "link:print": "link[href='${1:print}.css' media=print]",
4379 "link:favicon": "link[rel='shortcut icon' type=image/x-icon href='${1:favicon.ico}']",
4380 "link:mf|link:manifest": "link[rel='manifest' href='${1:manifest.json}']",
4381 "link:touch": "link[rel=apple-touch-icon href='${1:favicon.png}']",
4382 "link:rss": "link[rel=alternate type=application/rss+xml title=RSS href='${1:rss.xml}']",
4383 "link:atom": "link[rel=alternate type=application/atom+xml title=Atom href='${1:atom.xml}']",
4384 "link:im|link:import": "link[rel=import href='${1:component}.html']",
4385 "meta": "meta/",
4386 "meta:utf": "meta[http-equiv=Content-Type content='text/html;charset=UTF-8']",
4387 "meta:vp": "meta[name=viewport content='width=${1:device-width}, initial-scale=${2:1.0}']",
4388 "meta:compat": "meta[http-equiv=X-UA-Compatible content='${1:IE=7}']",
4389 "meta:edge": "meta:compat[content='${1:ie=edge}']",
4390 "meta:redirect": "meta[http-equiv=refresh content='0; url=${1:http://example.com}']",
4391 "meta:refresh": "meta[http-equiv=refresh content='${1:5}']",
4392 "meta:kw": "meta[name=keywords content]",
4393 "meta:desc": "meta[name=description content]",
4394 "style": "style",
4395 "script": "script",
4396 "script:src": "script[src]",
4397 "script:module": "script[type=module src]",
4398 "img": "img[src alt]/",
4399 "img:s|img:srcset": "img[srcset src alt]",
4400 "img:z|img:sizes": "img[sizes srcset src alt]",
4401 "picture": "picture",
4402 "src|source": "source/",
4403 "src:sc|source:src": "source[src type]",
4404 "src:s|source:srcset": "source[srcset]",
4405 "src:t|source:type": "source[srcset type='${1:image/}']",
4406 "src:z|source:sizes": "source[sizes srcset]",
4407 "src:m|source:media": "source[media='(${1:min-width: })' srcset]",
4408 "src:mt|source:media:type": "source:media[type='${2:image/}']",
4409 "src:mz|source:media:sizes": "source:media[sizes srcset]",
4410 "src:zt|source:sizes:type": "source[sizes srcset type='${1:image/}']",
4411 "iframe": "iframe[src frameborder=0]",
4412 "embed": "embed[src type]/",
4413 "object": "object[data type]",
4414 "param": "param[name value]/",
4415 "map": "map[name]",
4416 "area": "area[shape coords href alt]/",
4417 "area:d": "area[shape=default]",
4418 "area:c": "area[shape=circle]",
4419 "area:r": "area[shape=rect]",
4420 "area:p": "area[shape=poly]",
4421 "form": "form[action]",
4422 "form:get": "form[method=get]",
4423 "form:post": "form[method=post]",
4424 "label": "label[for]",
4425 "input": "input[type=${1:text}]/",
4426 "inp": "input[name=${1} id=${1}]",
4427 "input:h|input:hidden": "input[type=hidden name]",
4428 "input:t|input:text": "inp[type=text]",
4429 "input:search": "inp[type=search]",
4430 "input:email": "inp[type=email]",
4431 "input:url": "inp[type=url]",
4432 "input:p|input:password": "inp[type=password]",
4433 "input:datetime": "inp[type=datetime]",
4434 "input:date": "inp[type=date]",
4435 "input:datetime-local": "inp[type=datetime-local]",
4436 "input:month": "inp[type=month]",
4437 "input:week": "inp[type=week]",
4438 "input:time": "inp[type=time]",
4439 "input:tel": "inp[type=tel]",
4440 "input:number": "inp[type=number]",
4441 "input:color": "inp[type=color]",
4442 "input:c|input:checkbox": "inp[type=checkbox]",
4443 "input:r|input:radio": "inp[type=radio]",
4444 "input:range": "inp[type=range]",
4445 "input:f|input:file": "inp[type=file]",
4446 "input:s|input:submit": "input[type=submit value]",
4447 "input:i|input:image": "input[type=image src alt]",
4448 "input:b|input:btn|input:button": "input[type=button value]",
4449 "input:reset": "input:button[type=reset]",
4450 "isindex": "isindex/",
4451 "select": "select[name=${1} id=${1}]",
4452 "select:d|select:disabled": "select[disabled.]",
4453 "opt|option": "option[value]",
4454 "textarea": "textarea[name=${1} id=${1}]",
4455 "tarea:c|textarea:cols":"textarea[name=${1} id=${1} cols=${2:30}]",
4456 "tarea:r|textarea:rows":"textarea[name=${1} id=${1} rows=${3:10}]",
4457 "tarea:cr|textarea:cols:rows":"textarea[name=${1} id=${1} cols=${2:30} rows=${3:10}]",
4458 "marquee": "marquee[behavior direction]",
4459 "menu:c|menu:context": "menu[type=context]",
4460 "menu:t|menu:toolbar": "menu[type=toolbar]",
4461 "video": "video[src]",
4462 "audio": "audio[src]",
4463 "html:xml": "html[xmlns=http://www.w3.org/1999/xhtml]",
4464 "keygen": "keygen/",
4465 "command": "command/",
4466 "btn:s|button:s|button:submit" : "button[type=submit]",
4467 "btn:r|button:r|button:reset" : "button[type=reset]",
4468 "btn:b|button:b|button:button" : "button[type=button]",
4469 "btn:d|button:d|button:disabled" : "button[disabled.]",
4470 "fst:d|fset:d|fieldset:d|fieldset:disabled" : "fieldset[disabled.]",
4471
4472 "bq": "blockquote",
4473 "fig": "figure",
4474 "figc": "figcaption",
4475 "pic": "picture",
4476 "ifr": "iframe",
4477 "emb": "embed",
4478 "obj": "object",
4479 "cap": "caption",
4480 "colg": "colgroup",
4481 "fst": "fieldset",
4482 "btn": "button",
4483 "optg": "optgroup",
4484 "tarea": "textarea",
4485 "leg": "legend",
4486 "sect": "section",
4487 "art": "article",
4488 "hdr": "header",
4489 "ftr": "footer",
4490 "adr": "address",
4491 "dlg": "dialog",
4492 "str": "strong",
4493 "prog": "progress",
4494 "mn": "main",
4495 "tem": "template",
4496 "fset": "fieldset",
4497 "datal": "datalist",
4498 "kg": "keygen",
4499 "out": "output",
4500 "det": "details",
4501 "sum": "summary",
4502 "cmd": "command",
4503 "data": "data[value]",
4504 "meter": "meter[value]",
4505 "time": "time[datetime]",
4506
4507 "ri:d|ri:dpr": "img:s",
4508 "ri:v|ri:viewport": "img:z",
4509 "ri:a|ri:art": "pic>src:m+img",
4510 "ri:t|ri:type": "pic>src:t+img",
4511
4512 "!!!": "{<!DOCTYPE html>}",
4513 "doc": "html[lang=${lang}]>(head>meta[charset=${charset}]+meta:vp+title{${1:Document}})+body",
4514 "!|html:5": "!!!+doc",
4515
4516 "c": "{<!-- ${0} -->}",
4517 "cc:ie": "{<!--[if IE]>${0}<![endif]-->}",
4518 "cc:noie": "{<!--[if !IE]><!-->${0}<!--<![endif]-->}"
4519};
4520
4521var stylesheetSnippets = {
4522 "@f": "@font-face {\n\tfont-family: ${1};\n\tsrc: url(${2});\n}",
4523 "@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}",
4524 "@i|@import": "@import url(${0});",
4525 "@kf": "@keyframes ${1:identifier} {\n\t${2}\n}",
4526 "@m|@media": "@media ${1:screen} {\n\t${0}\n}",
4527 "ac": "align-content:start|end|flex-start|flex-end|center|space-between|space-around|stretch|space-evenly",
4528 "ai": "align-items:start|end|flex-start|flex-end|center|baseline|stretch",
4529 "anim": "animation:${1:name} ${2:duration} ${3:timing-function} ${4:delay} ${5:iteration-count} ${6:direction} ${7:fill-mode}",
4530 "animdel": "animation-delay:time",
4531 "animdir": "animation-direction:normal|reverse|alternate|alternate-reverse",
4532 "animdur": "animation-duration:${1:0}s",
4533 "animfm": "animation-fill-mode:both|forwards|backwards",
4534 "animic": "animation-iteration-count:1|infinite",
4535 "animn": "animation-name",
4536 "animps": "animation-play-state:running|paused",
4537 "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})",
4538 "ap": "appearance:none",
4539 "as": "align-self:start|end|auto|flex-start|flex-end|center|baseline|stretch",
4540 "b": "bottom",
4541 "bd": "border:${1:1px} ${2:solid} ${3:#000}",
4542 "bdb": "border-bottom:${1:1px} ${2:solid} ${3:#000}",
4543 "bdbc": "border-bottom-color:${1:#000}",
4544 "bdbi": "border-bottom-image:url(${0})",
4545 "bdbk": "border-break:close",
4546 "bdbli": "border-bottom-left-image:url(${0})|continue",
4547 "bdblrs": "border-bottom-left-radius",
4548 "bdbri": "border-bottom-right-image:url(${0})|continue",
4549 "bdbrrs": "border-bottom-right-radius",
4550 "bdbs": "border-bottom-style",
4551 "bdbw": "border-bottom-width",
4552 "bdc": "border-color:${1:#000}",
4553 "bdci": "border-corner-image:url(${0})|continue",
4554 "bdcl": "border-collapse:collapse|separate",
4555 "bdf": "border-fit:repeat|clip|scale|stretch|overwrite|overflow|space",
4556 "bdi": "border-image:url(${0})",
4557 "bdl": "border-left:${1:1px} ${2:solid} ${3:#000}",
4558 "bdlc": "border-left-color:${1:#000}",
4559 "bdlen": "border-length",
4560 "bdli": "border-left-image:url(${0})",
4561 "bdls": "border-left-style",
4562 "bdlw": "border-left-width",
4563 "bdr": "border-right:${1:1px} ${2:solid} ${3:#000}",
4564 "bdrc": "border-right-color:${1:#000}",
4565 "bdri": "border-right-image:url(${0})",
4566 "bdrs": "border-radius",
4567 "bdrst": "border-right-style",
4568 "bdrw": "border-right-width",
4569 "bds": "border-style:none|hidden|dotted|dashed|solid|double|dot-dash|dot-dot-dash|wave|groove|ridge|inset|outset",
4570 "bdsp": "border-spacing",
4571 "bdt": "border-top:${1:1px} ${2:solid} ${3:#000}",
4572 "bdtc": "border-top-color:${1:#000}",
4573 "bdti": "border-top-image:url(${0})",
4574 "bdtli": "border-top-left-image:url(${0})|continue",
4575 "bdtlrs": "border-top-left-radius",
4576 "bdtri": "border-top-right-image:url(${0})|continue",
4577 "bdtrrs": "border-top-right-radius",
4578 "bdts": "border-top-style",
4579 "bdtw": "border-top-width",
4580 "bdw": "border-width",
4581 "bbs": "border-block-start",
4582 "bbe": "border-block-end",
4583 "bis": "border-inline-start",
4584 "bie": "border-inline-end",
4585 "bfv": "backface-visibility:hidden|visible",
4586 "bg": "background:${1:#000}",
4587 "bg:n": "background: none",
4588 "bga": "background-attachment:fixed|scroll",
4589 "bgbk": "background-break:bounding-box|each-box|continuous",
4590 "bgc": "background-color:${1:#fff}",
4591 "bgcp": "background-clip:padding-box|border-box|content-box|no-clip",
4592 "bgi": "background-image:url(${0})",
4593 "bgo": "background-origin:padding-box|border-box|content-box",
4594 "bgp": "background-position:${1:0} ${2:0}",
4595 "bgpx": "background-position-x",
4596 "bgpy": "background-position-y",
4597 "bgr": "background-repeat:no-repeat|repeat-x|repeat-y|space|round",
4598 "bgsz": "background-size:contain|cover",
4599 "bs": "block-size",
4600 "bxsh": "box-shadow:${1:inset }${2:hoff} ${3:voff} ${4:blur} ${5:#000}|none",
4601 "bxsz": "box-sizing:border-box|content-box|border-box",
4602 "c": "color:${1:#000}",
4603 "cr": "color:rgb(${1:0}, ${2:0}, ${3:0})",
4604 "cra": "color:rgba(${1:0}, ${2:0}, ${3:0}, ${4:.5})",
4605 "cl": "clear:both|left|right|none",
4606 "cm": "/* ${0} */",
4607 "cnt": "content:'${0}'|normal|open-quote|no-open-quote|close-quote|no-close-quote|attr(${0})|counter(${0})|counters(${0})",
4608 "coi": "counter-increment",
4609 "colm": "columns",
4610 "colmc": "column-count",
4611 "colmf": "column-fill",
4612 "colmg": "column-gap",
4613 "colmr": "column-rule",
4614 "colmrc": "column-rule-color",
4615 "colmrs": "column-rule-style",
4616 "colmrw": "column-rule-width",
4617 "colms": "column-span",
4618 "colmw": "column-width",
4619 "cor": "counter-reset",
4620 "cp": "clip:auto|rect(${1:top} ${2:right} ${3:bottom} ${4:left})",
4621 "cps": "caption-side:top|bottom",
4622 "cur": "cursor:pointer|auto|default|crosshair|hand|help|move|pointer|text",
4623 "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",
4624 "ec": "empty-cells:show|hide",
4625 "f": "font:${1:1em} ${2:sans-serif}",
4626 "fd": "font-display:auto|block|swap|fallback|optional",
4627 "fef": "font-effect:none|engrave|emboss|outline",
4628 "fem": "font-emphasize",
4629 "femp": "font-emphasize-position:before|after",
4630 "fems": "font-emphasize-style:none|accent|dot|circle|disc",
4631 "ff": "font-family:serif|sans-serif|cursive|fantasy|monospace",
4632 "fft": "font-family:\"Times New Roman\", Times, Baskerville, Georgia, serif",
4633 "ffa": "font-family:Arial, \"Helvetica Neue\", Helvetica, sans-serif",
4634 "ffv": "font-family:Verdana, Geneva, sans-serif",
4635 "fl": "float:left|right|none",
4636 "fs": "font-style:italic|normal|oblique",
4637 "fsm": "font-smoothing:antialiased|subpixel-antialiased|none",
4638 "fst": "font-stretch:normal|ultra-condensed|extra-condensed|condensed|semi-condensed|semi-expanded|expanded|extra-expanded|ultra-expanded",
4639 "fv": "font-variant:normal|small-caps",
4640 "fvs": "font-variation-settings:normal|inherit|initial|unset",
4641 "fw": "font-weight:normal|bold|bolder|lighter",
4642 "fx": "flex",
4643 "fxb": "flex-basis:fill|max-content|min-content|fit-content|content",
4644 "fxd": "flex-direction:row|row-reverse|column|column-reverse",
4645 "fxf": "flex-flow",
4646 "fxg": "flex-grow",
4647 "fxsh": "flex-shrink",
4648 "fxw": "flex-wrap:nowrap|wrap|wrap-reverse",
4649 "fsz": "font-size",
4650 "fsza": "font-size-adjust",
4651 "g": "gap",
4652 "gtc": "grid-template-columns:repeat(${0})|minmax()",
4653 "gtr": "grid-template-rows:repeat(${0})|minmax()",
4654 "gta": "grid-template-areas",
4655 "gt": "grid-template",
4656 "gg": "grid-gap",
4657 "gcg": "grid-column-gap",
4658 "grg": "grid-row-gap",
4659 "gac": "grid-auto-columns:auto|minmax()",
4660 "gar": "grid-auto-rows:auto|minmax()",
4661 "gaf": "grid-auto-flow:row|column|dense|inherit|initial|unset",
4662 "gd": "grid",
4663 "gc": "grid-column",
4664 "gcs": "grid-column-start",
4665 "gce": "grid-column-end",
4666 "gr": "grid-row",
4667 "grs": "grid-row-start",
4668 "gre": "grid-row-end",
4669 "ga": "grid-area",
4670 "h": "height",
4671 "is": "inline-size",
4672 "jc": "justify-content:start|end|stretch|flex-start|flex-end|center|space-between|space-around|space-evenly",
4673 "ji": "justify-items:start|end|center|stretch",
4674 "js": "justify-self:start|end|center|stretch",
4675 "l": "left",
4676 "lg": "background-image:linear-gradient(${1})",
4677 "lh": "line-height",
4678 "lis": "list-style",
4679 "lisi": "list-style-image",
4680 "lisp": "list-style-position:inside|outside",
4681 "list": "list-style-type:disc|circle|square|decimal|decimal-leading-zero|lower-roman|upper-roman",
4682 "lts": "letter-spacing:normal",
4683 "m": "margin",
4684 "mah": "max-height",
4685 "mar": "max-resolution",
4686 "maw": "max-width",
4687 "mb": "margin-bottom",
4688 "mih": "min-height",
4689 "mir": "min-resolution",
4690 "miw": "min-width",
4691 "ml": "margin-left",
4692 "mr": "margin-right",
4693 "mt": "margin-top",
4694 "mbs": "margin-block-start",
4695 "mbe": "margin-block-end",
4696 "mis": "margin-inline-start",
4697 "mie": "margin-inline-end",
4698 "ol": "outline",
4699 "olc": "outline-color:${1:#000}|invert",
4700 "olo": "outline-offset",
4701 "ols": "outline-style:none|dotted|dashed|solid|double|groove|ridge|inset|outset",
4702 "olw": "outline-width:thin|medium|thick",
4703 "op|opa": "opacity",
4704 "ord": "order",
4705 "ori": "orientation:landscape|portrait",
4706 "orp": "orphans",
4707 "ov": "overflow:hidden|visible|hidden|scroll|auto",
4708 "ovs": "overflow-style:scrollbar|auto|scrollbar|panner|move|marquee",
4709 "ovx": "overflow-x:hidden|visible|hidden|scroll|auto",
4710 "ovy": "overflow-y:hidden|visible|hidden|scroll|auto",
4711 "p": "padding",
4712 "pb": "padding-bottom",
4713 "pgba": "page-break-after:auto|always|left|right",
4714 "pgbb": "page-break-before:auto|always|left|right",
4715 "pgbi": "page-break-inside:auto|avoid",
4716 "pl": "padding-left",
4717 "pos": "position:relative|absolute|relative|fixed|static",
4718 "pr": "padding-right",
4719 "pt": "padding-top",
4720 "pbs": "padding-block-start",
4721 "pbe": "padding-block-end",
4722 "pis": "padding-inline-start",
4723 "pie": "padding-inline-end",
4724 "spbs": "scroll-padding-block-start",
4725 "spbe": "scroll-padding-block-end",
4726 "spis": "scroll-padding-inline-start",
4727 "spie": "scroll-padding-inline-end",
4728 "q": "quotes",
4729 "qen": "quotes:'\\201C' '\\201D' '\\2018' '\\2019'",
4730 "qru": "quotes:'\\00AB' '\\00BB' '\\201E' '\\201C'",
4731 "r": "right",
4732 "rsz": "resize:none|both|horizontal|vertical",
4733 "t": "top",
4734 "ta": "text-align:left|center|right|justify",
4735 "tal": "text-align-last:left|center|right",
4736 "tbl": "table-layout:fixed",
4737 "td": "text-decoration:none|underline|overline|line-through",
4738 "te": "text-emphasis:none|accent|dot|circle|disc|before|after",
4739 "th": "text-height:auto|font-size|text-size|max-size",
4740 "ti": "text-indent",
4741 "tj": "text-justify:auto|inter-word|inter-ideograph|inter-cluster|distribute|kashida|tibetan",
4742 "to": "text-outline:${1:0} ${2:0} ${3:#000}",
4743 "tov": "text-overflow:ellipsis|clip",
4744 "tr": "text-replace",
4745 "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})",
4746 "trfo": "transform-origin",
4747 "trfs": "transform-style:preserve-3d",
4748 "trs": "transition:${1:prop} ${2:time}",
4749 "trsde": "transition-delay:${1:time}",
4750 "trsdu": "transition-duration:${1:time}",
4751 "trsp": "transition-property:${1:prop}",
4752 "trstf": "transition-timing-function:${1:fn}",
4753 "tsh": "text-shadow:${1:hoff} ${2:voff} ${3:blur} ${4:#000}",
4754 "tt": "text-transform:uppercase|lowercase|capitalize|none",
4755 "tw": "text-wrap:none|normal|unrestricted|suppress",
4756 "us": "user-select:none",
4757 "v": "visibility:hidden|visible|collapse",
4758 "va": "vertical-align:top|super|text-top|middle|baseline|bottom|text-bottom|sub",
4759 "w|wid": "width",
4760 "whs": "white-space:nowrap|pre|pre-wrap|pre-line|normal",
4761 "whsc": "white-space-collapse:normal|keep-all|loose|break-strict|break-all",
4762 "wido": "widows",
4763 "wm": "writing-mode:lr-tb|lr-tb|lr-bt|rl-tb|rl-bt|tb-rl|tb-lr|bt-lr|bt-rl",
4764 "wob": "word-break:normal|keep-all|break-all",
4765 "wos": "word-spacing",
4766 "wow": "word-wrap:none|unrestricted|suppress|break-word|normal",
4767 "z": "z-index",
4768 "zom": "zoom:1"
4769};
4770
4771var xslSnippets = {
4772 "tm|tmatch": "xsl:template[match mode]",
4773 "tn|tname": "xsl:template[name]",
4774 "call": "xsl:call-template[name]",
4775 "ap": "xsl:apply-templates[select mode]",
4776 "api": "xsl:apply-imports",
4777 "imp": "xsl:import[href]",
4778 "inc": "xsl:include[href]",
4779 "ch": "xsl:choose",
4780 "wh|xsl:when": "xsl:when[test]",
4781 "ot": "xsl:otherwise",
4782 "if": "xsl:if[test]",
4783 "par": "xsl:param[name]",
4784 "pare": "xsl:param[name select]",
4785 "var": "xsl:variable[name]",
4786 "vare": "xsl:variable[name select]",
4787 "wp": "xsl:with-param[name select]",
4788 "key": "xsl:key[name match use]",
4789 "elem": "xsl:element[name]",
4790 "attr": "xsl:attribute[name]",
4791 "attrs": "xsl:attribute-set[name]",
4792 "cp": "xsl:copy[select]",
4793 "co": "xsl:copy-of[select]",
4794 "val": "xsl:value-of[select]",
4795 "for|each": "xsl:for-each[select]",
4796 "tex": "xsl:text",
4797 "com": "xsl:comment",
4798 "msg": "xsl:message[terminate=no]",
4799 "fall": "xsl:fallback",
4800 "num": "xsl:number[value]",
4801 "nam": "namespace-alias[stylesheet-prefix result-prefix]",
4802 "pres": "xsl:preserve-space[elements]",
4803 "strip": "xsl:strip-space[elements]",
4804 "proc": "xsl:processing-instruction[name]",
4805 "sort": "xsl:sort[select order]",
4806 "choose": "xsl:choose>xsl:when+xsl:otherwise",
4807 "xsl": "!!!+xsl:stylesheet[version=1.0 xmlns:xsl=http://www.w3.org/1999/XSL/Transform]>{\n|}",
4808 "!!!": "{<?xml version=\"1.0\" encoding=\"UTF-8\"?>}"
4809};
4810
4811var pugSnippets = {
4812 "!!!": "{doctype html}"
4813};
4814
4815var variables = {
4816 "lang": "en",
4817 "locale": "en-US",
4818 "charset": "UTF-8",
4819 "indentation": "\t",
4820 "newline": "\n"
4821};
4822
4823/**
4824 * Default syntaxes for abbreviation types
4825 */
4826const defaultSyntaxes = {
4827 markup: 'html',
4828 stylesheet: 'css'
4829};
4830const defaultOptions$1 = {
4831 'inlineElements': [
4832 'a', 'abbr', 'acronym', 'applet', 'b', 'basefont', 'bdo',
4833 'big', 'br', 'button', 'cite', 'code', 'del', 'dfn', 'em', 'font', 'i',
4834 'iframe', 'img', 'input', 'ins', 'kbd', 'label', 'map', 'object', 'q',
4835 's', 'samp', 'select', 'small', 'span', 'strike', 'strong', 'sub', 'sup',
4836 'textarea', 'tt', 'u', 'var'
4837 ],
4838 'output.indent': '\t',
4839 'output.baseIndent': '',
4840 'output.newline': '\n',
4841 'output.tagCase': '',
4842 'output.attributeCase': '',
4843 'output.attributeQuotes': 'double',
4844 'output.format': true,
4845 'output.formatLeafNode': false,
4846 'output.formatSkip': ['html'],
4847 'output.formatForce': ['body'],
4848 'output.inlineBreak': 3,
4849 'output.compactBoolean': false,
4850 'output.booleanAttributes': [
4851 'contenteditable', 'seamless', 'async', 'autofocus',
4852 'autoplay', 'checked', 'controls', 'defer', 'disabled', 'formnovalidate',
4853 'hidden', 'ismap', 'loop', 'multiple', 'muted', 'novalidate', 'readonly',
4854 'required', 'reversed', 'selected', 'typemustmatch'
4855 ],
4856 'output.reverseAttributes': false,
4857 'output.selfClosingStyle': 'html',
4858 'output.field': (index, placeholder) => placeholder,
4859 'output.text': text => text,
4860 'markup.href': true,
4861 'comment.enabled': false,
4862 'comment.trigger': ['id', 'class'],
4863 'comment.before': '',
4864 'comment.after': '\n<!-- /[#ID][.CLASS] -->',
4865 'bem.enabled': false,
4866 'bem.element': '__',
4867 'bem.modifier': '_',
4868 'jsx.enabled': false,
4869 'stylesheet.keywords': ['auto', 'inherit', 'unset', 'none'],
4870 'stylesheet.unitless': ['z-index', 'line-height', 'opacity', 'font-weight', 'zoom', 'flex', 'flex-grow', 'flex-shrink'],
4871 'stylesheet.shortHex': true,
4872 'stylesheet.between': ': ',
4873 'stylesheet.after': ';',
4874 'stylesheet.intUnit': 'px',
4875 'stylesheet.floatUnit': 'em',
4876 'stylesheet.unitAliases': { e: 'em', p: '%', x: 'ex', r: 'rem' },
4877 'stylesheet.json': false,
4878 'stylesheet.jsonDoubleQuotes': false,
4879 'stylesheet.fuzzySearchMinScore': 0
4880};
4881const defaultConfig = {
4882 type: 'markup',
4883 syntax: 'html',
4884 variables,
4885 snippets: {},
4886 options: defaultOptions$1
4887};
4888/**
4889 * Default per-syntax config
4890 */
4891const syntaxConfig = {
4892 markup: {
4893 snippets: parseSnippets(markupSnippets),
4894 },
4895 xhtml: {
4896 options: {
4897 'output.selfClosingStyle': 'xhtml'
4898 }
4899 },
4900 xml: {
4901 options: {
4902 'output.selfClosingStyle': 'xml'
4903 }
4904 },
4905 xsl: {
4906 snippets: parseSnippets(xslSnippets),
4907 options: {
4908 'output.selfClosingStyle': 'xml'
4909 }
4910 },
4911 jsx: {
4912 options: {
4913 'jsx.enabled': true,
4914 'markup.attributes': {
4915 'class': 'className',
4916 'class*': 'styleName',
4917 'for': 'htmlFor'
4918 },
4919 'markup.valuePrefix': {
4920 'class*': 'styles'
4921 }
4922 }
4923 },
4924 vue: {
4925 options: {
4926 'markup.attributes': {
4927 'class*': ':class',
4928 }
4929 }
4930 },
4931 svelte: {
4932 options: {
4933 'jsx.enabled': true
4934 }
4935 },
4936 pug: {
4937 snippets: parseSnippets(pugSnippets)
4938 },
4939 stylesheet: {
4940 snippets: parseSnippets(stylesheetSnippets)
4941 },
4942 sass: {
4943 options: {
4944 'stylesheet.after': ''
4945 }
4946 },
4947 stylus: {
4948 options: {
4949 'stylesheet.between': ' ',
4950 'stylesheet.after': '',
4951 }
4952 }
4953};
4954/**
4955 * Parses raw snippets definitions with possibly multiple keys into a plan
4956 * snippet map
4957 */
4958function parseSnippets(snippets) {
4959 const result = {};
4960 Object.keys(snippets).forEach(k => {
4961 for (const name of k.split('|')) {
4962 result[name] = snippets[k];
4963 }
4964 });
4965 return result;
4966}
4967function resolveConfig(config = {}, globals = {}) {
4968 const type = config.type || 'markup';
4969 const syntax = config.syntax || defaultSyntaxes[type];
4970 return Object.assign(Object.assign(Object.assign({}, defaultConfig), config), { type,
4971 syntax, variables: mergedData(type, syntax, 'variables', config, globals), snippets: mergedData(type, syntax, 'snippets', config, globals), options: mergedData(type, syntax, 'options', config, globals) });
4972}
4973function mergedData(type, syntax, key, config, globals = {}) {
4974 const typeDefaults = syntaxConfig[type];
4975 const typeOverride = globals[type];
4976 const syntaxDefaults = syntaxConfig[syntax];
4977 const syntaxOverride = globals[syntax];
4978 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]);
4979}
4980
4981/**
4982 * Creates structure for scanning given string in backward direction
4983 */
4984function backwardScanner(text, start = 0) {
4985 return { text, start, pos: text.length };
4986}
4987/**
4988 * Check if given scanner position is at start of scanned text
4989 */
4990function sol(scanner) {
4991 return scanner.pos === scanner.start;
4992}
4993/**
4994 * “Peeks” character code an current scanner location without advancing it
4995 */
4996function peek(scanner, offset = 0) {
4997 return scanner.text.charCodeAt(scanner.pos - 1 + offset);
4998}
4999/**
5000 * Returns current character code and moves character location one symbol back
5001 */
5002function previous(scanner) {
5003 if (!sol(scanner)) {
5004 return scanner.text.charCodeAt(--scanner.pos);
5005 }
5006}
5007/**
5008 * Consumes current character code if it matches given `match` code or function
5009 */
5010function consume(scanner, match) {
5011 if (sol(scanner)) {
5012 return false;
5013 }
5014 const ok = typeof match === 'function'
5015 ? match(peek(scanner))
5016 : match === peek(scanner);
5017 if (ok) {
5018 scanner.pos--;
5019 }
5020 return !!ok;
5021}
5022function consumeWhile(scanner, match) {
5023 const start = scanner.pos;
5024 while (consume(scanner, match)) {
5025 // empty
5026 }
5027 return scanner.pos < start;
5028}
5029
5030var Chars$1;
5031(function (Chars) {
5032 Chars[Chars["SingleQuote"] = 39] = "SingleQuote";
5033 Chars[Chars["DoubleQuote"] = 34] = "DoubleQuote";
5034 Chars[Chars["Escape"] = 92] = "Escape";
5035})(Chars$1 || (Chars$1 = {}));
5036/**
5037 * Check if given character code is a quote
5038 */
5039function isQuote(c) {
5040 return c === Chars$1.SingleQuote || c === Chars$1.DoubleQuote;
5041}
5042/**
5043 * Consumes quoted value, if possible
5044 * @return Returns `true` is value was consumed
5045 */
5046function consumeQuoted(scanner) {
5047 const start = scanner.pos;
5048 const quote = previous(scanner);
5049 if (isQuote(quote)) {
5050 while (!sol(scanner)) {
5051 if (previous(scanner) === quote && peek(scanner) !== Chars$1.Escape) {
5052 return true;
5053 }
5054 }
5055 }
5056 scanner.pos = start;
5057 return false;
5058}
5059
5060var Brackets;
5061(function (Brackets) {
5062 Brackets[Brackets["SquareL"] = 91] = "SquareL";
5063 Brackets[Brackets["SquareR"] = 93] = "SquareR";
5064 Brackets[Brackets["RoundL"] = 40] = "RoundL";
5065 Brackets[Brackets["RoundR"] = 41] = "RoundR";
5066 Brackets[Brackets["CurlyL"] = 123] = "CurlyL";
5067 Brackets[Brackets["CurlyR"] = 125] = "CurlyR";
5068})(Brackets || (Brackets = {}));
5069const bracePairs = {
5070 [Brackets.SquareL]: Brackets.SquareR,
5071 [Brackets.RoundL]: Brackets.RoundR,
5072 [Brackets.CurlyL]: Brackets.CurlyR,
5073};
5074
5075var Chars;
5076(function (Chars) {
5077 Chars[Chars["Tab"] = 9] = "Tab";
5078 Chars[Chars["Space"] = 32] = "Space";
5079 /** `-` character */
5080 Chars[Chars["Dash"] = 45] = "Dash";
5081 /** `/` character */
5082 Chars[Chars["Slash"] = 47] = "Slash";
5083 /** `:` character */
5084 Chars[Chars["Colon"] = 58] = "Colon";
5085 /** `=` character */
5086 Chars[Chars["Equals"] = 61] = "Equals";
5087 /** `<` character */
5088 Chars[Chars["AngleLeft"] = 60] = "AngleLeft";
5089 /** `>` character */
5090 Chars[Chars["AngleRight"] = 62] = "AngleRight";
5091})(Chars || (Chars = {}));
5092/**
5093 * Check if given reader’s current position points at the end of HTML tag
5094 */
5095function isHtml(scanner) {
5096 const start = scanner.pos;
5097 if (!consume(scanner, Chars.AngleRight)) {
5098 return false;
5099 }
5100 let ok = false;
5101 consume(scanner, Chars.Slash); // possibly self-closed element
5102 while (!sol(scanner)) {
5103 consumeWhile(scanner, isWhiteSpace);
5104 if (consumeIdent(scanner)) {
5105 // ate identifier: could be a tag name, boolean attribute or unquoted
5106 // attribute value
5107 if (consume(scanner, Chars.Slash)) {
5108 // either closing tag or invalid tag
5109 ok = consume(scanner, Chars.AngleLeft);
5110 break;
5111 }
5112 else if (consume(scanner, Chars.AngleLeft)) {
5113 // opening tag
5114 ok = true;
5115 break;
5116 }
5117 else if (consume(scanner, isWhiteSpace)) {
5118 // boolean attribute
5119 continue;
5120 }
5121 else if (consume(scanner, Chars.Equals)) {
5122 // simple unquoted value or invalid attribute
5123 if (consumeIdent(scanner)) {
5124 continue;
5125 }
5126 break;
5127 }
5128 else if (consumeAttributeWithUnquotedValue(scanner)) {
5129 // identifier was a part of unquoted value
5130 ok = true;
5131 break;
5132 }
5133 // invalid tag
5134 break;
5135 }
5136 if (consumeAttribute(scanner)) {
5137 continue;
5138 }
5139 break;
5140 }
5141 scanner.pos = start;
5142 return ok;
5143}
5144/**
5145 * Consumes HTML attribute from given string.
5146 * @return `true` if attribute was consumed.
5147 */
5148function consumeAttribute(scanner) {
5149 return consumeAttributeWithQuotedValue(scanner) || consumeAttributeWithUnquotedValue(scanner);
5150}
5151function consumeAttributeWithQuotedValue(scanner) {
5152 const start = scanner.pos;
5153 if (consumeQuoted(scanner) && consume(scanner, Chars.Equals) && consumeIdent(scanner)) {
5154 return true;
5155 }
5156 scanner.pos = start;
5157 return false;
5158}
5159function consumeAttributeWithUnquotedValue(scanner) {
5160 const start = scanner.pos;
5161 const stack = [];
5162 while (!sol(scanner)) {
5163 const ch = peek(scanner);
5164 if (isCloseBracket(ch)) {
5165 stack.push(ch);
5166 }
5167 else if (isOpenBracket(ch)) {
5168 if (stack.pop() !== bracePairs[ch]) {
5169 // Unexpected open bracket
5170 break;
5171 }
5172 }
5173 else if (!isUnquotedValue(ch)) {
5174 break;
5175 }
5176 scanner.pos--;
5177 }
5178 if (start !== scanner.pos && consume(scanner, Chars.Equals) && consumeIdent(scanner)) {
5179 return true;
5180 }
5181 scanner.pos = start;
5182 return false;
5183}
5184/**
5185 * Consumes HTML identifier from stream
5186 */
5187function consumeIdent(scanner) {
5188 return consumeWhile(scanner, isIdent);
5189}
5190/**
5191 * Check if given character code belongs to HTML identifier
5192 */
5193function isIdent(ch) {
5194 return ch === Chars.Colon || ch === Chars.Dash || isAlpha(ch) || isNumber(ch);
5195}
5196/**
5197 * Check if given character code is alpha code (letter though A to Z)
5198 */
5199function isAlpha(ch) {
5200 ch &= ~32; // quick hack to convert any char code to uppercase char code
5201 return ch >= 65 && ch <= 90; // A-Z
5202}
5203/**
5204 * Check if given code is a number
5205 */
5206function isNumber(ch) {
5207 return ch > 47 && ch < 58;
5208}
5209/**
5210 * Check if given code is a whitespace
5211 */
5212function isWhiteSpace(ch) {
5213 return ch === Chars.Space || ch === Chars.Tab;
5214}
5215/**
5216 * Check if given code may belong to unquoted attribute value
5217 */
5218function isUnquotedValue(ch) {
5219 return !isNaN(ch) && ch !== Chars.Equals && !isWhiteSpace(ch) && !isQuote(ch);
5220}
5221function isOpenBracket(ch) {
5222 return ch === Brackets.CurlyL || ch === Brackets.RoundL || ch === Brackets.SquareL;
5223}
5224function isCloseBracket(ch) {
5225 return ch === Brackets.CurlyR || ch === Brackets.RoundR || ch === Brackets.SquareR;
5226}
5227
5228const code = (ch) => ch.charCodeAt(0);
5229const specialChars = '#.*:$-_!@%^+>/'.split('').map(code);
5230const defaultOptions = {
5231 type: 'markup',
5232 lookAhead: true,
5233 prefix: ''
5234};
5235/**
5236 * Extracts Emmet abbreviation from given string.
5237 * The goal of this module is to extract abbreviation from current editor’s line,
5238 * e.g. like this: `<span>.foo[title=bar|]</span>` -> `.foo[title=bar]`, where
5239 * `|` is a current caret position.
5240 * @param line A text line where abbreviation should be expanded
5241 * @param pos Caret position in line. If not given, uses end of line
5242 * @param options Extracting options
5243 */
5244function extractAbbreviation(line, pos = line.length, options = {}) {
5245 // make sure `pos` is within line range
5246 const opt = Object.assign(Object.assign({}, defaultOptions), options);
5247 pos = Math.min(line.length, Math.max(0, pos == null ? line.length : pos));
5248 if (opt.lookAhead) {
5249 pos = offsetPastAutoClosed(line, pos, opt);
5250 }
5251 let ch;
5252 const start = getStartOffset(line, pos, opt.prefix || '');
5253 if (start === -1) {
5254 return void 0;
5255 }
5256 const scanner = backwardScanner(line, start);
5257 scanner.pos = pos;
5258 const stack = [];
5259 while (!sol(scanner)) {
5260 ch = peek(scanner);
5261 if (stack.includes(Brackets.CurlyR)) {
5262 if (ch === Brackets.CurlyR) {
5263 stack.push(ch);
5264 scanner.pos--;
5265 continue;
5266 }
5267 if (ch !== Brackets.CurlyL) {
5268 scanner.pos--;
5269 continue;
5270 }
5271 }
5272 if (isCloseBrace(ch, opt.type)) {
5273 stack.push(ch);
5274 }
5275 else if (isOpenBrace(ch, opt.type)) {
5276 if (stack.pop() !== bracePairs[ch]) {
5277 // unexpected brace
5278 break;
5279 }
5280 }
5281 else if (stack.includes(Brackets.SquareR) || stack.includes(Brackets.CurlyR)) {
5282 // respect all characters inside attribute sets or text nodes
5283 scanner.pos--;
5284 continue;
5285 }
5286 else if (isHtml(scanner) || !isAbbreviation(ch)) {
5287 break;
5288 }
5289 scanner.pos--;
5290 }
5291 if (!stack.length && scanner.pos !== pos) {
5292 // Found something, remove some invalid symbols from the
5293 // beginning and return abbreviation
5294 const abbreviation = line.slice(scanner.pos, pos).replace(/^[*+>^]+/, '');
5295 return {
5296 abbreviation,
5297 location: pos - abbreviation.length,
5298 start: options.prefix
5299 ? start - options.prefix.length
5300 : pos - abbreviation.length,
5301 end: pos
5302 };
5303 }
5304}
5305/**
5306 * Returns new `line` index which is right after characters beyound `pos` that
5307 * editor will likely automatically close, e.g. }, ], and quotes
5308 */
5309function offsetPastAutoClosed(line, pos, options) {
5310 // closing quote is allowed only as a next character
5311 if (isQuote(line.charCodeAt(pos))) {
5312 pos++;
5313 }
5314 // offset pointer until non-autoclosed character is found
5315 while (isCloseBrace(line.charCodeAt(pos), options.type)) {
5316 pos++;
5317 }
5318 return pos;
5319}
5320/**
5321 * Returns start offset (left limit) in `line` where we should stop looking for
5322 * abbreviation: it’s nearest to `pos` location of `prefix` token
5323 */
5324function getStartOffset(line, pos, prefix) {
5325 if (!prefix) {
5326 return 0;
5327 }
5328 const scanner = backwardScanner(line);
5329 const compiledPrefix = prefix.split('').map(code);
5330 scanner.pos = pos;
5331 let result;
5332 while (!sol(scanner)) {
5333 if (consumePair(scanner, Brackets.SquareR, Brackets.SquareL) || consumePair(scanner, Brackets.CurlyR, Brackets.CurlyL)) {
5334 continue;
5335 }
5336 result = scanner.pos;
5337 if (consumeArray(scanner, compiledPrefix)) {
5338 return result;
5339 }
5340 scanner.pos--;
5341 }
5342 return -1;
5343}
5344/**
5345 * Consumes full character pair, if possible
5346 */
5347function consumePair(scanner, close, open) {
5348 const start = scanner.pos;
5349 if (consume(scanner, close)) {
5350 while (!sol(scanner)) {
5351 if (consume(scanner, open)) {
5352 return true;
5353 }
5354 scanner.pos--;
5355 }
5356 }
5357 scanner.pos = start;
5358 return false;
5359}
5360/**
5361 * Consumes all character codes from given array, right-to-left, if possible
5362 */
5363function consumeArray(scanner, arr) {
5364 const start = scanner.pos;
5365 let consumed = false;
5366 for (let i = arr.length - 1; i >= 0 && !sol(scanner); i--) {
5367 if (!consume(scanner, arr[i])) {
5368 break;
5369 }
5370 consumed = i === 0;
5371 }
5372 if (!consumed) {
5373 scanner.pos = start;
5374 }
5375 return consumed;
5376}
5377function isAbbreviation(ch) {
5378 return (ch > 64 && ch < 91) // uppercase letter
5379 || (ch > 96 && ch < 123) // lowercase letter
5380 || (ch > 47 && ch < 58) // number
5381 || specialChars.includes(ch); // special character
5382}
5383function isOpenBrace(ch, syntax) {
5384 return ch === Brackets.RoundL || (syntax === 'markup' && (ch === Brackets.SquareL || ch === Brackets.CurlyL));
5385}
5386function isCloseBrace(ch, syntax) {
5387 return ch === Brackets.RoundR || (syntax === 'markup' && (ch === Brackets.SquareR || ch === Brackets.CurlyR));
5388}
5389
5390function expandAbbreviation(abbr, config) {
5391 const resolvedConfig = resolveConfig(config);
5392 return resolvedConfig.type === 'stylesheet'
5393 ? stylesheet(abbr, resolvedConfig)
5394 : markup(abbr, resolvedConfig);
5395}
5396/**
5397 * Expands given *markup* abbreviation (e.g. regular Emmet abbreviation that
5398 * produces structured output like HTML) and outputs it according to options
5399 * provided in config
5400 */
5401function markup(abbr, config) {
5402 return stringify(parse$1(abbr, config), config);
5403}
5404/**
5405 * Expands given *stylesheet* abbreviation (a special Emmet abbreviation designed for
5406 * stylesheet languages like CSS, SASS etc.) and outputs it according to options
5407 * provided in config
5408 */
5409function stylesheet(abbr, config) {
5410 return css(parse(abbr, config), config);
5411}
5412
5413export { 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 };
5414//# sourceMappingURL=emmet.es.js.map