Skip to content
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/fix-vue-conditional-newline-comment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@biomejs/biome": patch
---

Fixed several bugs in Vue conditional rules (`useVueValidVIf`, `useVueValidVElse`, and `useVueValidVElseIf`) related to interspersed comments, newlines, and self-closing tags.
25 changes: 17 additions & 8 deletions crates/biome_html_analyze/src/lint/nursery/use_vue_valid_v_else.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use biome_analyze::{
Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_html_syntax::{AnyVueDirective, HtmlElement, HtmlSelfClosingElement, VueDirective};
use biome_html_syntax::{
AnyVueDirective, HtmlElement, HtmlSelfClosingElement, HtmlSyntaxKind, VueDirective,
};
use biome_rowan::{AstNode, TextRange, declare_node_union};
use biome_rule_options::use_vue_valid_v_else::UseVueValidVElseOptions;

Expand Down Expand Up @@ -201,7 +203,7 @@ fn has_v_if_or_else_if_directives(element: &AnyHtmlElement) -> bool {
AnyVueDirective::try_cast(attribute.syntax().clone())
&& let Ok(name_token) = vue_dir.name_token()
{
let name = name_token.text();
let name = name_token.text_trimmed();
if name == "v-if" || name == "v-else-if" {
return true;
}
Expand All @@ -212,12 +214,19 @@ fn has_v_if_or_else_if_directives(element: &AnyHtmlElement) -> bool {
}

fn has_previous_sibling_with_v_if_or_else_if(element: &AnyHtmlElement) -> bool {
if let Some(sibling) = element
.syntax()
.prev_sibling()
.and_then(|s| AnyHtmlElement::cast_ref(&s))
{
return has_v_if_or_else_if_directives(&sibling);
let mut prev = element.syntax().prev_sibling();
while let Some(node) = prev {
if let Some(sibling) = AnyHtmlElement::cast_ref(&node) {
return has_v_if_or_else_if_directives(&sibling);
}

// Skip comments and purely whitespace nodes
if matches!(node.kind(), HtmlSyntaxKind::COMMENT) || node.text_trimmed().is_empty() {
Comment thread
playhardgopro marked this conversation as resolved.
Outdated
prev = node.prev_sibling();
continue;
}

return false;
}

false
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,9 @@ use biome_analyze::{
Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_html_syntax::{AnyVueDirective, HtmlElement, HtmlSelfClosingElement, VueDirective};
use biome_html_syntax::{
AnyVueDirective, HtmlElement, HtmlSelfClosingElement, HtmlSyntaxKind, VueDirective,
};
use biome_rowan::{AstNode, AstNodeList, TextRange, declare_node_union};
use biome_rule_options::use_vue_valid_v_else_if::UseVueValidVElseIfOptions;

Expand Down Expand Up @@ -229,12 +231,19 @@ fn find_conflicting_directive(vue_directive: &VueDirective) -> Option<TextRange>
}

fn has_previous_sibling_with_v_if_or_else_if(element: &AnyHtmlElement) -> bool {
if let Some(sibling) = element
.syntax()
.prev_sibling()
.and_then(|s| AnyHtmlElement::cast_ref(&s))
{
return has_v_if_or_else_if_directives(&sibling);
let mut prev = element.syntax().prev_sibling();
while let Some(node) = prev {
if let Some(sibling) = AnyHtmlElement::cast_ref(&node) {
return has_v_if_or_else_if_directives(&sibling);
}

// Skip comments and purely whitespace nodes
if matches!(node.kind(), HtmlSyntaxKind::COMMENT) || node.text_trimmed().is_empty() {
prev = node.prev_sibling();
continue;
}

return false;
}
false
}
Expand All @@ -255,7 +264,7 @@ fn has_v_if_or_else_if_directives(element: &AnyHtmlElement) -> bool {
AnyVueDirective::try_cast(attribute.syntax().clone())
&& let Ok(name_token) = vue_dir.name_token()
{
let name = name_token.text();
let name = name_token.text_trimmed();
if (name == "v-if" || name == "v-else-if") && is_valid_chain_directive(name, &vue_dir) {
return true;
}
Expand Down
30 changes: 9 additions & 21 deletions crates/biome_html_analyze/src/lint/nursery/use_vue_valid_v_if.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use biome_analyze::{
Ast, Rule, RuleDiagnostic, RuleDomain, RuleSource, context::RuleContext, declare_lint_rule,
};
use biome_console::markup;
use biome_html_syntax::{AnyVueDirective, HtmlElement, VueDirective};
use biome_html_syntax::{AnyVueDirective, VueDirective};
use biome_rowan::{AstNode, AstNodeList, TextRange};
use biome_rule_options::use_vue_valid_v_if::UseVueValidVIfOptions;

Expand Down Expand Up @@ -106,10 +106,7 @@ impl Rule for UseVueValidVIf {
}

// Check for conflicts with v-else or v-else-if on the same element
if let Some(element) = find_containing_element(vue_directive)
&& let Some(conflict_range) =
find_conflicting_else_directives(&element, vue_directive)
{
if let Some(conflict_range) = find_conflicting_else_directives(vue_directive) {
return Some(ViolationKind::ConflictsWithElse(conflict_range));
}

Expand Down Expand Up @@ -168,23 +165,14 @@ impl Rule for UseVueValidVIf {
}
}

/// Find containing HTML element for a Vue directive
fn find_containing_element(directive: &VueDirective) -> Option<HtmlElement> {
directive
.syntax()
.ancestors()
.skip(1)
.find_map(HtmlElement::cast)
}

/// Find conflicting v-else or v-else-if directives on the same element
fn find_conflicting_else_directives(
element: &HtmlElement,
v_if_directive: &VueDirective,
) -> Option<TextRange> {
let opening_element = element.opening_element().ok()?;
fn find_conflicting_else_directives(v_if_directive: &VueDirective) -> Option<TextRange> {
let attribute_list = v_if_directive
.syntax()
.parent()
.and_then(biome_html_syntax::HtmlAttributeList::cast)?;

for attribute in opening_element.attributes() {
for attribute in attribute_list {
if let Some(AnyVueDirective::VueDirective(directive)) = attribute.as_any_vue_directive() {
// Skip the v-if directive we're currently checking
if directive.syntax() == v_if_directive.syntax() {
Expand All @@ -193,7 +181,7 @@ fn find_conflicting_else_directives(

// Check for v-else or v-else-if
if let Ok(name_token) = directive.name_token() {
let name = name_token.text();
let name = name_token.text_trimmed();
if name == "v-else" || name == "v-else-if" {
return Some(directive.range());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,4 +30,23 @@
<div v-else></div>
<span v-if="cond2"></span>
<span v-else></span>

<!-- Multiline conditional elements -->
<div
v-if="condition"
></div>
<div v-else></div>

<!-- Multiline conditional elements with comments -->
<div
v-if="condition"
></div>
<!-- comment between multiline elements -->
<div v-else></div>

<!-- Nested conditionals -->
<div v-if="cond1"></div>
<div v-else>
<span v-if="cond2" />
</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -36,5 +36,24 @@ expression: valid.vue
<div v-else></div>
<span v-if="cond2"></span>
<span v-else></span>

<!-- Multiline conditional elements -->
<div
v-if="condition"
></div>
<div v-else></div>

<!-- Multiline conditional elements with comments -->
<div
v-if="condition"
></div>
<!-- comment between multiline elements -->
<div v-else></div>

<!-- Nested conditionals -->
<div v-if="cond1"></div>
<div v-else>
<span v-if="cond2" />
</div>
</template>
```
Original file line number Diff line number Diff line change
Expand Up @@ -48,4 +48,23 @@
<div v-if="user.age >= 18 && user.hasPermission"></div>
<div v-else-if="user.age >= 13 && user.parentalConsent"></div>
<div v-else></div>

<!-- Multiline conditional elements -->
<div
v-if="condition"
></div>
<div v-else-if="other"></div>

<!-- Multiline conditional elements with comments -->
<div
v-if="condition"
></div>
<!-- comment between multiline elements -->
<div v-else-if="other"></div>

<!-- Nested conditionals -->
<div v-if="cond1"></div>
<div v-else-if="cond2">
<span v-if="cond3" />
</div>
</template>
Original file line number Diff line number Diff line change
Expand Up @@ -54,5 +54,24 @@ expression: valid.vue
<div v-if="user.age >= 18 && user.hasPermission"></div>
<div v-else-if="user.age >= 13 && user.parentalConsent"></div>
<div v-else></div>

<!-- Multiline conditional elements -->
<div
v-if="condition"
></div>
<div v-else-if="other"></div>

<!-- Multiline conditional elements with comments -->
<div
v-if="condition"
></div>
<!-- comment between multiline elements -->
<div v-else-if="other"></div>

<!-- Nested conditionals -->
<div v-if="cond1"></div>
<div v-else-if="cond2">
<span v-if="cond3" />
</div>
</template>
```
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,10 @@
<div v-if="a"></div>
<div v-else-if="b"></div>
<div v-else></div>
<div id="x" class="y" v-if="flag"></div>
<div id="x" class="y" v-if="flag"></div>

<!-- Nested conditionals -->
<div v-if="cond1"></div>
<div v-else>
<span v-if="cond2"/>
</div>
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,10 @@ expression: valid.vue
<div v-else-if="b"></div>
<div v-else></div>
<div id="x" class="y" v-if="flag"></div>

<!-- Nested conditionals -->
<div v-if="cond1"></div>
<div v-else>
<span v-if="cond2"/>
</div>
```