{{ first }}
+#^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue string.quoted.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.declaration.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.constant.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.operator.rest.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.declaration.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.constant.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.operator.rest.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.declaration.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.constant.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.declaration.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.constant.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.parameters.begin.ts +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue keyword.operator.relational.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue constant.numeric.decimal.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue keyword.operator.logical.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue +# ^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue variable.other.object.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue punctuation.accessor.ts +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue support.variable.property.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.parameters.end.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue punctuation.definition.interpolation.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue source.ts.embedded.html.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue source.ts.embedded.html.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue source.ts.embedded.html.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue punctuation.definition.interpolation.end.html.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.end.html +>{{ waiting }}
+#^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue string.quoted.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.operator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue string.quoted.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.declaration.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.constant.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue punctuation.definition.interpolation.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue source.ts.embedded.html.vue +# ^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue source.ts.embedded.html.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue source.ts.embedded.html.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end expression.embedded.vue punctuation.definition.interpolation.end.html.vue +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.end.html +>error
+#^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.end.html +>known
+#^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue constant.language.wildcard.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.operator.rest.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.end.html +>tuple
+#^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue constant.numeric.ts +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue string.quoted.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.operator.rest.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.end.html +>empty
+#^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue constant.language.wildcard.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag.structure.p.end.html punctuation.definition.tag.end.html +> +#^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end entity.name.tag.template.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start punctuation.definition.tag.end.html.vue +>after pattern
+#^^ text.html.vue text.html.derivative +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue punctuation.attribute-shorthand.bind.html.vue +# ^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue entity.other.attribute-name.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue punctuation.definition.string.begin.html.vue +# ^^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue source.ts.embedded.html.vue variable.other.object.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue source.ts.embedded.html.vue punctuation.accessor.ts +# ^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue source.ts.embedded.html.vue variable.other.property.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^^^^^^^^^^^^ text.html.vue text.html.derivative +# ^^ text.html.vue text.html.derivative meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.end.html punctuation.definition.tag.end.html +> +#^^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue entity.name.tag.template.html.vue +# ^ text.html.vue punctuation.definition.tag.end.html.vue +> +#^ text.html.vue" +`; + +exports[`embedded grammar > root-patterned-template.vue 1`] = ` +"> +#^^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^ text.html.vue entity.name.tag.script.html.vue +# ^ text.html.vue punctuation.definition.tag.end.html.vue +> +#^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue entity.name.tag.template.html.vue +# ^ text.html.vue meta.tag-stuff +# ^^^^^^^ text.html.vue meta.tag-stuff meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue meta.tag-stuff meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue meta.tag-stuff meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^^^^^^ text.html.vue meta.tag-stuff meta.attribute.directive.control.vue source.ts.embedded.html.vue variable.other.readwrite.ts +# ^ text.html.vue meta.tag-stuff meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue meta.tag-stuff +# ^^^^ text.html.vue meta.tag-stuff meta.attribute.lang.html entity.other.attribute-name.html +# ^ text.html.vue meta.tag-stuff meta.attribute.lang.html punctuation.separator.key-value.html +# ^ text.html.vue meta.tag-stuff meta.attribute.lang.html string.quoted.double.html punctuation.definition.string.begin.html +# ^^^^ text.html.vue meta.tag-stuff meta.attribute.lang.html string.quoted.double.html +# ^ text.html.vue meta.tag-stuff meta.attribute.lang.html string.quoted.double.html punctuation.definition.string.end.html +# ^ text.html.vue meta.tag-stuff punctuation.definition.tag.end.html.vue +>{{ data }}
+#^^ text.html.vue text.html.derivative +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue string.quoted.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.declaration.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.constant.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.parameters.begin.ts +# ^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue variable.other.object.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue punctuation.accessor.ts +# ^^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue source.ts.embedded.html.vue support.variable.property.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.parameters.end.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^ text.html.vue text.html.derivative expression.embedded.vue punctuation.definition.interpolation.begin.html.vue +# ^ text.html.vue text.html.derivative expression.embedded.vue source.ts.embedded.html.vue +# ^^^^ text.html.vue text.html.derivative expression.embedded.vue source.ts.embedded.html.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative expression.embedded.vue source.ts.embedded.html.vue +# ^^ text.html.vue text.html.derivative expression.embedded.vue punctuation.definition.interpolation.end.html.vue +# ^^ text.html.vue text.html.derivative meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.end.html punctuation.definition.tag.end.html +>error
+#^^ text.html.vue text.html.derivative +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html +# ^^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue keyword.control.conditional.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.separator.key-value.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.begin.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue variable.other.readwrite.ts +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.separator.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^^^^^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue string.quoted.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue meta.pattern.vue punctuation.definition.pattern.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html meta.attribute.directive.control.vue punctuation.definition.string.end.html.vue +# ^ text.html.vue text.html.derivative meta.tag.structure.p.start.html punctuation.definition.tag.end.html +# ^^^^^ text.html.vue text.html.derivative +# ^^ text.html.vue text.html.derivative meta.tag.structure.p.end.html punctuation.definition.tag.begin.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.end.html entity.name.tag.html +# ^ text.html.vue text.html.derivative meta.tag.structure.p.end.html punctuation.definition.tag.end.html +> +#^^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue entity.name.tag.template.html.vue +# ^ text.html.vue punctuation.definition.tag.end.html.vue +> +#^ text.html.vue" +`; + exports[`embedded grammar > single-line-setup.vue 1`] = ` "> #^ text.html.vue punctuation.definition.tag.begin.html.vue @@ -1290,6 +1702,58 @@ exports[`grammar > namespaced.vue 1`] = ` #^ text.html.vue" `; +exports[`grammar > patterned-templates.vue 1`] = ` +"> +#^^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^ text.html.vue entity.name.tag.script.html.vue +# ^ text.html.vue punctuation.definition.tag.end.html.vue +> +#^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue entity.name.tag.template.html.vue +# ^ text.html.vue meta.tag-stuff punctuation.definition.tag.end.html.vue +> +#^^ text.html.vue text.html.derivative +# ^ text.html.vue text.html.derivative meta.template-tag.start punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start entity.name.tag.template.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag-stuff +# ^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag-stuff meta.attribute.directive.vue entity.other.attribute-name.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag-stuff meta.attribute.directive.vue +# ^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag-stuff +# ^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end meta.tag-stuff punctuation.definition.tag.end.html.vue +>{{ first }}
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +>{{ waiting }}
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +>error
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +>known
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +>tuple
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +>empty
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +> +#^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end +# ^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue text.html.derivative meta.template-tag.start meta.template-tag.end entity.name.tag.template.html.vue +# ^ text.html.vue text.html.derivative meta.template-tag.start punctuation.definition.tag.end.html.vue +>after pattern
+#^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ text.html.vue text.html.derivative +> +#^^ text.html.vue punctuation.definition.tag.begin.html.vue +# ^^^^^^^^ text.html.vue entity.name.tag.template.html.vue +# ^ text.html.vue punctuation.definition.tag.end.html.vue +> +#^ text.html.vue" +`; + exports[`grammar > script-tag-in-script.vue 1`] = ` ">' #^ text.html.vue punctuation.definition.tag.begin.html.vue diff --git a/extensions/vscode/tests/embeddedGrammarFixtures/patterned-templates.vue b/extensions/vscode/tests/embeddedGrammarFixtures/patterned-templates.vue new file mode 100644 index 0000000000..f30e0366cc --- /dev/null +++ b/extensions/vscode/tests/embeddedGrammarFixtures/patterned-templates.vue @@ -0,0 +1,14 @@ + + + +{{ first }}
+{{ waiting }}
+error
+known
+tuple
+empty
+ +after pattern
+ diff --git a/extensions/vscode/tests/embeddedGrammarFixtures/root-patterned-template.vue b/extensions/vscode/tests/embeddedGrammarFixtures/root-patterned-template.vue new file mode 100644 index 0000000000..0f28f43606 --- /dev/null +++ b/extensions/vscode/tests/embeddedGrammarFixtures/root-patterned-template.vue @@ -0,0 +1,7 @@ + + +{{ data }}
+error
+ diff --git a/extensions/vscode/tests/embeddedGrammars/_lock.json b/extensions/vscode/tests/embeddedGrammars/_lock.json index 0ff3712a74..93b1eeea2c 100644 --- a/extensions/vscode/tests/embeddedGrammars/_lock.json +++ b/extensions/vscode/tests/embeddedGrammars/_lock.json @@ -1,27 +1,27 @@ [ { "file": "css.tmLanguage.json", - "url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/css/syntaxes/css.tmLanguage.json", + "url": "https://raw.githubusercontent.com/microsoft/vscode/283e479dda1c4a4a0fed0fcf110bf48a8ee43aae/extensions/css/syntaxes/css.tmLanguage.json", "checksum": "sha256-9678e7410bd051eafc1ad51d3c8562885a17e7d61602d76cc5030d06520724cd" }, { "file": "html.tmLanguage.json", - "url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/html/syntaxes/html.tmLanguage.json", + "url": "https://raw.githubusercontent.com/microsoft/vscode/283e479dda1c4a4a0fed0fcf110bf48a8ee43aae/extensions/html/syntaxes/html.tmLanguage.json", "checksum": "sha256-80dedf4fb27e88889ac8fb72763954a6d2660502c686f4415208d8c8d00352cd" }, { "file": "javascript.tmLanguage.json", - "url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/javascript/syntaxes/JavaScript.tmLanguage.json", + "url": "https://raw.githubusercontent.com/microsoft/vscode/283e479dda1c4a4a0fed0fcf110bf48a8ee43aae/extensions/javascript/syntaxes/JavaScript.tmLanguage.json", "checksum": "sha256-db6f17f15bc4f5e860a3b8fa6055a69720a53df845c8d5121cdc4f128c16291f" }, { "file": "scss.tmLanguage.json", - "url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/scss/syntaxes/scss.tmLanguage.json", + "url": "https://raw.githubusercontent.com/microsoft/vscode/283e479dda1c4a4a0fed0fcf110bf48a8ee43aae/extensions/scss/syntaxes/scss.tmLanguage.json", "checksum": "sha256-8f2824a80a7c6fd558fc538ec52d0a7a42a4d7ecb7ddf20d79f0d1f00fa6602b" }, { "file": "typescript.tmLanguage.json", - "url": "https://raw.githubusercontent.com/microsoft/vscode/main/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json", + "url": "https://raw.githubusercontent.com/microsoft/vscode/283e479dda1c4a4a0fed0fcf110bf48a8ee43aae/extensions/typescript-basics/syntaxes/TypeScript.tmLanguage.json", "checksum": "sha256-4e92e0d7de560217d6c8d3236d85e6e17a5d77825b15729a230c761743122661" } ] diff --git a/extensions/vscode/tests/grammar.spec.ts b/extensions/vscode/tests/grammar.spec.ts index 70608334ba..d840aa5be2 100644 --- a/extensions/vscode/tests/grammar.spec.ts +++ b/extensions/vscode/tests/grammar.spec.ts @@ -40,6 +40,10 @@ describe('embedded grammar', async () => { it(fixture, async () => { const result = await snapshot(`tests/embeddedGrammarFixtures/${fixture}`); + if (fixture === 'patterned-templates.vue') { + expect(result).toContain('constant.language.wildcard.vue'); + expect(result).toContain('keyword.declaration.pattern.vue'); + } expect(result).toMatchSnapshot(); }); } diff --git a/packages/language-core/index.ts b/packages/language-core/index.ts index 96be55b1ea..7006eda595 100644 --- a/packages/language-core/index.ts +++ b/packages/language-core/index.ts @@ -5,6 +5,7 @@ export * from './lib/parsers/scriptRanges'; export * from './lib/parsers/scriptSetupRanges'; export * from './lib/plugins'; export * from './lib/template/compile'; +export * from './lib/template/patterns/warnings'; export * from './lib/types'; export * from './lib/utils/collectBindings'; export * from './lib/utils/forEachTemplateNode'; diff --git a/packages/language-core/lib/codegen/names.ts b/packages/language-core/lib/codegen/names.ts index 0c67d10d7e..4c6878df64 100644 --- a/packages/language-core/lib/codegen/names.ts +++ b/packages/language-core/lib/codegen/names.ts @@ -20,6 +20,10 @@ const raw = { slots: '', src: '', + MatchPattern: '', + SubtractPattern: '', + CheckMatchExhaustive: '', + Emit: '', EmitProps: '', GlobalComponents: '', diff --git a/packages/language-core/lib/codegen/script/index.ts b/packages/language-core/lib/codegen/script/index.ts index 215f6d46cd..16b45dbea9 100644 --- a/packages/language-core/lib/codegen/script/index.ts +++ b/packages/language-core/lib/codegen/script/index.ts @@ -38,7 +38,7 @@ function* generateWorker( ): Generator {
const { script, scriptRanges, scriptSetup, scriptSetupRanges, vueCompilerOptions, fileName } = options;
- yield* generateGlobalTypesReference(vueCompilerOptions, fileName);
+ yield* generateGlobalTypesReference(vueCompilerOptions, fileName, options.templateAndStyleTypes);
//
+
+
+{{ data }}
+{{ error.message }}
+`;
+
+async function request(command: string, text: string, offset?: number, extra = {}) {
+ const server = await getLanguageServer();
+ const document = await server.open(uri, 'vue', text);
+ const position = offset === undefined ? undefined : document.positionAt(offset);
+ const result = await server.tsserver.message({
+ seq: server.nextSeq(),
+ type: 'request',
+ command,
+ arguments: {
+ file: URI.parse(uri).fsPath,
+ ...(position ? { line: position.line + 1, offset: position.character + 1 } : {}),
+ ...extra,
+ },
+ });
+ expect(result.success).toBe(true);
+ return { body: result.body, document };
+}
+
+test('pattern bindings have precise hover and guard / element completion', async () => {
+ const offset = source.indexOf('article.length');
+ const { body } = await request('quickinfo', source, offset);
+ expect(body.displayString).toBe('const article: string');
+ const completions = await request('completionInfo', source, offset + 'article.'.length);
+ expect(completions.body.entries.map((entry: { name: string }) => entry.name)).toContain('toUpperCase');
+ expect(completions.body.entries.map((entry: { name: string }) => entry.name)).not.toContain('toFixed');
+});
+
+test('definition and rename connect declaration, guard, props, events and children', async () => {
+ const { body, document } = await request('definition', source, source.lastIndexOf('article'));
+ const declaration = document.positionAt(source.indexOf('const article') + 6);
+ expect(body).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ start: { line: declaration.line + 1, offset: declaration.character + 1 } }),
+ ]),
+ );
+ const rename = await request('rename', source, source.lastIndexOf('article'), {
+ findInStrings: false,
+ findInComments: false,
+ });
+ expect(rename.body.info.canRename).toBe(true);
+ expect(rename.body.locs.flatMap((loc: { locs: unknown[] }) => loc.locs)).toHaveLength(5);
+});
+
+test('editor diagnostics enforce exhaustive coverage by default and refresh after edits', async () => {
+ const missing = source.replace(`{{ error.message }}
`, '');
+ const diagnostics = await request('semanticDiagnosticsSync', missing);
+ expect(diagnostics.body).toEqual(
+ expect.arrayContaining([
+ expect.objectContaining({ code: 2322, text: expect.stringContaining('Non-exhaustive v-match') }),
+ ]),
+ );
+ await (await getLanguageServer()).close(uri);
+ const complete = await request('semanticDiagnosticsSync', source);
+ expect(complete.body).toEqual([]);
+});
+
+test('type-aware unreachable arms warn without becoming vue-tsc errors', async () => {
+ const text = `
+`;
+ const server = await getLanguageServer();
+ await server.open(uri, 'vue', text);
+ const result = await server.tsserver.message({
+ seq: server.nextSeq(),
+ type: 'request',
+ command: '_vue:getMatchWarnings',
+ arguments: [URI.parse(uri).fsPath],
+ });
+ expect(result.success).toBe(true);
+ expect(result.body).toHaveLength(2);
+ expect(result.body.map((warning: { message: string }) => warning.message)).toEqual([
+ 'Unreachable v-when: this pattern cannot match any remaining value.',
+ 'Unreachable v-when: this pattern cannot match any remaining value.',
+ ]);
+});
+
+test('definition and rename distinguish shadowed bindings', async () => {
+ const text = `
+
+{{ value.value }}
+
+`;
+ const armDeclaration = text.indexOf('{ const value') + '{ const '.length;
+ const armReference = text.indexOf('value.toUpperCase');
+ const { body, document } = await request('definition', text, armReference);
+ const position = document.positionAt(armDeclaration);
+ expect(body).toEqual([
+ expect.objectContaining({ start: { line: position.line + 1, offset: position.character + 1 } }),
+ ]);
+ const rename = await request('rename', text, armReference, { findInStrings: false, findInComments: false });
+ const offsets = rename.body.locs.flatMap((file: { locs: { start: { line: number; offset: number } }[] }) =>
+ file.locs.map(loc => document.offsetAt({ line: loc.start.line - 1, character: loc.start.offset - 1 }))
+ );
+ expect(offsets.sort((a: number, b: number) => a - b)).toEqual([
+ armDeclaration,
+ text.indexOf('value.length'),
+ text.indexOf(':title="value"') + ':title="'.length,
+ armReference,
+ text.indexOf('in value') + 'in '.length,
+ text.indexOf('{{ value') + '{{ '.length,
+ ]);
+ const inner = await request('quickinfo', text, text.indexOf('value.toLowerCase'));
+ expect(inner.body.displayString).toBe('const value: string');
+ const diagnostics = await request('semanticDiagnosticsSync', text);
+ expect(diagnostics.body).toEqual([]);
+});
+
+test('editor rejects repeated names within one pattern', async () => {
+ const text = `
+`;
+ const { body } = await request('semanticDiagnosticsSync', text);
+ expect(body).toEqual([
+ expect.objectContaining({ text: expect.stringContaining('Duplicate pattern binding value') }),
+ ]);
+});
+
+test.each(['', ' lang="html"'])('SFC root header supports navigation and completion (%s)', async lang => {
+ const text = source.replace('',
+ '',
+ );
+ const offset = text.indexOf('v-match="result"') + 'v-match="'.length;
+ const { body } = await request('quickinfo', text, offset);
+ expect(body.displayString).toContain('result:');
+ const definition = await request('definition', text, offset);
+ const position = definition.document.positionAt(text.indexOf('result:'));
+ expect(definition.body).toEqual([
+ expect.objectContaining({ start: { line: position.line + 1, offset: position.character + 1 } }),
+ ]);
+ const rename = await request('rename', text, offset, { findInStrings: false, findInComments: false });
+ expect(rename.body.info.canRename).toBe(true);
+ expect(rename.body.locs.flatMap((file: { locs: unknown[] }) => file.locs)).toHaveLength(2);
+ const completions = await request('completionInfo', text, offset + 3);
+ expect(completions.body.entries.map((entry: { name: string }) => entry.name)).toContain('result');
+ const binding = await request('quickinfo', text, text.indexOf('article.length'));
+ expect(binding.body.displayString).toBe('const article: string');
+ expect((await request('semanticDiagnosticsSync', text)).body).toEqual([]);
+});
+
+test('header-only edits refresh coverage and diagnostic locations', async () => {
+ const text = `
+`;
+ expect((await request('semanticDiagnosticsSync', text)).body).toEqual([]);
+ for (
+ const edited of [text.replace('v-match="a"', 'v-match="b"'), text.replace('v-match="a"', 'lang="html" v-match="b"')]
+ ) {
+ const { body, document } = await request('semanticDiagnosticsSync', edited);
+ const position = document.positionAt(edited.indexOf('v-match="b"') + 'v-match="'.length);
+ expect(body).toEqual([
+ expect.objectContaining({
+ text: expect.stringContaining('Non-exhaustive v-match'),
+ start: { line: position.line + 1, offset: position.character + 1 },
+ }),
+ ]);
+ }
+ expect((await request('semanticDiagnosticsSync', text)).body).toEqual([]);
+});
diff --git a/packages/language-service/index.ts b/packages/language-service/index.ts
index 2d0e6deb46..2fd14311b6 100644
--- a/packages/language-service/index.ts
+++ b/packages/language-service/index.ts
@@ -22,6 +22,7 @@ import { create as createVueDocumentHighlightsPlugin } from './lib/plugins/vue-d
import { create as createVueExtractFilePlugin } from './lib/plugins/vue-extract-file';
import { create as createVueFormatPerBlockPlugin } from './lib/plugins/vue-format-per-block';
import { create as createVueInlayHintsPlugin } from './lib/plugins/vue-inlayhints';
+import { create as createVueMatchWarningsPlugin } from './lib/plugins/vue-match-warnings';
import { create as createVueMissingPropsHintsPlugin } from './lib/plugins/vue-missing-props-hints';
import { create as createVueScopedClassLinksPlugin } from './lib/plugins/vue-scoped-class-links';
import { create as createVueSfcPlugin } from './lib/plugins/vue-sfc';
@@ -75,5 +76,6 @@ export function createVueLanguageServicePlugins(
createVueTemplatePlugin(ts, 'html', client),
createVueTemplatePlugin(ts, 'jade', client),
createVueTwoslashQueriesPlugin(client),
+ createVueMatchWarningsPlugin(client),
];
}
diff --git a/packages/language-service/lib/data.ts b/packages/language-service/lib/data.ts
index ab6821d133..ca9a6220f2 100644
--- a/packages/language-service/lib/data.ts
+++ b/packages/language-service/lib/data.ts
@@ -40,7 +40,30 @@ export function loadTemplateData(lang: string) {
resolveReferences(data);
- return data;
+ return {
+ ...data,
+ globalAttributes: [
+ ...data.globalAttributes ?? [],
+ {
+ name: 'v-match',
+ description:
+ 'RFC #823 reference implementation: evaluate one subject and render the first matching v-when arm. Template type checking requires exhaustive coverage.',
+ references: [{
+ name: 'RFC #823 (Draft reference implementation)',
+ url: 'https://github.com/vuejs/rfcs/pull/823',
+ }],
+ },
+ {
+ name: 'v-when',
+ description:
+ 'RFC #823 reference implementation: a pattern with optional const/rest/as bindings and an if (guard). Must be a direct v-match child. No arguments, modifiers, or shorthand.',
+ references: [{
+ name: 'RFC #823 (Draft reference implementation)',
+ url: 'https://github.com/vuejs/rfcs/pull/823',
+ }],
+ },
+ ],
+ };
}
export function loadLanguageBlocks(lang: string): html.HTMLDataV1 {
diff --git a/packages/language-service/lib/plugins/vue-match-warnings.ts b/packages/language-service/lib/plugins/vue-match-warnings.ts
new file mode 100644
index 0000000000..cef028e83f
--- /dev/null
+++ b/packages/language-service/lib/plugins/vue-match-warnings.ts
@@ -0,0 +1,28 @@
+import type { LanguageServicePlugin } from '@volar/language-service';
+import type { Requests } from '@vue/typescript-plugin/lib/requests';
+import { resolveEmbeddedCode } from '../utils';
+
+export function create({ getMatchWarnings }: Requests): LanguageServicePlugin {
+ return {
+ name: 'vue-match-warnings',
+ capabilities: { diagnosticProvider: { interFileDependencies: true, workspaceDiagnostics: false } },
+ create(context) {
+ return {
+ async provideDiagnostics(document) {
+ const info = resolveEmbeddedCode(context, document.uri);
+ if (info?.code.id !== 'template' || !document.getText().includes('v-match')) {
+ return;
+ }
+ const warnings = await getMatchWarnings(info.root.fileName);
+ return warnings?.map(warning => ({
+ range: { start: document.positionAt(warning.start), end: document.positionAt(warning.end) },
+ severity: 2,
+ code: 'V_MATCH_UNREACHABLE',
+ source: 'vue',
+ message: warning.message,
+ }));
+ },
+ };
+ },
+ };
+}
diff --git a/packages/tsc/index.ts b/packages/tsc/index.ts
index 703a120d49..fc3ee3f084 100644
--- a/packages/tsc/index.ts
+++ b/packages/tsc/index.ts
@@ -1,5 +1,6 @@
import { runTsc } from '@volar/typescript/lib/quickstart/runTsc';
import * as core from '@vue/language-core';
+import { withMatchWarnings } from './lib/matchWarnings';
const windowsPathReg = /\\/g;
@@ -38,11 +39,11 @@ export function run(tscPath = require.resolve('typescript/lib/tsc')) {
);
try {
- return main();
+ return withMatchWarnings(main);
}
catch (err) {
if (err === extensionsChangedException) {
- return main();
+ return withMatchWarnings(main);
}
else {
throw err;
diff --git a/packages/tsc/lib/matchWarnings.ts b/packages/tsc/lib/matchWarnings.ts
new file mode 100644
index 0000000000..99d8447732
--- /dev/null
+++ b/packages/tsc/lib/matchWarnings.ts
@@ -0,0 +1,69 @@
+import * as core from '@vue/language-core';
+import type * as ts from 'typescript';
+
+/** runTsc currently has no after-program hook. Restore its adapter on exit. */
+export function withMatchWarnings(run: () => T): T {
+ const proxy = require(
+ '@volar/typescript/lib/node/proxyCreateProgram',
+ ) as typeof import('@volar/typescript/lib/node/proxyCreateProgram');
+ const originalProxy = proxy.proxyCreateProgram;
+ proxy.proxyCreateProgram = (ts, createProgram, plugins) =>
+ originalProxy(
+ ts,
+ new Proxy(createProgram, {
+ apply(target, thisArg, args) {
+ const options = args[0] as ts.CreateProgramOptions;
+ const program = Reflect.apply(target, thisArg, args) as ts.Program & {
+ getBindAndCheckDiagnostics: ts.Program['getSemanticDiagnostics'];
+ };
+ const warned = new WeakSet();
+ function reportWarnings(file?: ts.SourceFile) {
+ for (const sourceFile of file ? [file] : program.getSourceFiles()) {
+ if (warned.has(sourceFile)) {
+ continue;
+ }
+ warned.add(sourceFile);
+ const source = options.host?.readFile(sourceFile.fileName);
+ if (!source?.includes('v-match')) {
+ continue;
+ }
+ const warnings = core.getMatchWarnings(ts, program, sourceFile.fileName);
+ if (!warnings.length) {
+ continue;
+ }
+ const template = core.parseRawIR(source, {}).rawIr.templates[0];
+ if (!template) {
+ continue;
+ }
+ const originalFile = ts.createSourceFile(sourceFile.fileName, source, sourceFile.languageVersion);
+ for (const warning of warnings) {
+ const position = ts.getLineAndCharacterOfPosition(originalFile, template.innerStart + warning.start);
+ ts.sys.write(
+ `${sourceFile.fileName}(${position.line + 1},${
+ position.character + 1
+ }): warning V_MATCH_UNREACHABLE: ${warning.message}${ts.sys.newLine}`,
+ );
+ }
+ }
+ }
+ // Reuse the current checker; do not create a second TypeScript Program.
+ // Warnings are separate from TS errors so --noEmit and --build stay green.
+ for (const name of ['getSemanticDiagnostics', 'getBindAndCheckDiagnostics'] as const) {
+ const original = program[name].bind(program);
+ program[name] = (file, cancellationToken) => {
+ reportWarnings(file);
+ return original(file, cancellationToken);
+ };
+ }
+ return program;
+ },
+ }),
+ plugins,
+ );
+ try {
+ return run();
+ }
+ finally {
+ proxy.proxyCreateProgram = originalProxy;
+ }
+}
diff --git a/packages/tsc/tests/patternedTemplates.spec.ts b/packages/tsc/tests/patternedTemplates.spec.ts
new file mode 100644
index 0000000000..0b8228571f
--- /dev/null
+++ b/packages/tsc/tests/patternedTemplates.spec.ts
@@ -0,0 +1,444 @@
+import { proxyCreateProgram } from '@volar/typescript';
+import * as core from '@vue/language-core';
+import * as path from 'node:path';
+import * as ts from 'typescript';
+import { describe, expect, test } from 'vitest';
+
+const cases: [string, string, string[], boolean][] = [
+ ['literal union', "'a' | 'b'", ["'a'", "'b'"], true],
+ ['missing literal', "'a' | 'b' | 'c'", ["'a'", "'b'"], false],
+ ['boolean', 'boolean', ['true', 'false'], true],
+ ['missing false', 'boolean', ['true'], false],
+ ['nullable', "'a' | null | undefined", ["'a'", 'null', 'undefined'], true],
+ ['undefined is distinct', "'a' | null | undefined", ["'a'", 'null'], false],
+ ['open string', 'string', ["'a'", "'b'"], false],
+ ['open number', 'number', ['1', '2'], false],
+ ['open string needs catch-all', 'string', ['{}'], false],
+ ['open number needs catch-all', 'number', ['{}'], false],
+ ['any requires fallback', 'any', ['{}', '[...]', 'true', 'false', 'null', 'undefined'], false],
+ ['unknown requires fallback', 'unknown', ['{}', '[...]', 'null', 'undefined'], false],
+ ['unknown fallback', 'unknown', ['_'], true],
+ ['any binding fallback', 'any', ['const value'], true],
+ ['never', 'never', [], true],
+ ['or union', "'a' | 'b' | 'c'", ["('a' | 'b') as ab", "'c'"], true],
+ ['guard does not cover', "'a' | 'b'", ["'a' if (true)", "'b'"], false],
+ ['complementary guards', 'boolean', ['_ if (subject)', '_ if (!subject)'], false],
+ ['guarded wildcard', 'unknown', ['_ if (true)'], false],
+ ['unguarded after guard', 'boolean', ['true if (true)', 'true', 'false'], true],
+ ['tags', "{ kind: 'ok'; data: string } | { kind: 'error'; error: Error }", [
+ "{ kind: 'ok', const data }",
+ "{ kind: 'error', const error }",
+ ], true],
+ ['partial tag', "{ kind: 'ok'; data: string | null } | { kind: 'error' }", [
+ "{ kind: 'ok', data: null }",
+ "{ kind: 'error' }",
+ ], false],
+ ['nested object product', "{ x: 'a' | 'b'; nested: { y: true | false } }", [
+ "{ x: 'a', nested: { y: true } }",
+ "{ x: 'a', nested: { y: false } }",
+ "{ x: 'b' }",
+ ], true],
+ ['missing object product', "{ x: 'a' | 'b'; nested: { y: true | false } }", [
+ "{ x: 'a', nested: { y: true } }",
+ "{ x: 'b' }",
+ ], false],
+ ['optional absent', "{ x?: 'a' }", ["{ x: 'a' }", '{ x: undefined }'], false],
+ ['optional present wildcard', "{ x?: 'a' }", ['{ x: _ }'], false],
+ ['optional empty object', "{ x?: 'a' }", ['{}'], true],
+ ['open objects', '{ x: 1; y: 2 }', ['{ x: 1 }'], true],
+ ['object rest does not constrain', '{ x: 1; y: number }', ['{ x: 1, ...const rest }'], true],
+ ['tuple product', "['left' | 'right', 'top' | 'bottom']", [
+ "['left', 'top']",
+ "['left', 'bottom']",
+ "['right', 'top']",
+ "['right', 'bottom']",
+ ], true],
+ ['missing tuple product', "['left' | 'right', 'top' | 'bottom']", [
+ "['left', 'top']",
+ "['left', 'bottom']",
+ "['right', 'top']",
+ ], false],
+ ['tuple with rest', '[string, number, boolean]', ['[const first, ...const tail]'], true],
+ ['array partition', 'string[]', ['[]', '[const first, ...const tail]'], true],
+ ['array missing long', 'string[]', ['[]', '[const first]'], false],
+ ['all arrays', 'readonly string[]', ['[...const items]'], true],
+ ['nullable array', 'string[] | null', ['[...]'], false],
+ ['array boolean head', 'boolean[]', ['[]', '[true, ...]', '[false, ...]'], true],
+ ['array missing head', 'boolean[]', ['[]', '[true, ...]'], false],
+ ['tuple optional', '[string?]', ['[]', '[const value]'], true],
+ ['tuple optional missing', '[string?]', ['[]'], false],
+];
+
+const workspace = path.resolve(__dirname, '../../../test-workspace/patterned-templates').replace(/\\/g, '/');
+const sources = new Map();
+for (const [index, [, type, arms]] of cases.entries()) {
+ sources.set(
+ `${workspace}/${index}.vue`,
+ `\n\n\n${
+ arms.map(pattern => ``).join('\n')
+ }\n\n`,
+ );
+}
+sources.set(
+ `${workspace}/narrowing.vue`,
+ `
+
+
+{{ subject.data.toUpperCase() }} {{ success.data.toUpperCase() }}
+
+{{ subject.error }}
+
+{{ data.toFixed() }}
+
+{{ data = 'mutated' }}
+
+{{ subject.error.message }} {{ error.message }}
+
+
+{{ data }}
+`,
+);
+sources.set(
+ `${workspace}/values.vue`,
+ `
+
+
+
+
+`,
+);
+
+sources.set(
+ `${workspace}/rest.vue`,
+ `
+
+{{ exactType(tupleWhole, {} as readonly [string, number, boolean]) }}
+{{ exactType(first, {} as string) }} {{ exactType(tail, {} as [number, boolean]) }}
+{{ tail.push(1) }}
+
+
+{{ exactType(first, {} as string) }} {{ exactType(tail, {} as string[]) }} {{ tail.push('x') }}
+
+
+{{ exactType(data, {} as string) }} {{ exactType(rest, {} as { extra: number }) }}
+{{ exactType(success, {} as { kind: 'ok'; data: string; extra: number }) }}
+
+{{ rest.kind }}
+{{ exactType(error, {} as Error) }}
+`,
+);
+
+sources.set(
+ `${workspace}/imported.ts`,
+ `export type Result = { kind: 'ok'; data: T } | { kind: 'error'; error: Error };`,
+);
+sources.set(
+ `${workspace}/generics.vue`,
+ `
+{{ exactType(data, {} as T) }} {{ data.id }}{{ error.message }}
+{{ exactType(value, {} as T) }}
+`,
+);
+
+sources.set(
+ `${workspace}/nested.vue`,
+ `
+
+`,
+);
+
+sources.set(
+ `${workspace}/unknown-narrowing.vue`,
+ `
+{{ exactType(value, {} as unknown) }}
+{{ exactType(x, {} as string | undefined) }}
+{{ exactType(first, {} as unknown) }}
+{{ exactType(extra, {} as unknown) }}
+`,
+);
+sources.set(
+ `${workspace}/invalid.vue`,
+ ``,
+);
+
+sources.set(
+ `${workspace}/javascript.vue`,
+ ``,
+);
+
+sources.set(
+ `${workspace}/javascript-missing.vue`,
+ sources.get(`${workspace}/javascript.vue`)!.replace('', ''),
+);
+
+sources.set(
+ `${workspace}/ScopeSlot.vue`,
+ ``,
+);
+sources.set(
+ `${workspace}/scopes.vue`,
+ `
+
+
+
+ {{ exactType(value, {} as string) }} {{ exactType(count, {} as true) }}
+ {{ exactType(value, {} as 1 | 2) }}
+ {{ exactType(value, {} as boolean) }}
+ {{ exactType(value, {} as string) }}
+ {{ exactType(value, {} as string) }}
+
+ {{ exactType(value, {} as { value: string; count: boolean }) }}
+
+ {{ exactType(row, {} as { value: string; count: boolean }) }}
+
+{{ exactType(value, {} as 'setup') }} {{ exactType(count, {} as number) }}
+{{ exactType(Math, {} as number) }}
+
+ {{ exactType(value, {} as number) }}
+
+
+
+ {{ exactType(value, {} as boolean) }}
+ {{ exactType(value, {} as boolean) }}
+
+
+ {{ exactType(value, {} as boolean) }}
+
+
+
+
+ {{ exactType(rows, {} as { value: string; count: boolean }) }}
+ {{ exactType(rows, {} as { value: string; count: boolean }[]) }}
+
+
+
+ {{ row.value }} {{ tail.length }}
+
+
+ {{ row }}
+
+ {{ tail }}
+
+
+`,
+);
+sources.set(
+ `${workspace}/event-scopes.vue`,
+ `
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+`,
+);
+
+const duplicatePatterns = [
+ '{ a: const value, b: const value }',
+ '{ const value, nested: { const value } }',
+ '{ const value, ...const value }',
+ '[const value, ...const value]',
+ '{ const value } as value',
+ 'const value as value',
+];
+for (const [index, pattern] of duplicatePatterns.entries()) {
+ sources.set(
+ `${workspace}/duplicate-${index}.vue`,
+ `
+
+`,
+ );
+}
+
+for (const [index] of cases.entries()) {
+ for (const lang of ['', ' lang="html"']) {
+ sources.set(
+ `${workspace}/root-${index}${lang ? '-html' : ''}.vue`,
+ sources.get(`${workspace}/${index}.vue`)!
+ .replace('\n\n', ''),
+ );
+ }
+}
+sources.set(
+ `${workspace}/root-generics.vue`,
+ `
+
+{{ exactType(data, {} as T) }} {{ exactType(result.data, {} as T) }}
+
+{{ result.error }}
+{{ exactType(data, {} as 1 | 2) }}
+{{ exactType(data, {} as T) }}
+{{ exactType(id, {} as string) }}
+
+{{ result.error.message }} {{ error.message }}
+
+{{ data }}
+
+`,
+);
+
+const options: ts.CompilerOptions = {
+ allowJs: true,
+ checkJs: true,
+ strict: true,
+ noEmit: true,
+ skipLibCheck: true,
+ allowNonTsExtensions: true,
+ target: ts.ScriptTarget.ESNext,
+ module: ts.ModuleKind.ESNext,
+ moduleResolution: ts.ModuleResolutionKind.Bundler,
+ jsx: ts.JsxEmit.Preserve,
+ types: [],
+};
+const host = ts.createCompilerHost(options);
+const directoryExists = host.directoryExists?.bind(host);
+host.directoryExists = dir => dir.replace(/\\/g, '/') === workspace || !!directoryExists?.(dir);
+const readFile = host.readFile.bind(host);
+const fileExists = host.fileExists.bind(host);
+host.readFile = file => sources.get(file.replace(/\\/g, '/')) ?? readFile(file);
+host.fileExists = file => sources.has(file.replace(/\\/g, '/')) || fileExists(file);
+const createProgram = proxyCreateProgram(ts, ts.createProgram, (ts, options) => {
+ const vueOptions = core.createParsedCommandLineByJson(ts, ts.sys, workspace, {}).vueOptions;
+ return [core.createVueLanguagePlugin(ts, options.options, vueOptions, id => id)];
+});
+const program = createProgram({ rootNames: [...sources.keys()], options, host });
+
+describe('RFC 823 required coverage', () => {
+ for (const [index, [name, , , exhaustive]] of cases.entries()) {
+ test.each(['', '-html'])(`SFC root ${name} (%s)`, suffix => {
+ const fileName = `${workspace}/root-${index}${suffix}.vue`;
+ const file = program.getSourceFile(fileName)!;
+ const diagnostics = [...program.getSyntacticDiagnostics(file), ...program.getSemanticDiagnostics(file)];
+ expect(diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')))
+ .toEqual(exhaustive ? [] : [expect.stringContaining('Non-exhaustive v-match')]);
+ if (!exhaustive) {
+ expect(diagnostics[0]!.start).toBe(sources.get(fileName)!.indexOf('v-match="subject"') + 'v-match="'.length);
+ expect(diagnostics[0]!.length).toBe('subject'.length);
+ }
+ });
+ }
+ for (const [index, pattern] of duplicatePatterns.entries()) {
+ test(`rejects duplicate declarations in ${pattern}`, () => {
+ const file = program.getSourceFile(`${workspace}/duplicate-${index}.vue`)!;
+ expect(program.getSemanticDiagnostics(file).map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n')))
+ .toEqual([expect.stringContaining('Duplicate pattern binding value')]);
+ });
+ }
+ test('inferred JavaScript template types require coverage', () => {
+ const file = program.getSourceFile(`${workspace}/javascript-missing.vue`)!;
+ const diagnostics = program.getSemanticDiagnostics(file);
+ expect(diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n'))).toEqual([
+ expect.stringContaining('Non-exhaustive v-match'),
+ ]);
+ });
+
+ test('invalid pattern is a vue-tsc error', () => {
+ const file = program.getSourceFile(`${workspace}/invalid.vue`)!;
+ const diagnostics = program.getSemanticDiagnostics(file);
+ expect(diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n'))).toEqual([
+ expect.stringContaining('Only const pattern bindings'),
+ ]);
+ });
+
+ for (const [index, [name, , , exhaustive]] of cases.entries()) {
+ test(name, () => {
+ const fileName = `${workspace}/${index}.vue`;
+ const file = program.getSourceFile(fileName)!;
+ const diagnostics = [...program.getSyntacticDiagnostics(file), ...program.getSemanticDiagnostics(file)];
+ const messages = diagnostics.map(d => ts.flattenDiagnosticMessageText(d.messageText, '\n'));
+ expect(messages, name).toHaveLength(exhaustive ? 0 : 1);
+ if (!exhaustive) {
+ expect(messages[0]).toContain('Non-exhaustive v-match');
+ if (name === 'missing tuple product') {
+ expect(messages[0]?.replaceAll('\\"', '"')).toContain(`v-when="['right', 'bottom']"`);
+ }
+ if (name === 'missing literal') {
+ expect(messages[0]?.replaceAll('\\"', '"')).toContain('v-when="\'c\'"');
+ }
+ expect(sources.get(fileName)!.slice(diagnostics[0]!.start, diagnostics[0]!.start! + diagnostics[0]!.length!))
+ .toBe('subject');
+ }
+ });
+ }
+ for (
+ const name of [
+ 'narrowing',
+ 'values',
+ 'rest',
+ 'generics',
+ 'nested',
+ 'unknown-narrowing',
+ 'javascript',
+ 'scopes',
+ 'event-scopes',
+ 'root-generics',
+ ]
+ ) {
+ test(name, () => {
+ const file = program.getSourceFile(`${workspace}/${name}.vue`)!;
+ expect(
+ [...program.getSyntacticDiagnostics(file), ...program.getSemanticDiagnostics(file)].map(d =>
+ ts.flattenDiagnosticMessageText(d.messageText, '\n')
+ ),
+ ).toEqual([]);
+ });
+ }
+});
diff --git a/packages/tsc/tests/patternedTemplatesCli.spec.ts b/packages/tsc/tests/patternedTemplatesCli.spec.ts
new file mode 100644
index 0000000000..50b1eb8611
--- /dev/null
+++ b/packages/tsc/tests/patternedTemplatesCli.spec.ts
@@ -0,0 +1,60 @@
+import { spawnSync } from 'node:child_process';
+import * as fs from 'node:fs';
+import * as path from 'node:path';
+import { expect, test } from 'vitest';
+
+function check(arms: string) {
+ const directory = fs.mkdtempSync(path.resolve(__dirname, '../../../test-workspace/.patterned-cli-'));
+ try {
+ fs.writeFileSync(
+ path.join(directory, 'tsconfig.json'),
+ JSON.stringify({
+ compilerOptions: {
+ strict: true,
+ noEmit: true,
+ skipLibCheck: true,
+ target: 'ESNext',
+ module: 'ESNext',
+ moduleResolution: 'Bundler',
+ types: [],
+ },
+ include: ['*.vue'],
+ }),
+ );
+ fs.writeFileSync(
+ path.join(directory, 'App.vue'),
+ `${arms}`,
+ );
+ return spawnSync(process.execPath, [
+ path.resolve(__dirname, '../bin/vue-tsc.js'),
+ '--noEmit',
+ '--pretty',
+ 'false',
+ '-p',
+ directory,
+ ], { encoding: 'utf8' });
+ }
+ finally {
+ fs.rmSync(directory, { recursive: true, force: true });
+ }
+}
+
+test('vue-tsc --noEmit fails on missing coverage with a source diagnostic', () => {
+ const result = check(``);
+ expect(result.status).toBe(2);
+ expect(result.stdout).toContain('App.vue(');
+ expect(result.stdout).toContain('Non-exhaustive v-match');
+ expect(result.stdout.replaceAll('\\"', '"')).toContain(`v-when="'b'"`);
+});
+
+test('vue-tsc reports unreachable arms as warnings without failing', () => {
+ const result = check(``);
+ expect(result.status, result.stdout + result.stderr).toBe(0);
+ expect(result.stdout.match(/warning V_MATCH_UNREACHABLE/g)).toHaveLength(1);
+});
+
+test('vue-tsc fails on invalid pattern syntax even with a catch-all', () => {
+ const result = check(``);
+ expect(result.status).toBe(2);
+ expect(result.stdout).toContain('Only const pattern bindings');
+});
diff --git a/packages/typescript-plugin/index.ts b/packages/typescript-plugin/index.ts
index a400ef1a04..3d8567a823 100644
--- a/packages/typescript-plugin/index.ts
+++ b/packages/typescript-plugin/index.ts
@@ -19,6 +19,7 @@ import { getComponentSlots } from './lib/requests/getComponentSlots';
import { getElementAttrs } from './lib/requests/getElementAttrs';
import { getElementNames } from './lib/requests/getElementNames';
import { getImportPathForFile } from './lib/requests/getImportPathForFile';
+import { getMatchWarnings } from './lib/requests/getMatchWarnings';
import { isRefAtPosition } from './lib/requests/isRefAtPosition';
import { resolveModuleName } from './lib/requests/resolveModuleName';
@@ -272,6 +273,11 @@ export = createLanguageServicePlugin(
),
);
});
+ session.addProtocolHandler('_vue:getMatchWarnings', request => {
+ const [fileName]: Parameters = request.arguments;
+ const { program, virtualCode } = getProjectAndVirtualCode(fileName);
+ return createResponse(getMatchWarnings(ts, program, virtualCode.fileName));
+ });
session.addProtocolHandler('_vue:getComponentNames', request => {
const [fileName]: Parameters = request.arguments;
const { program, virtualCode } = getProjectAndVirtualCode(fileName);
diff --git a/packages/typescript-plugin/lib/requests/getMatchWarnings.ts b/packages/typescript-plugin/lib/requests/getMatchWarnings.ts
new file mode 100644
index 0000000000..3b016a5cac
--- /dev/null
+++ b/packages/typescript-plugin/lib/requests/getMatchWarnings.ts
@@ -0,0 +1 @@
+export { getMatchWarnings } from '@vue/language-core';
diff --git a/packages/typescript-plugin/lib/requests/index.ts b/packages/typescript-plugin/lib/requests/index.ts
index c6d61f3b51..f3def8b41c 100644
--- a/packages/typescript-plugin/lib/requests/index.ts
+++ b/packages/typescript-plugin/lib/requests/index.ts
@@ -4,6 +4,7 @@ import type { VueCompletionData } from '../common';
type Response = T | null | undefined | Promise;
export interface Requests {
+ getMatchWarnings(fileName: string): Response>;
collectExtractProps(
fileName: string,
templateCodeRange: [number, number],