fix dx fmt span attribute issue (#5132)

* fix: if expr spans causing invalid span locations

* cleanups

* clippy, typo
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-12-25 16:50:50 -05:00
1 parent b050dfec5c
commit db23197925
4 files changed
+63 -38

No files matched your search

+26 -19
View File
@@ -58,10 +58,10 @@ impl<'a> Writer<'a> {
fn write_trailing_body_comments(&mut self, body: &CallBody) -> Result { fn write_trailing_body_comments(&mut self, body: &CallBody) -> Result {
if let Some(span) = body.span { if let Some(span) = body.span {
self.out.indent_level += 1; self.out.indent_level += 1;
let comments = self.accumulate_comments(span.span().end()); let comments = self.accumulate_full_line_comments(span.span().end());
if !comments.is_empty() { if !comments.is_empty() {
self.out.new_line()?; self.out.new_line()?;
self.apply_comments(comments)?; self.apply_line_comments(comments)?;
self.out.buf.pop(); // remove the trailing newline, forcing us to end at the end of the comment self.out.buf.pop(); // remove the trailing newline, forcing us to end at the end of the comment
} }
self.out.indent_level -= 1; self.out.indent_level -= 1;
@@ -425,9 +425,9 @@ impl<'a> Writer<'a> {
ShortOptimization::NoOpt | ShortOptimization::PropsOnTop ShortOptimization::NoOpt | ShortOptimization::PropsOnTop
) && self.leading_row_is_empty(brace.span.span().end()) ) && self.leading_row_is_empty(brace.span.span().end())
{ {
let comments = self.accumulate_comments(brace.span.span().end()); let comments = self.accumulate_full_line_comments(brace.span.span().end());
if !comments.is_empty() { if !comments.is_empty() {
self.apply_comments(comments)?; self.apply_line_comments(comments)?;
self.out.tab()?; self.out.tab()?;
} }
} }
@@ -487,7 +487,7 @@ impl<'a> Writer<'a> {
.comma .comma
.as_ref() .as_ref()
.map(|c| c.span()) .map(|c| c.span())
.unwrap_or_else(|| self.final_span_of_attr(attr)), .unwrap_or_else(|| self.total_span_of_attr(attr)),
AttrType::Spread(attr) => attr.span(), AttrType::Spread(attr) => attr.span(),
}; };
@@ -565,7 +565,7 @@ impl<'a> Writer<'a> {
} }
fn write_attribute_if_chain(&mut self, if_chain: &IfAttributeValue) -> Result { fn write_attribute_if_chain(&mut self, if_chain: &IfAttributeValue) -> Result {
let cond = self.unparse_expr(&if_chain.condition); let cond = self.unparse_expr(&if_chain.if_expr.cond);
write!(self.out, "if {cond} {{ ")?; write!(self.out, "if {cond} {{ ")?;
self.write_attribute_value(&if_chain.then_value)?; self.write_attribute_value(&if_chain.then_value)?;
write!(self.out, " }}")?; write!(self.out, " }}")?;
@@ -619,13 +619,19 @@ impl<'a> Writer<'a> {
whitespace = whitespace[offset..].trim(); whitespace = whitespace[offset..].trim();
// don't emit whitespace if the span is messed up for some reason
if final_span.line == 1 && final_span.column == 0 {
return Ok(());
};
if whitespace.starts_with("//") { if whitespace.starts_with("//") {
write!(self.out, " {whitespace}")?; write!(self.out, " {whitespace}")?;
} }
Ok(()) Ok(())
} }
fn accumulate_comments(&mut self, loc: LineColumn) -> VecDeque<usize> {
fn accumulate_full_line_comments(&mut self, loc: LineColumn) -> VecDeque<usize> {
// collect all comments upwards // collect all comments upwards
// make sure we don't collect the comments of the node that we're currently under. // make sure we don't collect the comments of the node that we're currently under.
let start = loc; let start = loc;
@@ -633,6 +639,11 @@ impl<'a> Writer<'a> {
let mut comments = VecDeque::new(); let mut comments = VecDeque::new();
// don't emit whitespace if the span is messed up for some reason
if loc.line == 1 && loc.column == 0 {
return comments;
};
let Some(lines) = self.src.get(..line_start) else { let Some(lines) = self.src.get(..line_start) else {
return comments; return comments;
}; };
@@ -649,7 +660,8 @@ impl<'a> Writer<'a> {
comments comments
} }
fn apply_comments(&mut self, mut comments: VecDeque<usize>) -> Result {
fn apply_line_comments(&mut self, mut comments: VecDeque<usize>) -> Result {
while let Some(comment_line) = comments.pop_front() { while let Some(comment_line) = comments.pop_front() {
let Some(line) = self.src.get(comment_line) else { let Some(line) = self.src.get(comment_line) else {
continue; continue;
@@ -668,16 +680,15 @@ impl<'a> Writer<'a> {
} }
fn write_comments(&mut self, loc: LineColumn) -> Result { fn write_comments(&mut self, loc: LineColumn) -> Result {
let comments = self.accumulate_comments(loc); let comments = self.accumulate_full_line_comments(loc);
self.apply_comments(comments)?; self.apply_line_comments(comments)?;
Ok(()) Ok(())
} }
fn attr_value_len(&mut self, value: &AttributeValue) -> usize { fn attr_value_len(&mut self, value: &AttributeValue) -> usize {
match value { match value {
AttributeValue::IfExpr(if_chain) => { AttributeValue::IfExpr(if_chain) => {
let condition_len = self.retrieve_formatted_expr(&if_chain.condition).len(); let condition_len = self.retrieve_formatted_expr(&if_chain.if_expr.cond).len();
let value_len = self.attr_value_len(&if_chain.then_value); let value_len = self.attr_value_len(&if_chain.then_value);
let if_len = 2; let if_len = 2;
let brace_len = 2; let brace_len = 2;
@@ -1069,17 +1080,13 @@ impl<'a> Writer<'a> {
} }
} }
fn final_span_of_attr(&self, attr: &Attribute) -> Span { fn total_span_of_attr(&self, attr: &Attribute) -> Span {
match &attr.value { match &attr.value {
AttributeValue::Shorthand(s) => s.span(), AttributeValue::Shorthand(s) => s.span(),
AttributeValue::AttrLiteral(l) => l.span(), AttributeValue::AttrLiteral(l) => l.span(),
AttributeValue::EventTokens(closure) => closure.body.span(), AttributeValue::EventTokens(closure) => closure.span(),
AttributeValue::AttrExpr(exp) => exp.span(), AttributeValue::AttrExpr(exp) => exp.span(),
AttributeValue::IfExpr(ex) => ex AttributeValue::IfExpr(ex) => ex.span(),
.else_value
.as_ref()
.map(|v| v.span())
.unwrap_or_else(|| ex.then_value.span()),
} }
} }
+1
View File
@@ -61,4 +61,5 @@ twoway![
prop_rsx, prop_rsx,
asset, asset,
collapse, collapse,
expr_on_conditional,
]; ];
@@ -0,0 +1,14 @@
//! A comment about this file
use dioxus::prelude::*;
#[component]
pub fn Sample() -> Element {
let is_active = use_signal(|| false);
rsx! {
div { class: if is_active() { "active" } else { "inactive" },
div { class: if is_active2() { "a" } else { "b" } }
}
}
}
+22 -19
View File
@@ -17,7 +17,7 @@
use super::literal::HotLiteral; use super::literal::HotLiteral;
use crate::{innerlude::*, partial_closure::PartialClosure}; use crate::{innerlude::*, partial_closure::PartialClosure};
use proc_macro2::TokenStream as TokenStream2; use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, quote_spanned, ToTokens, TokenStreamExt}; use quote::{quote, quote_spanned, ToTokens, TokenStreamExt};
use std::fmt::Display; use std::fmt::Display;
use syn::{ use syn::{
@@ -173,7 +173,7 @@ impl Attribute {
let AttributeValue::AttrExpr(expr) = &self.value else { let AttributeValue::AttrExpr(expr) = &self.value else {
unreachable!("Spread attributes should always be expressions") unreachable!("Spread attributes should always be expressions")
}; };
return quote! { {#expr}.into_boxed_slice() }; return quote_spanned! { expr.span() => {#expr}.into_boxed_slice() };
} }
let el_name = self let el_name = self
@@ -289,8 +289,9 @@ impl Attribute {
} }
}; };
let attr_span = attribute.span();
let completion_hints = self.completion_hints(); let completion_hints = self.completion_hints();
quote! { quote_spanned! { attr_span =>
Box::new([ Box::new([
{ {
#completion_hints #completion_hints
@@ -531,6 +532,7 @@ impl AttributeValue {
/// A if else chain attribute value /// A if else chain attribute value
#[derive(PartialEq, Eq, Clone, Debug, Hash)] #[derive(PartialEq, Eq, Clone, Debug, Hash)]
pub struct IfAttributeValue { pub struct IfAttributeValue {
pub if_expr: ExprIf,
pub condition: Expr, pub condition: Expr,
pub then_value: Box<AttributeValue>, pub then_value: Box<AttributeValue>,
pub else_value: Option<Box<AttributeValue>>, pub else_value: Option<Box<AttributeValue>>,
@@ -560,7 +562,7 @@ impl IfAttributeValue {
return non_string_diagnostic(current_if_value.span()); return non_string_diagnostic(current_if_value.span());
}; };
let condition = &current_if_value.condition; let condition = &current_if_value.if_expr.cond;
expression.extend(quote! { expression.extend(quote! {
if #condition { if #condition {
#new.to_string() #new.to_string()
@@ -599,8 +601,8 @@ impl IfAttributeValue {
} }
} }
fn span(&self) -> proc_macro2::Span { fn span(&self) -> Span {
self.then_value.span() self.if_expr.span()
} }
fn is_terminated(&self) -> bool { fn is_terminated(&self) -> bool {
@@ -646,7 +648,7 @@ impl IfAttributeValue {
match stmt { match stmt {
syn::Stmt::Expr(exp, None) => { syn::Stmt::Expr(exp, None) => {
// Try parsing the statement as an IfmtInput by passing it through tokens // Try parsing the statement as an IfmtInput by passing it through tokens
let value: Result<HotLiteral, syn::Error> = syn::parse2(quote! { #exp }); let value: Result<HotLiteral, syn::Error> = syn::parse2(exp.to_token_stream());
Ok(match value { Ok(match value {
Ok(res) => Box::new(AttributeValue::AttrLiteral(res)), Ok(res) => Box::new(AttributeValue::AttrLiteral(res)),
Err(_) => Box::new(AttributeValue::AttrExpr(PartialExpr::from_expr(exp))), Err(_) => Box::new(AttributeValue::AttrExpr(PartialExpr::from_expr(exp))),
@@ -663,9 +665,10 @@ impl IfAttributeValue {
contains_expression: bool, contains_expression: bool,
) { ) {
let IfAttributeValue { let IfAttributeValue {
condition, if_expr,
then_value, then_value,
else_value, else_value,
..
} = self; } = self;
// Quote an attribute value and convert the value to a string if it is formatted // Quote an attribute value and convert the value to a string if it is formatted
@@ -712,21 +715,20 @@ impl IfAttributeValue {
Some(other) => { Some(other) => {
let other = quote_attribute_value_string(other, contains_expression); let other = quote_attribute_value_string(other, contains_expression);
if terminated { if terminated {
quote! { #other } other
} else { } else {
quote! { Some(#other) } quote_spanned! { other.span() => Some(#other) }
} }
} }
None => quote! { None }, None => quote! { None },
}; };
tokens.append_all(quote! { let condition = &if_expr.cond;
{ tokens.append_all(quote_spanned! { if_expr.span()=>
if #condition { if #condition {
#then_value #then_value
} else { } else {
#else_value #else_value
}
} }
}); });
} }
@@ -756,7 +758,7 @@ impl Parse for IfAttributeValue {
// If it is a block, then the else is terminated // If it is a block, then the else is terminated
Expr::Block(block) => Self::parse_attribute_value_from_block(&block.block)?, Expr::Block(block) => Self::parse_attribute_value_from_block(&block.block)?,
// Otherwise try to parse it as an if expression // Otherwise try to parse it as an if expression
_ => Box::new(syn::parse2(quote! { #else_branch })?), _ => Box::new(syn::parse2(else_branch.to_token_stream())?),
}; };
Some(attribute_value) Some(attribute_value)
} }
@@ -764,7 +766,8 @@ impl Parse for IfAttributeValue {
}; };
Ok(Self { Ok(Self {
condition: *if_expr.cond, condition: *if_expr.cond.clone(),
if_expr,
then_value, then_value,
else_value, else_value,
}) })