diff --git a/Cargo.toml b/Cargo.toml index 6ee28c1..41a8652 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,6 +29,8 @@ anyhow = "1" regen-tests = [] # solely for testing [lints.clippy] +unit_arg = "allow" + single_char_lifetime_names = "warn" deref_by_slicing = "warn" derive_partial_eq_without_eq = "warn" diff --git a/src/formatter.rs b/src/formatter.rs index 1bf851a..bc5a7b1 100644 --- a/src/formatter.rs +++ b/src/formatter.rs @@ -43,7 +43,7 @@ fn add_last_line_len(prev: usize, new: &str) -> usize { enum Comment<'src> { /// the initial `//` and the newline are not included Line(&'src str), - /// the `/*` and `*/` are not included + /// the `/*` and `*/` are included Multi(&'src str), } @@ -116,16 +116,6 @@ pub struct Location { pub end: LineColumn, } -impl Location { - pub fn from_slice(src: impl AsRef<[T]>) -> Option { - match src.as_ref() { - [first, .., last] => Some(Self { start: first.start(), end: last.end() }), - [only] => Some(only.loc()), - [] => None, - } - } -} - /// Represents an object that has an associated location in the source pub trait Located { fn start(&self) -> LineColumn; @@ -204,16 +194,6 @@ enum FmtToken<'fmt, 'src> { Block(FmtBlock<'fmt, 'src>), } -impl FmtToken<'_, '_> { - fn is_broken(&self) -> bool { - match self { - FmtToken::Text(text) => text.bytes().any(|b| b == b'\n'), - FmtToken::LineComment(_) | FmtToken::Sep(_) => false, - FmtToken::Block(block) => block.spacing.is_none(), - } - } -} - #[derive(Clone, Copy, PartialEq, Eq, Default, Debug)] pub struct Spacing { pub before: bool, @@ -277,21 +257,40 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { } } - fn add_raw_text(&mut self, text: &'src str) { - self.tokens.push(FmtToken::Text(text)); - self.width += text.len(); - } + // Functions for adding all the token kinds directly; not to be exposed - fn add_raw_space(&mut self) { - self.tokens.push(FmtToken::Text(" ")); - self.width += 1; + fn add_raw_text(&mut self, text: &'src str) { + match text.bytes().filter(|&b| b == b'\n').count() { + 0 => self.width += text.len(), + _ => self.spacing = None, + } + self.tokens.push(FmtToken::Text(text)) } fn add_line_comment(&mut self, comment: &'src str) { - self.tokens.push(FmtToken::LineComment(comment)); self.width += comment.len() + 4; + self.tokens.push(FmtToken::LineComment(comment)) } + fn add_raw_sep(&mut self, n_newlines: u8) { + self.width += self.spacing.map_or(false, |s| s.between) as usize; + self.tokens.push(FmtToken::Sep(n_newlines)) + } + + fn add_raw_block(&mut self, mut block: FmtBlock<'fmt, 'src>) { + if matches!(block.tokens.last(), Some(FmtToken::Sep(_))) { + block.tokens.pop(); + } + match block.spacing { + Some(_) => self.width += block.width, + None => self.spacing = None, + } + self.cur_offset = block.cur_offset; + self.tokens.push(FmtToken::Block(block)) + } + + // Utilities + fn add_comments_with_sep( &mut self, ctx: &FormatCtx<'_, 'src>, @@ -328,7 +327,7 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { } pub fn add_space(&mut self, ctx: &FormatCtx<'_, 'src>, at: LineColumn) -> Result { - self.add_raw_space(); + self.add_raw_text(" "); self.add_comments(ctx, at) } @@ -344,11 +343,6 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { Ok(()) } - fn add_raw_sep(&mut self, n_newlines: u8) { - self.tokens.push(FmtToken::Sep(n_newlines)); - self.width += self.spacing.map_or(false, |s| s.between) as usize; - } - pub fn add_sep(&mut self, ctx: &FormatCtx<'_, 'src>, at: LineColumn) -> Result { self.add_raw_sep(1); self.add_comments_with_sep(ctx, at, |b| b.add_raw_sep(1)) @@ -388,15 +382,7 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { ) -> R { let mut block = Self::new(self.tokens.bump(), spacing, chaining, self.cur_offset); let res = f(&mut block); - if matches!(block.tokens.last(), Some(FmtToken::Sep(_))) { - block.tokens.pop(); - } - match spacing { - Some(_) => self.width += block.width, - None => self.spacing = None, - } - self.cur_offset = block.cur_offset; - self.tokens.push(FmtToken::Block(block)); + self.add_raw_block(block); res } @@ -467,16 +453,19 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { Ok(()) } - pub fn add_source_punctuated( + pub fn add_source_punctuated( &mut self, ctx: &FormatCtx<'_, 'src>, - iter: &Punctuated, - ) -> Result { + iter: &Punctuated, + ) -> Result + where + for<'any> &'any T: Located, + for<'any> &'any P: Located, + { for pair in iter.pairs() { - self.add_source(ctx, pair.value().loc())?; - if let Some(punct) = pair.punct() { - self.add_source(ctx, punct.loc())?; - } + let (value, punct) = pair.into_tuple(); + self.add_source(ctx, value)?; + self.add_source_iter(ctx, punct)?; } Ok(()) } @@ -561,11 +550,6 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { return true; } - if self.tokens.iter().any(FmtToken::is_broken) { - self.force_breaking(ctx, indent); - return true; - } - false } @@ -579,28 +563,26 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { let space_if = |c| if c { Sep::Space } else { Sep::None }; - fn print_token(token: &FmtToken, indent: usize, sep: Sep, cfg: &Config, out: &mut String) { - match token { - FmtToken::Text(text) => out.push_str(text), - FmtToken::LineComment(comment) => { - if let Sep::Newline = sep { - out.push_str("//"); - out.push_str(comment); - print_break(out, 1, indent) - } else { - out.push_str("/*"); - out.push_str(comment); - out.push_str("*/") - } + let print_token = |token: &FmtToken, out: &mut String, indent, sep| match token { + FmtToken::Text(text) => out.push_str(text), + FmtToken::LineComment(comment) => { + if let Sep::Newline = sep { + out.push_str("//"); + out.push_str(comment); + print_break(out, 1, indent) + } else { + out.push_str("/*"); + out.push_str(comment); + out.push_str("*/") } - FmtToken::Sep(n_newlines) => match sep { - Sep::None => (), - Sep::Space => out.push(' '), - Sep::Newline => print_break(out, *n_newlines, indent), - }, - FmtToken::Block(block) => block.print(indent, cfg, out), } - } + FmtToken::Sep(n_newlines) => match sep { + Sep::None => (), + Sep::Space => out.push(' '), + Sep::Newline => print_break(out, *n_newlines, indent), + }, + FmtToken::Block(block) => block.print(indent, cfg, out), + }; if self.tokens.is_empty() { if self.spacing.map_or(false, |s| s.before && s.after) { @@ -611,7 +593,7 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { out.push(' '); } for token in &self.tokens { - print_token(token, indent, space_if(spacing.between), cfg, out); + print_token(token, out, indent, space_if(spacing.between)); } if spacing.after { out.push(' '); @@ -620,7 +602,7 @@ impl<'fmt, 'src> FmtBlock<'fmt, 'src> { let new_indent = indent + cfg.tab_spaces; print_break(out, 1, new_indent); for token in &self.tokens { - print_token(token, new_indent, Sep::Newline, cfg, out); + print_token(token, out, new_indent, Sep::Newline); } if let Some(FmtToken::LineComment(_)) = self.tokens.last() { out.truncate(out.len() - 4) diff --git a/src/html.rs b/src/html.rs index e6c55fc..7c70a43 100644 --- a/src/html.rs +++ b/src/html.rs @@ -122,7 +122,7 @@ pub struct HtmlProp { pub enum HtmlPropKind { Shortcut(Brace, Punctuated), Literal(Punctuated, Token![=], Lit), - Expr(Punctuated, Token![=], Block), + Block(Punctuated, Token![=], Block), } pub struct HtmlIf { @@ -406,7 +406,7 @@ impl Parse for HtmlProp { let name = Punctuated::parse_separated_nonempty(input)?; let eq_token = input.parse()?; if input.peek(Brace) { - HtmlPropKind::Expr(name, eq_token, input.parse()?) + HtmlPropKind::Block(name, eq_token, input.parse()?) } else { HtmlPropKind::Literal(name, eq_token, input.parse()?) } @@ -740,11 +740,10 @@ impl<'src> Format<'src> for HtmlLiteralElement { element_children_spacing(ctx, &self.children), ChainingRule::End, |block, ctx| { - for child in &self.children { + Ok(for child in &self.children { child.format(block, ctx)?; block.add_sep(ctx, child.end())?; - } - Ok(()) + }) }, )?; block.add_source(ctx, self.div_token)?; @@ -759,9 +758,7 @@ impl<'src> Format<'src> for HtmlLiteralElement { impl<'src> Format<'src> for HtmlProp { fn format(&self, block: &mut FmtBlock<'_, 'src>, ctx: &mut FormatCtx<'_, 'src>) -> Result { - if let Some(tilde) = &self.access_spec { - block.add_source(ctx, tilde)?; - } + block.add_source_iter(ctx, self.access_spec)?; match &self.kind { HtmlPropKind::Shortcut(brace, name) => { block.add_source(ctx, brace.span.open())?; @@ -773,25 +770,25 @@ impl<'src> Format<'src> for HtmlProp { block.add_source(ctx, eq)?; block.add_source(ctx, lit) } - HtmlPropKind::Expr(name, eq, expr) => { - if ctx.config.yew.use_prop_init_shorthand - && name.len() == 1 - && matches!(&*expr.stmts, [Stmt::Expr(Expr::Path(p), None)] - if p.path.is_ident(&**name.first().context("prop name is empty")?)) + HtmlPropKind::Block(name, eq, expr) => match &*expr.stmts { + [Stmt::Expr(Expr::Path(p), None)] + if ctx.config.yew.use_prop_init_shorthand + && name.len() == 1 + && p.path.is_ident(&**name.first().context("prop name is empty")?) => { - return expr.format(block, ctx); + expr.format(block, ctx) } - - block.add_source_punctuated(ctx, name)?; - block.add_source(ctx, eq)?; - if ctx.config.yew.unwrap_literal_prop_values - && matches!(&*expr.stmts, [Stmt::Expr(Expr::Lit(_), None)]) - { - block.add_source_iter(ctx, Location::from_slice(&expr.stmts)) - } else { + [Stmt::Expr(Expr::Lit(l), None)] if ctx.config.yew.unwrap_literal_prop_values => { + block.add_source_punctuated(ctx, name)?; + block.add_source(ctx, eq)?; + block.add_source(ctx, l) + } + _ => { + block.add_source_punctuated(ctx, name)?; + block.add_source(ctx, eq)?; expr.format(block, ctx) } - } + }, } } } @@ -799,7 +796,13 @@ impl<'src> Format<'src> for HtmlProp { impl<'src> Format<'src> for Block { fn format(&self, block: &mut FmtBlock<'_, 'src>, ctx: &mut FormatCtx<'_, 'src>) -> Result { block.add_source(ctx, self.brace_token.span.open())?; - block.add_source_iter(ctx, Location::from_slice(&self.stmts))?; + match &*self.stmts { + [] => (), + [only] => block.add_source(ctx, only)?, + [first, .., last] => { + block.add_source(ctx, Location { start: first.start(), end: last.end() })? + } + } block.add_source(ctx, self.brace_token.span.close()) } } @@ -1082,7 +1085,7 @@ impl Located for HtmlProp { match &self.kind { HtmlPropKind::Shortcut(brace, _) => brace.span.open().start(), - HtmlPropKind::Literal(name, ..) | HtmlPropKind::Expr(name, ..) => unsafe { + HtmlPropKind::Literal(name, ..) | HtmlPropKind::Block(name, ..) => unsafe { // Safety: the name of the prop is guaranteed to be non-empty by // `Punctuated::parse_terminated(_nonempty)` name.first().unwrap_unchecked().span().start() @@ -1094,7 +1097,7 @@ impl Located for HtmlProp { match &self.kind { HtmlPropKind::Shortcut(brace, _) => brace.span.close().end(), HtmlPropKind::Literal(_, _, lit) => lit.span().end(), - HtmlPropKind::Expr(_, _, expr) => expr.brace_token.span.close().end(), + HtmlPropKind::Block(_, _, expr) => expr.brace_token.span.close().end(), } } } diff --git a/tests/main.rs b/tests/main.rs index b8329f2..9cfba70 100644 --- a/tests/main.rs +++ b/tests/main.rs @@ -73,6 +73,11 @@ fn issue_16() { cmp("tests/samples/issue_16") } +#[test] +fn issue_17() { + cmp("tests/samples/issue_17") +} + #[test] fn issue_2() { cmp("tests/samples/issue_2") diff --git a/tests/samples/block_in_prop/source.rs b/tests/samples/block_in_prop/source.rs index 85aac24..85fdd4b 100644 --- a/tests/samples/block_in_prop/source.rs +++ b/tests/samples/block_in_prop/source.rs @@ -6,5 +6,7 @@ fn Comp() -> Html { html!(
) + let id = "idk"; id} />); + html!(
) } diff --git a/tests/samples/block_in_prop/target.rs b/tests/samples/block_in_prop/target.rs index ad9511d..891f60f 100644 --- a/tests/samples/block_in_prop/target.rs +++ b/tests/samples/block_in_prop/target.rs @@ -2,5 +2,11 @@ use yew::prelude::*; #[function_component] fn Comp() -> Html { - html!(
) + html!(
); + html!( +
+ ) } diff --git a/tests/samples/elements_broken_up/source.rs b/tests/samples/elements_broken_up/source.rs index 7661f78..05be502 100644 --- a/tests/samples/elements_broken_up/source.rs +++ b/tests/samples/elements_broken_up/source.rs @@ -3,6 +3,7 @@ yew::prelude::*; #[function_component] fn Comp() -> Html { + /* ??? */ html! { <> diff --git a/tests/samples/elements_broken_up/target.rs b/tests/samples/elements_broken_up/target.rs index 8ad1be7..ca62e52 100644 --- a/tests/samples/elements_broken_up/target.rs +++ b/tests/samples/elements_broken_up/target.rs @@ -2,6 +2,7 @@ use yew::prelude::*; #[function_component] fn Comp() -> Html { + /* ??? */ html! { <> diff --git a/tests/samples/issue_17/source.rs b/tests/samples/issue_17/source.rs new file mode 100644 index 0000000..4ec3daf --- /dev/null +++ b/tests/samples/issue_17/source.rs @@ -0,0 +1,14 @@ +use +yew::prelude::*; + +#[function_component] +fn Comp() -> Html { + tml! { +
+ } +} diff --git a/tests/samples/issue_17/target.rs b/tests/samples/issue_17/target.rs new file mode 100644 index 0000000..7ed75f4 --- /dev/null +++ b/tests/samples/issue_17/target.rs @@ -0,0 +1,13 @@ +use yew::prelude::*; + +#[function_component] +fn Comp() -> Html { + tml! { +
+ } +}