From c28e2f21b03a5bb8c39a610f86398617d86b1a1f Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Sat, 22 Nov 2025 13:06:57 -0500 Subject: [PATCH 1/2] fix(parse/html/vue): fix lexing of event handlers with `:` in them --- .changeset/lemon-impalas-check.md | 5 ++ .../tests/specs/html/vue/event-with-colon.vue | 1 + .../specs/html/vue/event-with-colon.vue.snap | 33 ++++++++ crates/biome_html_parser/src/lexer/mod.rs | 16 ++++ .../html_specs/ok/vue/event-with-colon.vue | 1 + .../ok/vue/event-with-colon.vue.snap | 75 +++++++++++++++++++ 6 files changed, 131 insertions(+) create mode 100644 .changeset/lemon-impalas-check.md create mode 100644 crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue create mode 100644 crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap create mode 100644 crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue create mode 100644 crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue.snap diff --git a/.changeset/lemon-impalas-check.md b/.changeset/lemon-impalas-check.md new file mode 100644 index 000000000000..951884655787 --- /dev/null +++ b/.changeset/lemon-impalas-check.md @@ -0,0 +1,5 @@ +--- +"@biomejs/biome": patch +--- + +Fixed [#8190](https://github.com/biomejs/biome/issues/8190): The HTML parser will now parse event handlers that contain `:` correctly, e.g. `@update:modelValue="onUpdate"`. diff --git a/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue b/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue new file mode 100644 index 000000000000..0c264b19c63f --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue @@ -0,0 +1 @@ + diff --git a/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap b/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap new file mode 100644 index 000000000000..e8ab8faee7f0 --- /dev/null +++ b/crates/biome_html_formatter/tests/specs/html/vue/event-with-colon.vue.snap @@ -0,0 +1,33 @@ +--- +source: crates/biome_formatter_test/src/snapshot_builder.rs +info: vue/event-with-colon.vue +--- +# Input + +```vue + + +``` + + +============================= + +# Outputs + +## Output 1 + +----- +Indent style: Tab +Indent width: 2 +Line ending: LF +Line width: 80 +Attribute Position: Auto +Bracket same line: false +Whitespace sensitivity: css +Indent script and style: false +Self close void elements: never +----- + +```vue + +``` diff --git a/crates/biome_html_parser/src/lexer/mod.rs b/crates/biome_html_parser/src/lexer/mod.rs index f576d9a48c09..bdc290be2be5 100644 --- a/crates/biome_html_parser/src/lexer/mod.rs +++ b/crates/biome_html_parser/src/lexer/mod.rs @@ -35,6 +35,7 @@ enum IdentifierContext { Doctype, Svelte, Vue, + VueDirectiveArgument, } impl IdentifierContext { @@ -147,6 +148,9 @@ impl<'src> HtmlLexer<'src> { // https://html.spec.whatwg.org/multipage/syntax.html#start-tags self.consume_tag_name(current) } + _ if (self.current_kind == T![@] && is_attribute_name_byte_vue(current)) => { + self.consume_identifier(current, IdentifierContext::VueDirectiveArgument) + } _ if (self.current_kind != T![<] && is_attribute_name_byte_vue(current)) => { self.consume_identifier(current, IdentifierContext::Vue) } @@ -471,6 +475,18 @@ impl<'src> HtmlLexer<'src> { len += 1; } + self.advance(1) + } else { + break; + } + } + IdentifierContext::VueDirectiveArgument => { + if is_attribute_name_byte_vue(byte) || byte == b':' { + if len < BUFFER_SIZE { + buffer[len] = byte; + len += 1; + } + self.advance(1) } else { break; diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue b/crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue new file mode 100644 index 000000000000..0c264b19c63f --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue @@ -0,0 +1 @@ + diff --git a/crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue.snap b/crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue.snap new file mode 100644 index 000000000000..483aa9fde51e --- /dev/null +++ b/crates/biome_html_parser/tests/html_specs/ok/vue/event-with-colon.vue.snap @@ -0,0 +1,75 @@ +--- +source: crates/biome_html_parser/tests/spec_test.rs +expression: snapshot +--- +## Input + +```vue + + +``` + + +## AST + +``` +HtmlRoot { + bom_token: missing (optional), + frontmatter: missing (optional), + directive: missing (optional), + html: HtmlElementList [ + HtmlSelfClosingElement { + l_angle_token: L_ANGLE@0..1 "<" [] [], + name: HtmlTagName { + value_token: HTML_LITERAL@1..5 "Foo" [] [Whitespace(" ")], + }, + attributes: HtmlAttributeList [ + VueVOnShorthandDirective { + at_token: AT@5..6 "@" [] [], + arg: VueStaticArgument { + name_token: HTML_LITERAL@6..23 "update:modelValue" [] [], + }, + modifiers: VueModifierList [], + initializer: HtmlAttributeInitializerClause { + eq_token: EQ@23..24 "=" [] [], + value: HtmlString { + value_token: HTML_STRING_LITERAL@24..35 "\"onUpdate\"" [] [Whitespace(" ")], + }, + }, + }, + ], + slash_token: SLASH@35..36 "/" [] [], + r_angle_token: R_ANGLE@36..37 ">" [] [], + }, + ], + eof_token: EOF@37..38 "" [Newline("\n")] [], +} +``` + +## CST + +``` +0: HTML_ROOT@0..38 + 0: (empty) + 1: (empty) + 2: (empty) + 3: HTML_ELEMENT_LIST@0..37 + 0: HTML_SELF_CLOSING_ELEMENT@0..37 + 0: L_ANGLE@0..1 "<" [] [] + 1: HTML_TAG_NAME@1..5 + 0: HTML_LITERAL@1..5 "Foo" [] [Whitespace(" ")] + 2: HTML_ATTRIBUTE_LIST@5..35 + 0: VUE_V_ON_SHORTHAND_DIRECTIVE@5..35 + 0: AT@5..6 "@" [] [] + 1: VUE_STATIC_ARGUMENT@6..23 + 0: HTML_LITERAL@6..23 "update:modelValue" [] [] + 2: VUE_MODIFIER_LIST@23..23 + 3: HTML_ATTRIBUTE_INITIALIZER_CLAUSE@23..35 + 0: EQ@23..24 "=" [] [] + 1: HTML_STRING@24..35 + 0: HTML_STRING_LITERAL@24..35 "\"onUpdate\"" [] [Whitespace(" ")] + 3: SLASH@35..36 "/" [] [] + 4: R_ANGLE@36..37 ">" [] [] + 4: EOF@37..38 "" [Newline("\n")] [] + +``` From d37d42fb1d1251acc8f12d6691eced8f5fd9d84f Mon Sep 17 00:00:00 2001 From: Carson McManus Date: Mon, 24 Nov 2025 07:56:51 -0500 Subject: [PATCH 2/2] Update .changeset/lemon-impalas-check.md Co-authored-by: Emanuele Stoppa --- .changeset/lemon-impalas-check.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.changeset/lemon-impalas-check.md b/.changeset/lemon-impalas-check.md index 951884655787..8ece8032d1d3 100644 --- a/.changeset/lemon-impalas-check.md +++ b/.changeset/lemon-impalas-check.md @@ -2,4 +2,4 @@ "@biomejs/biome": patch --- -Fixed [#8190](https://github.com/biomejs/biome/issues/8190): The HTML parser will now parse event handlers that contain `:` correctly, e.g. `@update:modelValue="onUpdate"`. +Fixed [#8190](https://github.com/biomejs/biome/issues/8190): The HTML parser will now parse Vue event handlers that contain `:` correctly, e.g. `@update:modelValue="onUpdate"`.