From 5b9e34aadd00c5541884fe006f24d1542f374c61 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 24 Jun 2022 14:22:08 -0400 Subject: [PATCH 01/13] feat: add fork of prettyplease for autoformatting --- Cargo.toml | 1 + packages/autofmt/Cargo.toml | 14 + packages/autofmt/README.md | 5 + packages/autofmt/src/lib.rs | 413 +++++++ .../autofmt/src/prettyplease/algorithm.rs | 376 ++++++ packages/autofmt/src/prettyplease/attr.rs | 208 ++++ .../autofmt/src/prettyplease/convenience.rs | 98 ++ packages/autofmt/src/prettyplease/data.rs | 95 ++ packages/autofmt/src/prettyplease/expr.rs | 1028 +++++++++++++++++ packages/autofmt/src/prettyplease/file.rs | 17 + packages/autofmt/src/prettyplease/generics.rs | 280 +++++ packages/autofmt/src/prettyplease/item.rs | 757 ++++++++++++ packages/autofmt/src/prettyplease/iter.rs | 46 + packages/autofmt/src/prettyplease/lifetime.rs | 9 + packages/autofmt/src/prettyplease/lit.rs | 50 + packages/autofmt/src/prettyplease/mac.rs | 220 ++++ packages/autofmt/src/prettyplease/mod.rs | 381 ++++++ packages/autofmt/src/prettyplease/pat.rs | 210 ++++ packages/autofmt/src/prettyplease/path.rs | 181 +++ packages/autofmt/src/prettyplease/ring.rs | 81 ++ packages/autofmt/src/prettyplease/stmt.rs | 85 ++ packages/autofmt/src/prettyplease/token.rs | 80 ++ packages/autofmt/src/prettyplease/ty.rs | 202 ++++ packages/autofmt/tests/sink.rs | 471 ++++++++ 24 files changed, 5308 insertions(+) create mode 100644 packages/autofmt/Cargo.toml create mode 100644 packages/autofmt/README.md create mode 100644 packages/autofmt/src/lib.rs create mode 100644 packages/autofmt/src/prettyplease/algorithm.rs create mode 100644 packages/autofmt/src/prettyplease/attr.rs create mode 100644 packages/autofmt/src/prettyplease/convenience.rs create mode 100644 packages/autofmt/src/prettyplease/data.rs create mode 100644 packages/autofmt/src/prettyplease/expr.rs create mode 100644 packages/autofmt/src/prettyplease/file.rs create mode 100644 packages/autofmt/src/prettyplease/generics.rs create mode 100644 packages/autofmt/src/prettyplease/item.rs create mode 100644 packages/autofmt/src/prettyplease/iter.rs create mode 100644 packages/autofmt/src/prettyplease/lifetime.rs create mode 100644 packages/autofmt/src/prettyplease/lit.rs create mode 100644 packages/autofmt/src/prettyplease/mac.rs create mode 100644 packages/autofmt/src/prettyplease/mod.rs create mode 100644 packages/autofmt/src/prettyplease/pat.rs create mode 100644 packages/autofmt/src/prettyplease/path.rs create mode 100644 packages/autofmt/src/prettyplease/ring.rs create mode 100644 packages/autofmt/src/prettyplease/stmt.rs create mode 100644 packages/autofmt/src/prettyplease/token.rs create mode 100644 packages/autofmt/src/prettyplease/ty.rs create mode 100644 packages/autofmt/tests/sink.rs diff --git a/Cargo.toml b/Cargo.toml index 7db59226c..a4f0b1434 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ members = [ "packages/fermi", "packages/tui", "packages/liveview", + "packages/autofmt", "packages/rsx", "packages/rsx_interpreter", "packages/native-core", diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml new file mode 100644 index 000000000..8fb7a128f --- /dev/null +++ b/packages/autofmt/Cargo.toml @@ -0,0 +1,14 @@ +[package] +name = "dioxus-autofmt" +version = "0.0.0" +edition = "2021" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] +proc-macro2 = { version = "1.0.6", features = ["span-locations"] } +quote = "1.0" +syn = { version = "1.0.11", features = ["full", "extra-traits"] } +dioxus-rsx = { path = "../rsx" } +triple_accel = "0.4.0" +serde = { version = "1.0.136", features = ["derive"] } diff --git a/packages/autofmt/README.md b/packages/autofmt/README.md new file mode 100644 index 000000000..9fded3260 --- /dev/null +++ b/packages/autofmt/README.md @@ -0,0 +1,5 @@ +# This crate autofmts blocks of rsx! + +This crate formats rsx! by parsing call bodies and pretty-printing them back out. + +It also incorporates a fork of prettyplease to allow formatting arbitrary rust code too. Prettyplease rejected a suggestion to allow arbitrary expression formatting - something our fork lets us do. diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs new file mode 100644 index 000000000..4f467e139 --- /dev/null +++ b/packages/autofmt/src/lib.rs @@ -0,0 +1,413 @@ +//! pretty printer for rsx! +use dioxus_rsx::*; +use proc_macro2::TokenStream as TokenStream2; +use quote::ToTokens; +use std::{ + fmt::{self, Write}, + ptr::NonNull, +}; +use syn::{ + buffer::TokenBuffer, + parse::{ParseBuffer, ParseStream}, +}; +use triple_accel::{levenshtein_search, Match}; + +mod prettyplease; + +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)] +pub struct FormattedBlock { + pub formatted: String, + pub start: usize, + pub end: usize, +} + +/* + + +*/ +pub fn get_format_blocks(contents: &str) -> Vec { + let mut matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable(); + + let mut cur_match: Option = None; + + let mut formatted_blocks = Vec::new(); + + let mut last_bracket_end = 0; + + for item in matches { + let Match { start, end, k } = item; + + if start < last_bracket_end { + continue; + } + + let remaining = &contents[end - 1..]; + + if let Some(bracket_end) = find_bracket_end(remaining) { + let sub_string = &contents[end..bracket_end + end - 1]; + + last_bracket_end = bracket_end + end - 1; + + // with the edge brackets + // println!("{}", &contents[end - 1..bracket_end + end]); + + if let Some(new) = fmt_block(sub_string) { + if !new.is_empty() { + println!("{}", &contents[end + 1..bracket_end + end - 1]); + println!("{}", new); + + let stripped = &contents[end + 1..bracket_end + end - 1]; + if stripped == new { + println!("no changes necessary"); + } + + // if we have code to push, we want the code to end up on the right lines with the right indentation + + let mut output = String::new(); + writeln!(output).unwrap(); + + for line in new.lines() { + writeln!(output, " {}", line).ok(); + } + + formatted_blocks.push(FormattedBlock { + formatted: output, + start: end, + end: end + bracket_end - 1, + }); + } + } else { + panic!("failed to format block: {}", sub_string); + } + } else { + panic!("failed to find end of block: {}", remaining); + } + } + + formatted_blocks +} + +pub fn fmt_block(block: &str) -> Option { + let mut raw_lines = block.split('\n').collect::>(); + + let parsed: CallBody = syn::parse_str(block).ok()?; + + let mut buf = String::new(); + + for node in parsed.roots.iter() { + write_ident(&mut buf, &raw_lines, node, 0).ok()?; + } + + Some(buf) +} + +pub fn write_ident( + buf: &mut String, + lines: &[&str], + node: &BodyNode, + indent: usize, +) -> fmt::Result { + match node { + BodyNode::Element(el) => { + let Element { + name, + key, + attributes, + children, + _is_static, + } = el; + + write_tabs(buf, indent)?; + write!(buf, "{name} {{")?; + + let total_attr_len = attributes + .iter() + .map(|attr| match &attr.attr { + ElementAttr::AttrText { name, value } => value.value().len(), + ElementAttr::AttrExpression { name, value } => 10, + ElementAttr::CustomAttrText { name, value } => value.value().len(), + ElementAttr::CustomAttrExpression { name, value } => 10, + ElementAttr::EventTokens { name, tokens } => 1000000, + ElementAttr::Meta(_) => todo!(), + }) + .sum::(); + + let is_long_attr_list = total_attr_len > 80; + + if let Some(key) = key { + let key = key.value(); + if is_long_attr_list { + write_tabs(buf, indent + 1)?; + } else { + write!(buf, " ")?; + } + write!(buf, "key: \"{key}\"")?; + + if !attributes.is_empty() { + writeln!(buf, ",")?; + } + } + + let mut attr_iter = attributes.iter().peekable(); + while let Some(attr) = attr_iter.next() { + if is_long_attr_list { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + } else { + write!(buf, " ")?; + } + + match &attr.attr { + ElementAttr::AttrText { name, value } => { + write!(buf, "{name}: \"{value}\"", value = value.value())?; + } + ElementAttr::AttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "{}: {}", name, out)?; + } + + ElementAttr::CustomAttrText { name, value } => { + write!( + buf, + "\"{name}\": \"{value}\"", + name = name.value(), + value = value.value() + )?; + } + + ElementAttr::CustomAttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "\"{}\": {}", name.value(), out)?; + } + + ElementAttr::EventTokens { name, tokens } => { + let out = prettyplease::unparse_expr(tokens); + + dbg!(&out); + + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(buf, "{}: {}", name, first)?; + } else { + writeln!(buf, "{}: {}", name, first)?; + + while let Some(line) = lines.next() { + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + if lines.peek().is_none() { + write!(buf, "")?; + } else { + writeln!(buf)?; + } + } + } + } + ElementAttr::Meta(_) => {} + } + + if attr_iter.peek().is_some() || !children.is_empty() { + write!(buf, ",")?; + } + } + + if children.len() == 1 && children[0].is_litstr() && !is_long_attr_list { + if let BodyNode::Text(text) = &children[0] { + let text_val = text.value(); + if total_attr_len + text_val.len() > 80 { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + writeln!(buf, "\"{}\"", text.value())?; + write_tabs(buf, indent)?; + } else { + write!(buf, " \"{}\" ", text.value())?; + } + } + } else { + if is_long_attr_list || !children.is_empty() { + writeln!(buf)?; + } + + for child in children { + write_ident(buf, lines, child, indent + 1)?; + } + + if is_long_attr_list || !children.is_empty() { + write_tabs(buf, indent)?; + } else { + write!(buf, " ")?; + } + } + + writeln!(buf, "}}")?; + } + BodyNode::Component(component) => { + let Component { + name, + body, + children, + manual_props, + } = component; + + let mut name = name.to_token_stream().to_string(); + name.retain(|c| !c.is_whitespace()); + + write_tabs(buf, indent)?; + write!(buf, "{name} {{")?; + + if !body.is_empty() || !children.is_empty() { + writeln!(buf)?; + } + + for field in body { + write_tabs(buf, indent + 1)?; + let name = &field.name; + match &field.content { + ContentField::ManExpr(exp) => { + let out = prettyplease::unparse_expr(exp); + writeln!(buf, "{}: {},", name, out)?; + } + ContentField::Formatted(s) => { + writeln!(buf, "{}: \"{}\",", name, s.value())?; + } + ContentField::OnHandlerRaw(exp) => { + let out = prettyplease::unparse_expr(exp); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(buf, "{}: {}", name, first)?; + for line in lines { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + } + writeln!(buf, ",")?; + } + } + } + + if let Some(exp) = manual_props { + write_tabs(buf, indent + 1)?; + let out = prettyplease::unparse_expr(exp); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(buf, "..{}", first)?; + for line in lines { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + } + writeln!(buf)?; + } + + for child in children { + write_ident(buf, lines, child, indent + 1)?; + } + + if !body.is_empty() || !children.is_empty() { + write_tabs(buf, indent)?; + } + + writeln!(buf, "}}")?; + + // + // write!(buf, "{}", " ".repeat(ident)) + } + BodyNode::Text(t) => { + // + // write!(buf, "{}", " ".repeat(ident)) + write_tabs(buf, indent)?; + writeln!(buf, "\"{}\"", t.value())?; + } + BodyNode::RawExpr(exp) => { + use syn::spanned::Spanned; + + let placement = exp.span(); + + let start = placement.start(); + let end = placement.end(); + + let num_spaces_desired = (indent * 4) as isize; + let first = lines[start.line - 1]; + let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; + let offset = num_spaces_real - num_spaces_desired; + + for line_id in start.line - 1..end.line { + let line = lines[line_id]; + + // trim the leading whitespace + + if offset < 0 { + for _ in 0..-offset { + write!(buf, " ")?; + } + + writeln!(buf, "{}", line)?; + } else { + let offset = offset as usize; + + let right = &line[offset..]; + writeln!(buf, "{}", right)?; + } + } + + // let toks = exp.to_token_stream(); + + // let out = prettyplease::unparse_expr(exp); + // let mut lines = out.split('\n').peekable(); + // for line in lines { + // write_tabs(buf, indent)?; + // writeln!(buf, "{}", line)?; + // // writeln!(buf)?; + // } + // write_tabs(buf, indent)?; + // let first = lines.next().unwrap(); + // write!(buf, "{}", name, first)?; + // writeln!(buf)?; + + // + // write!(buf, "{}", " ".repeat(ident)) + } + BodyNode::Meta(att) => { + // + // if att.path.segments.last().unwrap().ident == "doc" { + let val = att.to_string(); + write_tabs(buf, indent)?; + writeln!(buf, "{}", val)?; + // } + // match att {} + } + } + + Ok(()) +} + +pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result { + for _ in 0..num { + write!(f, " ")? + } + Ok(()) +} + +fn find_bracket_end(contents: &str) -> Option { + let mut depth = 0; + let mut i = 0; + + for c in contents.chars() { + if c == '{' { + depth += 1; + } else if c == '}' { + depth -= 1; + } + + if depth == 0 { + return Some(i); + } + + i += 1; + } + + None +} diff --git a/packages/autofmt/src/prettyplease/algorithm.rs b/packages/autofmt/src/prettyplease/algorithm.rs new file mode 100644 index 000000000..b70bd57ea --- /dev/null +++ b/packages/autofmt/src/prettyplease/algorithm.rs @@ -0,0 +1,376 @@ +// Adapted from https://github.com/rust-lang/rust/blob/1.57.0/compiler/rustc_ast_pretty/src/pp.rs. +// See "Algorithm notes" in the crate-level rustdoc. + +use super::ring::RingBuffer; +use super::{MARGIN, MIN_SPACE}; +use std::borrow::Cow; +use std::cmp; +use std::collections::VecDeque; +use std::iter; + +#[derive(Clone, Copy, PartialEq)] +pub enum Breaks { + Consistent, + Inconsistent, +} + +#[derive(Clone, Copy, Default)] +pub struct BreakToken { + pub offset: isize, + pub blank_space: usize, + pub pre_break: Option, + pub post_break: Option, + pub no_break: Option, + pub if_nonempty: bool, + pub never_break: bool, +} + +#[derive(Clone, Copy)] +pub struct BeginToken { + pub offset: isize, + pub breaks: Breaks, +} + +#[derive(Clone)] +pub enum Token { + String(Cow<'static, str>), + Break(BreakToken), + Begin(BeginToken), + End, +} + +#[derive(Copy, Clone)] +enum PrintFrame { + Fits(Breaks), + Broken(usize, Breaks), +} + +pub const SIZE_INFINITY: isize = 0xffff; + +pub struct Printer { + out: String, + // Number of spaces left on line + space: isize, + // Ring-buffer of tokens and calculated sizes + buf: RingBuffer, + // Total size of tokens already printed + left_total: isize, + // Total size of tokens enqueued, including printed and not yet printed + right_total: isize, + // Holds the ring-buffer index of the Begin that started the current block, + // possibly with the most recent Break after that Begin (if there is any) on + // top of it. Values are pushed and popped on the back of the queue using it + // like stack, and elsewhere old values are popped from the front of the + // queue as they become irrelevant due to the primary ring-buffer advancing. + scan_stack: VecDeque, + // Stack of blocks-in-progress being flushed by print + print_stack: Vec, + // Level of indentation of current line + indent: usize, + // Buffered indentation to avoid writing trailing whitespace + pending_indentation: usize, +} + +#[derive(Clone)] +struct BufEntry { + token: Token, + size: isize, +} + +impl Printer { + pub fn new() -> Self { + Printer { + out: String::new(), + space: MARGIN, + buf: RingBuffer::new(), + left_total: 0, + right_total: 0, + scan_stack: VecDeque::new(), + print_stack: Vec::new(), + indent: 0, + pending_indentation: 0, + } + } + + pub fn eof(mut self) -> String { + if !self.scan_stack.is_empty() { + self.check_stack(0); + self.advance_left(); + } + self.out + } + + pub fn scan_begin(&mut self, token: BeginToken) { + if self.scan_stack.is_empty() { + self.left_total = 1; + self.right_total = 1; + self.buf.clear(); + } + let right = self.buf.push(BufEntry { + token: Token::Begin(token), + size: -self.right_total, + }); + self.scan_stack.push_back(right); + } + + pub fn scan_end(&mut self) { + if self.scan_stack.is_empty() { + self.print_end(); + } else { + if !self.buf.is_empty() { + if let Token::Break(break_token) = self.buf.last().token { + if self.buf.len() >= 2 { + if let Token::Begin(_) = self.buf.second_last().token { + self.buf.pop_last(); + self.buf.pop_last(); + self.scan_stack.pop_back(); + self.scan_stack.pop_back(); + self.right_total -= break_token.blank_space as isize; + return; + } + } + if break_token.if_nonempty { + self.buf.pop_last(); + self.scan_stack.pop_back(); + self.right_total -= break_token.blank_space as isize; + } + } + } + let right = self.buf.push(BufEntry { + token: Token::End, + size: -1, + }); + self.scan_stack.push_back(right); + } + } + + pub fn scan_break(&mut self, token: BreakToken) { + if self.scan_stack.is_empty() { + self.left_total = 1; + self.right_total = 1; + self.buf.clear(); + } else { + self.check_stack(0); + } + let right = self.buf.push(BufEntry { + token: Token::Break(token), + size: -self.right_total, + }); + self.scan_stack.push_back(right); + self.right_total += token.blank_space as isize; + } + + pub fn scan_string(&mut self, string: Cow<'static, str>) { + if self.scan_stack.is_empty() { + self.print_string(string); + } else { + let len = string.len() as isize; + self.buf.push(BufEntry { + token: Token::String(string), + size: len, + }); + self.right_total += len; + self.check_stream(); + } + } + + pub fn offset(&mut self, offset: isize) { + match &mut self.buf.last_mut().token { + Token::Break(token) => token.offset += offset, + Token::Begin(_) => {} + Token::String(_) | Token::End => unreachable!(), + } + } + + pub fn end_with_max_width(&mut self, max: isize) { + let mut depth = 1; + for &index in self.scan_stack.iter().rev() { + let entry = &self.buf[index]; + match entry.token { + Token::Begin(_) => { + depth -= 1; + if depth == 0 { + if entry.size < 0 { + let actual_width = entry.size + self.right_total; + if actual_width > max { + self.buf.push(BufEntry { + token: Token::String(Cow::Borrowed("")), + size: SIZE_INFINITY, + }); + self.right_total += SIZE_INFINITY; + } + } + break; + } + } + Token::End => depth += 1, + Token::Break(_) => {} + Token::String(_) => unreachable!(), + } + } + self.scan_end(); + } + + fn check_stream(&mut self) { + while self.right_total - self.left_total > self.space { + if *self.scan_stack.front().unwrap() == self.buf.index_of_first() { + self.scan_stack.pop_front().unwrap(); + self.buf.first_mut().size = SIZE_INFINITY; + } + + self.advance_left(); + + if self.buf.is_empty() { + break; + } + } + } + + fn advance_left(&mut self) { + while self.buf.first().size >= 0 { + let left = self.buf.pop_first(); + + match left.token { + Token::String(string) => { + self.left_total += left.size; + self.print_string(string); + } + Token::Break(token) => { + self.left_total += token.blank_space as isize; + self.print_break(token, left.size); + } + Token::Begin(token) => self.print_begin(token, left.size), + Token::End => self.print_end(), + } + + if self.buf.is_empty() { + break; + } + } + } + + fn check_stack(&mut self, mut depth: usize) { + while let Some(&index) = self.scan_stack.back() { + let mut entry = &mut self.buf[index]; + match entry.token { + Token::Begin(_) => { + if depth == 0 { + break; + } + self.scan_stack.pop_back().unwrap(); + entry.size += self.right_total; + depth -= 1; + } + Token::End => { + self.scan_stack.pop_back().unwrap(); + entry.size = 1; + depth += 1; + } + Token::Break(_) => { + self.scan_stack.pop_back().unwrap(); + entry.size += self.right_total; + if depth == 0 { + break; + } + } + Token::String(_) => unreachable!(), + } + } + } + + fn get_top(&self) -> PrintFrame { + const OUTER: PrintFrame = PrintFrame::Broken(0, Breaks::Inconsistent); + self.print_stack.last().map_or(OUTER, PrintFrame::clone) + } + + fn print_begin(&mut self, token: BeginToken, size: isize) { + if cfg!(prettyplease_debug) { + self.out.push(match token.breaks { + Breaks::Consistent => '«', + Breaks::Inconsistent => '‹', + }); + if cfg!(prettyplease_debug_indent) { + self.out + .extend(token.offset.to_string().chars().map(|ch| match ch { + '0'..='9' => ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'] + [(ch as u8 - b'0') as usize] + as char, + '-' => '₋', + _ => unreachable!(), + })); + } + } + if size > self.space { + self.print_stack + .push(PrintFrame::Broken(self.indent, token.breaks)); + self.indent = usize::try_from(self.indent as isize + token.offset).unwrap(); + } else { + self.print_stack.push(PrintFrame::Fits(token.breaks)); + } + } + + fn print_end(&mut self) { + let breaks = match self.print_stack.pop().unwrap() { + PrintFrame::Broken(indent, breaks) => { + self.indent = indent; + breaks + } + PrintFrame::Fits(breaks) => breaks, + }; + if cfg!(prettyplease_debug) { + self.out.push(match breaks { + Breaks::Consistent => '»', + Breaks::Inconsistent => '›', + }); + } + } + + fn print_break(&mut self, token: BreakToken, size: isize) { + let fits = token.never_break + || match self.get_top() { + PrintFrame::Fits(..) => true, + PrintFrame::Broken(.., Breaks::Consistent) => false, + PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, + }; + if fits { + self.pending_indentation += token.blank_space; + self.space -= token.blank_space as isize; + if let Some(no_break) = token.no_break { + self.out.push(no_break); + self.space -= no_break.len_utf8() as isize; + } + if cfg!(prettyplease_debug) { + self.out.push('·'); + } + } else { + if let Some(pre_break) = token.pre_break { + self.out.push(pre_break); + } + if cfg!(prettyplease_debug) { + self.out.push('·'); + } + self.out.push('\n'); + let indent = self.indent as isize + token.offset; + self.pending_indentation = usize::try_from(indent).unwrap(); + self.space = cmp::max(MARGIN - indent, MIN_SPACE); + if let Some(post_break) = token.post_break { + self.print_indent(); + self.out.push(post_break); + self.space -= post_break.len_utf8() as isize; + } + } + } + + fn print_string(&mut self, string: Cow<'static, str>) { + self.print_indent(); + self.out.push_str(&string); + self.space -= string.len() as isize; + } + + fn print_indent(&mut self) { + self.out.reserve(self.pending_indentation); + self.out + .extend(iter::repeat(' ').take(self.pending_indentation)); + self.pending_indentation = 0; + } +} diff --git a/packages/autofmt/src/prettyplease/attr.rs b/packages/autofmt/src/prettyplease/attr.rs new file mode 100644 index 000000000..d934a1c1f --- /dev/null +++ b/packages/autofmt/src/prettyplease/attr.rs @@ -0,0 +1,208 @@ +use super::algorithm::Printer; +use super::INDENT; +use proc_macro2::{Delimiter, TokenStream, TokenTree}; +use syn::{AttrStyle, Attribute, Lit, PathArguments}; + +impl Printer { + pub fn outer_attrs(&mut self, attrs: &[Attribute]) { + for attr in attrs { + if let AttrStyle::Outer = attr.style { + self.attr(attr); + } + } + } + + pub fn inner_attrs(&mut self, attrs: &[Attribute]) { + for attr in attrs { + if let AttrStyle::Inner(_) = attr.style { + self.attr(attr); + } + } + } + + fn attr(&mut self, attr: &Attribute) { + if let Some(doc) = value_of_attribute("doc", attr) { + if doc.contains('\n') { + self.word(match attr.style { + AttrStyle::Outer => "/**", + AttrStyle::Inner(_) => "/*!", + }); + self.word(doc); + self.word("*/"); + } else { + self.word(match attr.style { + AttrStyle::Outer => "///", + AttrStyle::Inner(_) => "//!", + }); + self.word(doc); + } + self.hardbreak(); + } else if let Some(comment) = value_of_attribute("comment", attr) { + if comment.contains('\n') { + self.word("/*"); + self.word(comment); + self.word("*/"); + } else { + self.word("//"); + self.word(comment); + } + self.hardbreak(); + } else { + self.word(match attr.style { + AttrStyle::Outer => "#", + AttrStyle::Inner(_) => "#!", + }); + self.word("["); + self.path(&attr.path); + self.attr_tokens(attr.tokens.clone()); + self.word("]"); + self.space(); + } + } + + fn attr_tokens(&mut self, tokens: TokenStream) { + let mut stack = Vec::new(); + stack.push((tokens.into_iter().peekable(), Delimiter::None)); + let mut space = Self::nbsp as fn(&mut Self); + + #[derive(PartialEq)] + enum State { + Word, + Punct, + TrailingComma, + } + + use State::*; + let mut state = Word; + + while let Some((tokens, delimiter)) = stack.last_mut() { + match tokens.next() { + Some(TokenTree::Ident(ident)) => { + if let Word = state { + space(self); + } + self.ident(&ident); + state = Word; + } + Some(TokenTree::Punct(punct)) => { + let ch = punct.as_char(); + if let (Word, '=') = (state, ch) { + self.nbsp(); + } + if ch == ',' && tokens.peek().is_none() { + self.trailing_comma(true); + state = TrailingComma; + } else { + self.token_punct(ch); + if ch == '=' { + self.nbsp(); + } else if ch == ',' { + space(self); + } + state = Punct; + } + } + Some(TokenTree::Literal(literal)) => { + if let Word = state { + space(self); + } + self.token_literal(&literal); + state = Word; + } + Some(TokenTree::Group(group)) => { + let delimiter = group.delimiter(); + let stream = group.stream(); + match delimiter { + Delimiter::Parenthesis => { + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + state = Punct; + } + Delimiter::Brace => { + self.word("{"); + state = Punct; + } + Delimiter::Bracket => { + self.word("["); + state = Punct; + } + Delimiter::None => {} + } + stack.push((stream.into_iter().peekable(), delimiter)); + space = Self::space; + } + None => { + match delimiter { + Delimiter::Parenthesis => { + if state != TrailingComma { + self.zerobreak(); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + state = Punct; + } + Delimiter::Brace => { + self.word("}"); + state = Punct; + } + Delimiter::Bracket => { + self.word("]"); + state = Punct; + } + Delimiter::None => {} + } + stack.pop(); + if stack.is_empty() { + space = Self::nbsp; + } + } + } + } + } +} + +fn value_of_attribute(requested: &str, attr: &Attribute) -> Option { + let is_doc = attr.path.leading_colon.is_none() + && attr.path.segments.len() == 1 + && matches!(attr.path.segments[0].arguments, PathArguments::None) + && attr.path.segments[0].ident == requested; + if !is_doc { + return None; + } + let mut tokens = attr.tokens.clone().into_iter(); + match tokens.next() { + Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => {} + _ => return None, + } + let literal = match tokens.next() { + Some(TokenTree::Literal(literal)) => literal, + _ => return None, + }; + if tokens.next().is_some() { + return None; + } + match Lit::new(literal) { + Lit::Str(string) => Some(string.value()), + _ => None, + } +} + +pub fn has_outer(attrs: &[Attribute]) -> bool { + for attr in attrs { + if let AttrStyle::Outer = attr.style { + return true; + } + } + false +} + +pub fn has_inner(attrs: &[Attribute]) -> bool { + for attr in attrs { + if let AttrStyle::Inner(_) = attr.style { + return true; + } + } + false +} diff --git a/packages/autofmt/src/prettyplease/convenience.rs b/packages/autofmt/src/prettyplease/convenience.rs new file mode 100644 index 000000000..f87206320 --- /dev/null +++ b/packages/autofmt/src/prettyplease/convenience.rs @@ -0,0 +1,98 @@ +use super::algorithm::{self, BeginToken, BreakToken, Breaks, Printer}; +use std::borrow::Cow; + +impl Printer { + pub fn ibox(&mut self, indent: isize) { + self.scan_begin(BeginToken { + offset: indent, + breaks: Breaks::Inconsistent, + }); + } + + pub fn cbox(&mut self, indent: isize) { + self.scan_begin(BeginToken { + offset: indent, + breaks: Breaks::Consistent, + }); + } + + pub fn end(&mut self) { + self.scan_end(); + } + + pub fn word>>(&mut self, wrd: S) { + let s = wrd.into(); + self.scan_string(s); + } + + fn spaces(&mut self, n: usize) { + self.scan_break(BreakToken { + blank_space: n, + ..BreakToken::default() + }); + } + + pub fn zerobreak(&mut self) { + self.spaces(0); + } + + pub fn space(&mut self) { + self.spaces(1); + } + + pub fn nbsp(&mut self) { + self.word(" "); + } + + pub fn hardbreak(&mut self) { + self.spaces(algorithm::SIZE_INFINITY as usize); + } + + pub fn space_if_nonempty(&mut self) { + self.scan_break(BreakToken { + blank_space: 1, + if_nonempty: true, + ..BreakToken::default() + }); + } + + pub fn hardbreak_if_nonempty(&mut self) { + self.scan_break(BreakToken { + blank_space: algorithm::SIZE_INFINITY as usize, + if_nonempty: true, + ..BreakToken::default() + }); + } + + pub fn trailing_comma(&mut self, is_last: bool) { + if is_last { + self.scan_break(BreakToken { + pre_break: Some(','), + ..BreakToken::default() + }); + } else { + self.word(","); + self.space(); + } + } + + pub fn trailing_comma_or_space(&mut self, is_last: bool) { + if is_last { + self.scan_break(BreakToken { + blank_space: 1, + pre_break: Some(','), + ..BreakToken::default() + }); + } else { + self.word(","); + self.space(); + } + } + + pub fn neverbreak(&mut self) { + self.scan_break(BreakToken { + never_break: true, + ..BreakToken::default() + }); + } +} diff --git a/packages/autofmt/src/prettyplease/data.rs b/packages/autofmt/src/prettyplease/data.rs new file mode 100644 index 000000000..be20eae05 --- /dev/null +++ b/packages/autofmt/src/prettyplease/data.rs @@ -0,0 +1,95 @@ +use super::algorithm::Printer; +use super::iter::IterDelimited; +use super::INDENT; +use syn::{ + Field, Fields, FieldsUnnamed, PathArguments, Variant, VisCrate, VisPublic, VisRestricted, + Visibility, +}; + +impl Printer { + pub fn variant(&mut self, variant: &Variant) { + self.outer_attrs(&variant.attrs); + self.ident(&variant.ident); + match &variant.fields { + Fields::Named(fields) => { + self.nbsp(); + self.word("{"); + self.cbox(INDENT); + self.space(); + for field in fields.named.iter().delimited() { + self.field(&field); + self.trailing_comma_or_space(field.is_last); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + Fields::Unnamed(fields) => { + self.cbox(INDENT); + self.fields_unnamed(fields); + self.end(); + } + Fields::Unit => {} + } + if let Some((_eq_token, discriminant)) = &variant.discriminant { + self.word(" = "); + self.expr(discriminant); + } + } + + pub fn fields_unnamed(&mut self, fields: &FieldsUnnamed) { + self.word("("); + self.zerobreak(); + for field in fields.unnamed.iter().delimited() { + self.field(&field); + self.trailing_comma(field.is_last); + } + self.offset(-INDENT); + self.word(")"); + } + + pub fn field(&mut self, field: &Field) { + self.outer_attrs(&field.attrs); + self.visibility(&field.vis); + if let Some(ident) = &field.ident { + self.ident(ident); + self.word(": "); + } + self.ty(&field.ty); + } + + pub fn visibility(&mut self, vis: &Visibility) { + match vis { + Visibility::Public(vis) => self.vis_public(vis), + Visibility::Crate(vis) => self.vis_crate(vis), + Visibility::Restricted(vis) => self.vis_restricted(vis), + Visibility::Inherited => {} + } + } + + fn vis_public(&mut self, vis: &VisPublic) { + let _ = vis; + self.word("pub "); + } + + fn vis_crate(&mut self, vis: &VisCrate) { + let _ = vis; + self.word("crate "); + } + + fn vis_restricted(&mut self, vis: &VisRestricted) { + self.word("pub("); + let omit_in = vis.path.leading_colon.is_none() + && vis.path.segments.len() == 1 + && matches!(vis.path.segments[0].arguments, PathArguments::None) + && matches!( + vis.path.segments[0].ident.to_string().as_str(), + "self" | "super" | "crate", + ); + if !omit_in { + self.word("in "); + } + self.path(&vis.path); + self.word(") "); + } +} diff --git a/packages/autofmt/src/prettyplease/expr.rs b/packages/autofmt/src/prettyplease/expr.rs new file mode 100644 index 000000000..e659118a4 --- /dev/null +++ b/packages/autofmt/src/prettyplease/expr.rs @@ -0,0 +1,1028 @@ +use super::algorithm::{BreakToken, Printer}; +use super::attr; +use super::iter::IterDelimited; +use super::stmt; +use super::INDENT; +use proc_macro2::TokenStream; +use syn::punctuated::Punctuated; +use syn::{ + token, Arm, Attribute, BinOp, Block, Expr, ExprArray, ExprAssign, ExprAssignOp, ExprAsync, + ExprAwait, ExprBinary, ExprBlock, ExprBox, ExprBreak, ExprCall, ExprCast, ExprClosure, + ExprContinue, ExprField, ExprForLoop, ExprGroup, ExprIf, ExprIndex, ExprLet, ExprLit, ExprLoop, + ExprMacro, ExprMatch, ExprMethodCall, ExprParen, ExprPath, ExprRange, ExprReference, + ExprRepeat, ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprTuple, ExprType, ExprUnary, + ExprUnsafe, ExprWhile, ExprYield, FieldValue, GenericMethodArgument, Index, Label, Member, + MethodTurbofish, PathArguments, RangeLimits, ReturnType, Stmt, Token, UnOp, +}; + +impl Printer { + pub fn expr(&mut self, expr: &Expr) { + match expr { + Expr::Array(expr) => self.expr_array(expr), + Expr::Assign(expr) => self.expr_assign(expr), + Expr::AssignOp(expr) => self.expr_assign_op(expr), + Expr::Async(expr) => self.expr_async(expr), + Expr::Await(expr) => self.expr_await(expr, false), + Expr::Binary(expr) => self.expr_binary(expr), + Expr::Block(expr) => self.expr_block(expr), + Expr::Box(expr) => self.expr_box(expr), + Expr::Break(expr) => self.expr_break(expr), + Expr::Call(expr) => self.expr_call(expr, false), + Expr::Cast(expr) => self.expr_cast(expr), + Expr::Closure(expr) => self.expr_closure(expr), + Expr::Continue(expr) => self.expr_continue(expr), + Expr::Field(expr) => self.expr_field(expr, false), + Expr::ForLoop(expr) => self.expr_for_loop(expr), + Expr::Group(expr) => self.expr_group(expr), + Expr::If(expr) => self.expr_if(expr), + Expr::Index(expr) => self.expr_index(expr, false), + Expr::Let(expr) => self.expr_let(expr), + Expr::Lit(expr) => self.expr_lit(expr), + Expr::Loop(expr) => self.expr_loop(expr), + Expr::Macro(expr) => self.expr_macro(expr), + Expr::Match(expr) => self.expr_match(expr), + Expr::MethodCall(expr) => self.expr_method_call(expr, false), + Expr::Paren(expr) => self.expr_paren(expr), + Expr::Path(expr) => self.expr_path(expr), + Expr::Range(expr) => self.expr_range(expr), + Expr::Reference(expr) => self.expr_reference(expr), + Expr::Repeat(expr) => self.expr_repeat(expr), + Expr::Return(expr) => self.expr_return(expr), + Expr::Struct(expr) => self.expr_struct(expr), + Expr::Try(expr) => self.expr_try(expr, false), + Expr::TryBlock(expr) => self.expr_try_block(expr), + Expr::Tuple(expr) => self.expr_tuple(expr), + Expr::Type(expr) => self.expr_type(expr), + Expr::Unary(expr) => self.expr_unary(expr), + Expr::Unsafe(expr) => self.expr_unsafe(expr), + Expr::Verbatim(expr) => self.expr_verbatim(expr), + Expr::While(expr) => self.expr_while(expr), + Expr::Yield(expr) => self.expr_yield(expr), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Expr"), + } + } + + pub fn expr_beginning_of_line(&mut self, expr: &Expr, beginning_of_line: bool) { + match expr { + Expr::Await(expr) => self.expr_await(expr, beginning_of_line), + Expr::Field(expr) => self.expr_field(expr, beginning_of_line), + Expr::Index(expr) => self.expr_index(expr, beginning_of_line), + Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line), + Expr::Try(expr) => self.expr_try(expr, beginning_of_line), + _ => self.expr(expr), + } + } + + fn subexpr(&mut self, expr: &Expr, beginning_of_line: bool) { + match expr { + Expr::Await(expr) => self.subexpr_await(expr, beginning_of_line), + Expr::Call(expr) => self.subexpr_call(expr), + Expr::Field(expr) => self.subexpr_field(expr, beginning_of_line), + Expr::Index(expr) => self.subexpr_index(expr, beginning_of_line), + Expr::MethodCall(expr) => self.subexpr_method_call(expr, beginning_of_line, false), + Expr::Try(expr) => self.subexpr_try(expr, beginning_of_line), + _ => { + self.cbox(-INDENT); + self.expr(expr); + self.end(); + } + } + } + + // If the given expression is a bare `ExprStruct`, wraps it in parenthesis + // before appending it to `TokenStream`. + fn wrap_exterior_struct(&mut self, expr: &Expr) { + let needs_paren = contains_exterior_struct_lit(expr); + if needs_paren { + self.word("("); + } + self.cbox(0); + self.expr(expr); + if needs_paren { + self.word(")"); + } + if needs_newline_if_wrap(expr) { + self.space(); + } else { + self.nbsp(); + } + self.end(); + } + + fn expr_array(&mut self, expr: &ExprArray) { + self.outer_attrs(&expr.attrs); + self.word("["); + self.cbox(INDENT); + self.zerobreak(); + for element in expr.elems.iter().delimited() { + self.expr(&element); + self.trailing_comma(element.is_last); + } + self.offset(-INDENT); + self.end(); + self.word("]"); + } + + fn expr_assign(&mut self, expr: &ExprAssign) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + self.expr(&expr.left); + self.word(" = "); + self.expr(&expr.right); + self.end(); + } + + fn expr_assign_op(&mut self, expr: &ExprAssignOp) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.left); + self.end(); + self.space(); + self.binary_operator(&expr.op); + self.nbsp(); + self.expr(&expr.right); + self.end(); + } + + fn expr_async(&mut self, expr: &ExprAsync) { + self.outer_attrs(&expr.attrs); + self.word("async "); + if expr.capture.is_some() { + self.word("move "); + } + self.cbox(INDENT); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + + fn expr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.subexpr_await(expr, beginning_of_line); + self.end(); + } + + fn subexpr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) { + self.subexpr(&expr.base, beginning_of_line); + self.zerobreak_unless_short_ident(beginning_of_line, &expr.base); + self.word(".await"); + } + + fn expr_binary(&mut self, expr: &ExprBinary) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.left); + self.end(); + self.space(); + self.binary_operator(&expr.op); + self.nbsp(); + self.expr(&expr.right); + self.end(); + } + + pub fn expr_block(&mut self, expr: &ExprBlock) { + self.outer_attrs(&expr.attrs); + if let Some(label) = &expr.label { + self.label(label); + } + self.cbox(INDENT); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + + fn expr_box(&mut self, expr: &ExprBox) { + self.outer_attrs(&expr.attrs); + self.word("box "); + self.expr(&expr.expr); + } + + fn expr_break(&mut self, expr: &ExprBreak) { + self.outer_attrs(&expr.attrs); + self.word("break"); + if let Some(lifetime) = &expr.label { + self.nbsp(); + self.lifetime(lifetime); + } + if let Some(value) = &expr.expr { + self.nbsp(); + self.expr(value); + } + } + + fn expr_call(&mut self, expr: &ExprCall, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.expr_beginning_of_line(&expr.func, beginning_of_line); + self.call_args(&expr.args); + } + + fn subexpr_call(&mut self, expr: &ExprCall) { + self.subexpr(&expr.func, false); + self.call_args(&expr.args); + } + + fn expr_cast(&mut self, expr: &ExprCast) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.expr); + self.end(); + self.space(); + self.word("as "); + self.ty(&expr.ty); + self.end(); + } + + fn expr_closure(&mut self, expr: &ExprClosure) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + if expr.asyncness.is_some() { + self.word("async "); + } + if expr.movability.is_some() { + self.word("static "); + } + if expr.capture.is_some() { + self.word("move "); + } + self.cbox(INDENT); + self.word("|"); + for pat in expr.inputs.iter().delimited() { + if pat.is_first { + self.zerobreak(); + } + self.pat(&pat); + if !pat.is_last { + self.word(","); + self.space(); + } + } + match &expr.output { + ReturnType::Default => { + self.word("|"); + self.space(); + self.offset(-INDENT); + self.end(); + } + ReturnType::Type(_arrow, ty) => { + if !expr.inputs.is_empty() { + self.trailing_comma(true); + self.offset(-INDENT); + } + self.word("|"); + self.end(); + self.word(" -> "); + self.ty(ty); + self.nbsp(); + } + } + self.neverbreak(); + self.expr(&expr.body); + self.end(); + } + + fn expr_continue(&mut self, expr: &ExprContinue) { + self.outer_attrs(&expr.attrs); + self.word("continue"); + if let Some(lifetime) = &expr.label { + self.nbsp(); + self.lifetime(lifetime); + } + } + + fn expr_field(&mut self, expr: &ExprField, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.subexpr_field(expr, beginning_of_line); + self.end(); + } + + fn subexpr_field(&mut self, expr: &ExprField, beginning_of_line: bool) { + self.subexpr(&expr.base, beginning_of_line); + self.zerobreak_unless_short_ident(beginning_of_line, &expr.base); + self.word("."); + self.member(&expr.member); + } + + fn expr_for_loop(&mut self, expr: &ExprForLoop) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + if let Some(label) = &expr.label { + self.label(label); + } + self.word("for "); + self.pat(&expr.pat); + self.word(" in "); + self.neverbreak(); + self.wrap_exterior_struct(&expr.expr); + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in &expr.body.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.end(); + } + + fn expr_group(&mut self, expr: &ExprGroup) { + self.outer_attrs(&expr.attrs); + self.expr(&expr.expr); + } + + fn expr_if(&mut self, expr: &ExprIf) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.word("if "); + self.cbox(-INDENT); + self.wrap_exterior_struct(&expr.cond); + self.end(); + if let Some((_else_token, else_branch)) = &expr.else_branch { + let mut else_branch = &**else_branch; + self.small_block(&expr.then_branch, &[]); + loop { + self.word(" else "); + match else_branch { + Expr::If(expr) => { + self.word("if "); + self.wrap_exterior_struct(&expr.cond); + self.small_block(&expr.then_branch, &[]); + if let Some((_else_token, next)) = &expr.else_branch { + else_branch = next; + continue; + } + } + Expr::Block(expr) => { + self.small_block(&expr.block, &[]); + } + // If not one of the valid expressions to exist in an else + // clause, wrap in a block. + other => { + self.word("{"); + self.space(); + self.ibox(INDENT); + self.expr(other); + self.end(); + self.space(); + self.offset(-INDENT); + self.word("}"); + } + } + break; + } + } else if expr.then_branch.stmts.is_empty() { + self.word("{}"); + } else { + self.word("{"); + self.hardbreak(); + for stmt in &expr.then_branch.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.word("}"); + } + self.end(); + } + + fn expr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.expr_beginning_of_line(&expr.expr, beginning_of_line); + self.word("["); + self.expr(&expr.index); + self.word("]"); + } + + fn subexpr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) { + self.subexpr(&expr.expr, beginning_of_line); + self.word("["); + self.expr(&expr.index); + self.word("]"); + } + + fn expr_let(&mut self, expr: &ExprLet) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.word("let "); + self.ibox(-INDENT); + self.pat(&expr.pat); + self.end(); + self.space(); + self.word("= "); + let needs_paren = contains_exterior_struct_lit(&expr.expr); + if needs_paren { + self.word("("); + } + self.expr(&expr.expr); + if needs_paren { + self.word(")"); + } + self.end(); + } + + pub fn expr_lit(&mut self, expr: &ExprLit) { + self.outer_attrs(&expr.attrs); + self.lit(&expr.lit); + } + + fn expr_loop(&mut self, expr: &ExprLoop) { + self.outer_attrs(&expr.attrs); + if let Some(label) = &expr.label { + self.label(label); + } + self.word("loop {"); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in &expr.body.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn expr_macro(&mut self, expr: &ExprMacro) { + self.outer_attrs(&expr.attrs); + self.mac(&expr.mac, None); + } + + fn expr_match(&mut self, expr: &ExprMatch) { + self.outer_attrs(&expr.attrs); + self.ibox(0); + self.word("match "); + self.wrap_exterior_struct(&expr.expr); + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for arm in &expr.arms { + self.arm(arm); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.end(); + } + + fn expr_method_call(&mut self, expr: &ExprMethodCall, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + let unindent_call_args = beginning_of_line && is_short_ident(&expr.receiver); + self.subexpr_method_call(expr, beginning_of_line, unindent_call_args); + self.end(); + } + + fn subexpr_method_call( + &mut self, + expr: &ExprMethodCall, + beginning_of_line: bool, + unindent_call_args: bool, + ) { + self.subexpr(&expr.receiver, beginning_of_line); + self.zerobreak_unless_short_ident(beginning_of_line, &expr.receiver); + self.word("."); + self.ident(&expr.method); + if let Some(turbofish) = &expr.turbofish { + self.method_turbofish(turbofish); + } + self.cbox(if unindent_call_args { -INDENT } else { 0 }); + self.call_args(&expr.args); + self.end(); + } + + fn expr_paren(&mut self, expr: &ExprParen) { + self.outer_attrs(&expr.attrs); + self.word("("); + self.expr(&expr.expr); + self.word(")"); + } + + fn expr_path(&mut self, expr: &ExprPath) { + self.outer_attrs(&expr.attrs); + self.qpath(&expr.qself, &expr.path); + } + + fn expr_range(&mut self, expr: &ExprRange) { + self.outer_attrs(&expr.attrs); + if let Some(from) = &expr.from { + self.expr(from); + } + self.word(match expr.limits { + RangeLimits::HalfOpen(_) => "..", + RangeLimits::Closed(_) => "..=", + }); + if let Some(to) = &expr.to { + self.expr(to); + } + } + + fn expr_reference(&mut self, expr: &ExprReference) { + self.outer_attrs(&expr.attrs); + self.word("&"); + if expr.mutability.is_some() { + self.word("mut "); + } + self.expr(&expr.expr); + } + + fn expr_repeat(&mut self, expr: &ExprRepeat) { + self.outer_attrs(&expr.attrs); + self.word("["); + self.expr(&expr.expr); + self.word("; "); + self.expr(&expr.len); + self.word("]"); + } + + fn expr_return(&mut self, expr: &ExprReturn) { + self.outer_attrs(&expr.attrs); + self.word("return"); + if let Some(value) = &expr.expr { + self.nbsp(); + self.expr(value); + } + } + + fn expr_struct(&mut self, expr: &ExprStruct) { + self.outer_attrs(&expr.attrs); + self.cbox(INDENT); + self.ibox(-INDENT); + self.path(&expr.path); + self.end(); + self.word(" {"); + self.space_if_nonempty(); + for field_value in expr.fields.iter().delimited() { + self.field_value(&field_value); + self.trailing_comma_or_space(field_value.is_last && expr.rest.is_none()); + } + if let Some(rest) = &expr.rest { + self.word(".."); + self.expr(rest); + self.space(); + } + self.offset(-INDENT); + self.end_with_max_width(34); + self.word("}"); + } + + fn expr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) { + self.outer_attrs(&expr.attrs); + self.expr_beginning_of_line(&expr.expr, beginning_of_line); + self.word("?"); + } + + fn subexpr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) { + self.subexpr(&expr.expr, beginning_of_line); + self.word("?"); + } + + fn expr_try_block(&mut self, expr: &ExprTryBlock) { + self.outer_attrs(&expr.attrs); + self.word("try "); + self.cbox(INDENT); + self.small_block(&expr.block, &expr.attrs); + self.end(); + } + + fn expr_tuple(&mut self, expr: &ExprTuple) { + self.outer_attrs(&expr.attrs); + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in expr.elems.iter().delimited() { + self.expr(&elem); + if expr.elems.len() == 1 { + self.word(","); + self.zerobreak(); + } else { + self.trailing_comma(elem.is_last); + } + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + fn expr_type(&mut self, expr: &ExprType) { + self.outer_attrs(&expr.attrs); + self.ibox(INDENT); + self.ibox(-INDENT); + self.expr(&expr.expr); + self.end(); + self.space(); + self.word(": "); + self.ty(&expr.ty); + self.end(); + } + + fn expr_unary(&mut self, expr: &ExprUnary) { + self.outer_attrs(&expr.attrs); + self.unary_operator(&expr.op); + self.expr(&expr.expr); + } + + fn expr_unsafe(&mut self, expr: &ExprUnsafe) { + self.outer_attrs(&expr.attrs); + self.word("unsafe {"); + self.cbox(INDENT); + self.space_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in expr.block.stmts.iter().delimited() { + if stmt.is_first && stmt.is_last { + if let Stmt::Expr(expr) = &*stmt { + self.expr(expr); + self.space(); + continue; + } + } + self.stmt(&stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn expr_verbatim(&mut self, expr: &TokenStream) { + if !expr.is_empty() { + unimplemented!("Expr::Verbatim `{}`", expr); + } + } + + fn expr_while(&mut self, expr: &ExprWhile) { + self.outer_attrs(&expr.attrs); + if let Some(label) = &expr.label { + self.label(label); + } + self.word("while "); + self.wrap_exterior_struct(&expr.cond); + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&expr.attrs); + for stmt in &expr.body.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn expr_yield(&mut self, expr: &ExprYield) { + self.outer_attrs(&expr.attrs); + self.word("yield"); + if let Some(value) = &expr.expr { + self.nbsp(); + self.expr(value); + } + } + + fn label(&mut self, label: &Label) { + self.lifetime(&label.name); + self.word(": "); + } + + fn field_value(&mut self, field_value: &FieldValue) { + self.outer_attrs(&field_value.attrs); + self.member(&field_value.member); + if field_value.colon_token.is_some() { + self.word(": "); + self.ibox(0); + self.expr(&field_value.expr); + self.end(); + } + } + + fn arm(&mut self, arm: &Arm) { + self.outer_attrs(&arm.attrs); + self.ibox(0); + self.pat(&arm.pat); + if let Some((_if_token, guard)) = &arm.guard { + self.word(" if "); + self.expr(guard); + } + self.word(" =>"); + let empty_block; + let mut body = &*arm.body; + while let Expr::Block(expr) = body { + if expr.attrs.is_empty() && expr.label.is_none() { + let mut stmts = expr.block.stmts.iter(); + if let (Some(Stmt::Expr(inner)), None) = (stmts.next(), stmts.next()) { + body = inner; + continue; + } + } + break; + } + if let Expr::Tuple(expr) = body { + if expr.elems.is_empty() && expr.attrs.is_empty() { + empty_block = Expr::Block(ExprBlock { + attrs: Vec::new(), + label: None, + block: Block { + brace_token: token::Brace::default(), + stmts: Vec::new(), + }, + }); + body = &empty_block; + } + } + if let Expr::Block(body) = body { + self.nbsp(); + if let Some(label) = &body.label { + self.label(label); + } + self.word("{"); + self.neverbreak(); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + self.inner_attrs(&body.attrs); + for stmt in &body.block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.end(); + } else { + self.nbsp(); + self.neverbreak(); + self.cbox(INDENT); + self.scan_break(BreakToken { + pre_break: Some('{'), + ..BreakToken::default() + }); + self.expr(body); + self.scan_break(BreakToken { + offset: -INDENT, + pre_break: stmt::add_semi(body).then(|| ';'), + post_break: Some('}'), + no_break: requires_terminator(body).then(|| ','), + ..BreakToken::default() + }); + self.end(); + self.end(); + } + } + + fn method_turbofish(&mut self, turbofish: &MethodTurbofish) { + self.word("::<"); + self.cbox(0); + self.zerobreak(); + for arg in turbofish.args.iter().delimited() { + self.generic_method_argument(&arg); + self.trailing_comma(arg.is_last); + } + self.offset(-INDENT); + self.end(); + self.word(">"); + } + + fn generic_method_argument(&mut self, generic: &GenericMethodArgument) { + match generic { + GenericMethodArgument::Type(arg) => self.ty(arg), + GenericMethodArgument::Const(arg) => self.expr(arg), + } + } + + fn call_args(&mut self, args: &Punctuated) { + self.word("("); + let mut iter = args.iter(); + match (iter.next(), iter.next()) { + ( + Some( + expr @ (Expr::Array(ExprArray { attrs, .. }) + | Expr::Async(ExprAsync { attrs, .. }) + | Expr::Block(ExprBlock { attrs, .. }) + | Expr::Closure(ExprClosure { attrs, .. }) + | Expr::Struct(ExprStruct { attrs, .. }) + | Expr::TryBlock(ExprTryBlock { attrs, .. }) + | Expr::Tuple(ExprTuple { attrs, .. }) + | Expr::Unsafe(ExprUnsafe { attrs, .. })), + ), + None, + ) if !attr::has_outer(attrs) => { + self.expr(expr); + } + _ => { + self.cbox(INDENT); + self.zerobreak(); + for arg in args.iter().delimited() { + self.expr(&arg); + self.trailing_comma(arg.is_last); + } + self.offset(-INDENT); + self.end(); + } + } + self.word(")"); + } + + fn small_block(&mut self, block: &Block, attrs: &[Attribute]) { + self.word("{"); + if attr::has_inner(attrs) || !block.stmts.is_empty() { + self.space(); + self.inner_attrs(attrs); + match (block.stmts.get(0), block.stmts.get(1)) { + (Some(Stmt::Expr(expr)), None) if stmt::break_after(expr) => { + self.ibox(0); + self.expr_beginning_of_line(expr, true); + self.end(); + self.space(); + } + _ => { + for stmt in &block.stmts { + self.stmt(stmt); + } + } + } + self.offset(-INDENT); + } + self.word("}"); + } + + pub fn member(&mut self, member: &Member) { + match member { + Member::Named(ident) => self.ident(ident), + Member::Unnamed(index) => self.index(index), + } + } + + fn index(&mut self, member: &Index) { + self.word(member.index.to_string()); + } + + fn binary_operator(&mut self, op: &BinOp) { + self.word(match op { + BinOp::Add(_) => "+", + BinOp::Sub(_) => "-", + BinOp::Mul(_) => "*", + BinOp::Div(_) => "/", + BinOp::Rem(_) => "%", + BinOp::And(_) => "&&", + BinOp::Or(_) => "||", + BinOp::BitXor(_) => "^", + BinOp::BitAnd(_) => "&", + BinOp::BitOr(_) => "|", + BinOp::Shl(_) => "<<", + BinOp::Shr(_) => ">>", + BinOp::Eq(_) => "==", + BinOp::Lt(_) => "<", + BinOp::Le(_) => "<=", + BinOp::Ne(_) => "!=", + BinOp::Ge(_) => ">=", + BinOp::Gt(_) => ">", + BinOp::AddEq(_) => "+=", + BinOp::SubEq(_) => "-=", + BinOp::MulEq(_) => "*=", + BinOp::DivEq(_) => "/=", + BinOp::RemEq(_) => "%=", + BinOp::BitXorEq(_) => "^=", + BinOp::BitAndEq(_) => "&=", + BinOp::BitOrEq(_) => "|=", + BinOp::ShlEq(_) => "<<=", + BinOp::ShrEq(_) => ">>=", + }); + } + + fn unary_operator(&mut self, op: &UnOp) { + self.word(match op { + UnOp::Deref(_) => "*", + UnOp::Not(_) => "!", + UnOp::Neg(_) => "-", + }); + } + + fn zerobreak_unless_short_ident(&mut self, beginning_of_line: bool, expr: &Expr) { + if beginning_of_line && is_short_ident(expr) { + return; + } + self.zerobreak(); + } +} + +pub fn requires_terminator(expr: &Expr) -> bool { + // see https://github.com/rust-lang/rust/blob/2679c38fc/src/librustc_ast/util/classify.rs#L7-L25 + match expr { + Expr::Unsafe(_) + | Expr::Block(_) + | Expr::If(_) + | Expr::Match(_) + | Expr::While(_) + | Expr::Loop(_) + | Expr::ForLoop(_) + | Expr::Async(_) + | Expr::TryBlock(_) => false, + _ => true, + } +} + +// Expressions that syntactically contain an "exterior" struct literal i.e. not +// surrounded by any parens or other delimiters. For example `X { y: 1 }`, `X { +// y: 1 }.method()`, `foo == X { y: 1 }` and `X { y: 1 } == foo` all do, but `(X +// { y: 1 }) == foo` does not. +fn contains_exterior_struct_lit(expr: &Expr) -> bool { + match expr { + Expr::Struct(_) => true, + + Expr::Assign(ExprAssign { left, right, .. }) + | Expr::AssignOp(ExprAssignOp { left, right, .. }) + | Expr::Binary(ExprBinary { left, right, .. }) => { + // X { y: 1 } + X { y: 2 } + contains_exterior_struct_lit(left) || contains_exterior_struct_lit(right) + } + + Expr::Await(ExprAwait { base: e, .. }) + | Expr::Box(ExprBox { expr: e, .. }) + | Expr::Cast(ExprCast { expr: e, .. }) + | Expr::Field(ExprField { base: e, .. }) + | Expr::Index(ExprIndex { expr: e, .. }) + | Expr::MethodCall(ExprMethodCall { receiver: e, .. }) + | Expr::Reference(ExprReference { expr: e, .. }) + | Expr::Type(ExprType { expr: e, .. }) + | Expr::Unary(ExprUnary { expr: e, .. }) => { + // &X { y: 1 }, X { y: 1 }.y + contains_exterior_struct_lit(e) + } + + _ => false, + } +} + +fn needs_newline_if_wrap(expr: &Expr) -> bool { + match expr { + Expr::Array(_) + | Expr::Async(_) + | Expr::Block(_) + | Expr::Break(ExprBreak { expr: None, .. }) + | Expr::Closure(_) + | Expr::Continue(_) + | Expr::ForLoop(_) + | Expr::If(_) + | Expr::Lit(_) + | Expr::Loop(_) + | Expr::Macro(_) + | Expr::Match(_) + | Expr::Path(_) + | Expr::Range(ExprRange { to: None, .. }) + | Expr::Repeat(_) + | Expr::Return(ExprReturn { expr: None, .. }) + | Expr::Struct(_) + | Expr::TryBlock(_) + | Expr::Tuple(_) + | Expr::Unsafe(_) + | Expr::Verbatim(_) + | Expr::While(_) + | Expr::Yield(ExprYield { expr: None, .. }) => false, + + Expr::Assign(_) + | Expr::AssignOp(_) + | Expr::Await(_) + | Expr::Binary(_) + | Expr::Cast(_) + | Expr::Field(_) + | Expr::Index(_) + | Expr::MethodCall(_) + | Expr::Type(_) => true, + + Expr::Box(ExprBox { expr: e, .. }) + | Expr::Break(ExprBreak { expr: Some(e), .. }) + | Expr::Call(ExprCall { func: e, .. }) + | Expr::Group(ExprGroup { expr: e, .. }) + | Expr::Let(ExprLet { expr: e, .. }) + | Expr::Paren(ExprParen { expr: e, .. }) + | Expr::Range(ExprRange { to: Some(e), .. }) + | Expr::Reference(ExprReference { expr: e, .. }) + | Expr::Return(ExprReturn { expr: Some(e), .. }) + | Expr::Try(ExprTry { expr: e, .. }) + | Expr::Unary(ExprUnary { expr: e, .. }) + | Expr::Yield(ExprYield { expr: Some(e), .. }) => needs_newline_if_wrap(e), + + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => false, + } +} + +fn is_short_ident(expr: &Expr) -> bool { + if let Expr::Path(expr) = expr { + if expr.attrs.is_empty() + && expr.qself.is_none() + && expr.path.leading_colon.is_none() + && expr.path.segments.len() == 1 + && expr.path.segments[0].ident.to_string().len() as isize <= INDENT + { + if let PathArguments::None = expr.path.segments[0].arguments { + return true; + } + } + } + false +} diff --git a/packages/autofmt/src/prettyplease/file.rs b/packages/autofmt/src/prettyplease/file.rs new file mode 100644 index 000000000..51eece54c --- /dev/null +++ b/packages/autofmt/src/prettyplease/file.rs @@ -0,0 +1,17 @@ +use super::algorithm::Printer; +use syn::File; + +impl Printer { + pub fn file(&mut self, file: &File) { + self.cbox(0); + if let Some(shebang) = &file.shebang { + self.word(shebang.clone()); + self.hardbreak(); + } + self.inner_attrs(&file.attrs); + for item in &file.items { + self.item(item); + } + self.end(); + } +} diff --git a/packages/autofmt/src/prettyplease/generics.rs b/packages/autofmt/src/prettyplease/generics.rs new file mode 100644 index 000000000..ded973b08 --- /dev/null +++ b/packages/autofmt/src/prettyplease/generics.rs @@ -0,0 +1,280 @@ +use super::algorithm::Printer; +use super::iter::IterDelimited; +use super::INDENT; +use syn::{ + BoundLifetimes, ConstParam, GenericParam, Generics, LifetimeDef, PredicateEq, + PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, TypeParam, TypeParamBound, + WhereClause, WherePredicate, +}; + +impl Printer { + pub fn generics(&mut self, generics: &Generics) { + if generics.params.is_empty() { + return; + } + + self.word("<"); + self.cbox(0); + self.zerobreak(); + + // Print lifetimes before types and consts, regardless of their + // order in self.params. + // + // TODO: ordering rules for const parameters vs type parameters have + // not been settled yet. https://github.com/rust-lang/rust/issues/44580 + for param in generics.params.iter().delimited() { + if let GenericParam::Lifetime(_) = *param { + self.generic_param(¶m); + self.trailing_comma(param.is_last); + } + } + for param in generics.params.iter().delimited() { + match *param { + GenericParam::Type(_) | GenericParam::Const(_) => { + self.generic_param(¶m); + self.trailing_comma(param.is_last); + } + GenericParam::Lifetime(_) => {} + } + } + + self.offset(-INDENT); + self.end(); + self.word(">"); + } + + fn generic_param(&mut self, generic_param: &GenericParam) { + match generic_param { + GenericParam::Type(type_param) => self.type_param(type_param), + GenericParam::Lifetime(lifetime_def) => self.lifetime_def(lifetime_def), + GenericParam::Const(const_param) => self.const_param(const_param), + } + } + + pub fn bound_lifetimes(&mut self, bound_lifetimes: &BoundLifetimes) { + self.word("for<"); + for lifetime_def in bound_lifetimes.lifetimes.iter().delimited() { + self.lifetime_def(&lifetime_def); + if !lifetime_def.is_last { + self.word(", "); + } + } + self.word("> "); + } + + fn lifetime_def(&mut self, lifetime_def: &LifetimeDef) { + self.outer_attrs(&lifetime_def.attrs); + self.lifetime(&lifetime_def.lifetime); + for lifetime in lifetime_def.bounds.iter().delimited() { + if lifetime.is_first { + self.word(": "); + } else { + self.word(" + "); + } + self.lifetime(&lifetime); + } + } + + fn type_param(&mut self, type_param: &TypeParam) { + self.outer_attrs(&type_param.attrs); + self.ident(&type_param.ident); + self.ibox(INDENT); + for type_param_bound in type_param.bounds.iter().delimited() { + if type_param_bound.is_first { + self.word(": "); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&type_param_bound); + } + if let Some(default) = &type_param.default { + self.space(); + self.word("= "); + self.ty(default); + } + self.end(); + } + + pub fn type_param_bound(&mut self, type_param_bound: &TypeParamBound) { + match type_param_bound { + TypeParamBound::Trait(trait_bound) => self.trait_bound(trait_bound), + TypeParamBound::Lifetime(lifetime) => self.lifetime(lifetime), + } + } + + fn trait_bound(&mut self, trait_bound: &TraitBound) { + if trait_bound.paren_token.is_some() { + self.word("("); + } + let skip = match trait_bound.path.segments.first() { + Some(segment) if segment.ident == "const" => { + self.word("~const "); + 1 + } + _ => 0, + }; + self.trait_bound_modifier(&trait_bound.modifier); + if let Some(bound_lifetimes) = &trait_bound.lifetimes { + self.bound_lifetimes(bound_lifetimes); + } + for segment in trait_bound.path.segments.iter().skip(skip).delimited() { + if !segment.is_first || trait_bound.path.leading_colon.is_some() { + self.word("::"); + } + self.path_segment(&segment); + } + if trait_bound.paren_token.is_some() { + self.word(")"); + } + } + + fn trait_bound_modifier(&mut self, trait_bound_modifier: &TraitBoundModifier) { + match trait_bound_modifier { + TraitBoundModifier::None => {} + TraitBoundModifier::Maybe(_question_mark) => self.word("?"), + } + } + + fn const_param(&mut self, const_param: &ConstParam) { + self.outer_attrs(&const_param.attrs); + self.word("const "); + self.ident(&const_param.ident); + self.word(": "); + self.ty(&const_param.ty); + if let Some(default) = &const_param.default { + self.word(" = "); + self.expr(default); + } + } + + pub fn where_clause_for_body(&mut self, where_clause: &Option) { + let hardbreaks = true; + let semi = false; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + pub fn where_clause_semi(&mut self, where_clause: &Option) { + let hardbreaks = true; + let semi = true; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + pub fn where_clause_oneline(&mut self, where_clause: &Option) { + let hardbreaks = false; + let semi = false; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + pub fn where_clause_oneline_semi(&mut self, where_clause: &Option) { + let hardbreaks = false; + let semi = true; + self.where_clause_impl(where_clause, hardbreaks, semi); + } + + fn where_clause_impl( + &mut self, + where_clause: &Option, + hardbreaks: bool, + semi: bool, + ) { + let where_clause = match where_clause { + Some(where_clause) if !where_clause.predicates.is_empty() => where_clause, + _ => { + if semi { + self.word(";"); + } else { + self.nbsp(); + } + return; + } + }; + if hardbreaks { + self.hardbreak(); + self.offset(-INDENT); + self.word("where"); + self.hardbreak(); + for predicate in where_clause.predicates.iter().delimited() { + self.where_predicate(&predicate); + if predicate.is_last && semi { + self.word(";"); + } else { + self.word(","); + self.hardbreak(); + } + } + if !semi { + self.offset(-INDENT); + } + } else { + self.space(); + self.offset(-INDENT); + self.word("where"); + self.space(); + for predicate in where_clause.predicates.iter().delimited() { + self.where_predicate(&predicate); + if predicate.is_last && semi { + self.word(";"); + } else { + self.trailing_comma_or_space(predicate.is_last); + } + } + if !semi { + self.offset(-INDENT); + } + } + } + + fn where_predicate(&mut self, predicate: &WherePredicate) { + match predicate { + WherePredicate::Type(predicate) => self.predicate_type(predicate), + WherePredicate::Lifetime(predicate) => self.predicate_lifetime(predicate), + WherePredicate::Eq(predicate) => self.predicate_eq(predicate), + } + } + + fn predicate_type(&mut self, predicate: &PredicateType) { + if let Some(bound_lifetimes) = &predicate.lifetimes { + self.bound_lifetimes(bound_lifetimes); + } + self.ty(&predicate.bounded_ty); + self.word(":"); + if predicate.bounds.len() == 1 { + self.ibox(0); + } else { + self.ibox(INDENT); + } + for type_param_bound in predicate.bounds.iter().delimited() { + if type_param_bound.is_first { + self.nbsp(); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&type_param_bound); + } + self.end(); + } + + fn predicate_lifetime(&mut self, predicate: &PredicateLifetime) { + self.lifetime(&predicate.lifetime); + self.word(":"); + self.ibox(INDENT); + for lifetime in predicate.bounds.iter().delimited() { + if lifetime.is_first { + self.nbsp(); + } else { + self.space(); + self.word("+ "); + } + self.lifetime(&lifetime); + } + self.end(); + } + + fn predicate_eq(&mut self, predicate: &PredicateEq) { + self.ty(&predicate.lhs_ty); + self.word(" = "); + self.ty(&predicate.rhs_ty); + } +} diff --git a/packages/autofmt/src/prettyplease/item.rs b/packages/autofmt/src/prettyplease/item.rs new file mode 100644 index 000000000..5512137d9 --- /dev/null +++ b/packages/autofmt/src/prettyplease/item.rs @@ -0,0 +1,757 @@ +use super::algorithm::Printer; +use super::iter::IterDelimited; +use super::INDENT; +use proc_macro2::TokenStream; +use syn::{ + Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemMacro, ForeignItemStatic, + ForeignItemType, ImplItem, ImplItemConst, ImplItemMacro, ImplItemMethod, ImplItemType, Item, + ItemConst, ItemEnum, ItemExternCrate, ItemFn, ItemForeignMod, ItemImpl, ItemMacro, ItemMacro2, + ItemMod, ItemStatic, ItemStruct, ItemTrait, ItemTraitAlias, ItemType, ItemUnion, ItemUse, Pat, + Receiver, Signature, Stmt, TraitItem, TraitItemConst, TraitItemMacro, TraitItemMethod, + TraitItemType, Type, UseGlob, UseGroup, UseName, UsePath, UseRename, UseTree, +}; + +impl Printer { + pub fn item(&mut self, item: &Item) { + match item { + Item::Const(item) => self.item_const(item), + Item::Enum(item) => self.item_enum(item), + Item::ExternCrate(item) => self.item_extern_crate(item), + Item::Fn(item) => self.item_fn(item), + Item::ForeignMod(item) => self.item_foreign_mod(item), + Item::Impl(item) => self.item_impl(item), + Item::Macro(item) => self.item_macro(item), + Item::Macro2(item) => self.item_macro2(item), + Item::Mod(item) => self.item_mod(item), + Item::Static(item) => self.item_static(item), + Item::Struct(item) => self.item_struct(item), + Item::Trait(item) => self.item_trait(item), + Item::TraitAlias(item) => self.item_trait_alias(item), + Item::Type(item) => self.item_type(item), + Item::Union(item) => self.item_union(item), + Item::Use(item) => self.item_use(item), + Item::Verbatim(item) => self.item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Item"), + } + } + + fn item_const(&mut self, item: &ItemConst) { + self.outer_attrs(&item.attrs); + self.cbox(0); + self.visibility(&item.vis); + self.word("const "); + self.ident(&item.ident); + self.word(": "); + self.ty(&item.ty); + self.word(" = "); + self.neverbreak(); + self.expr(&item.expr); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn item_enum(&mut self, item: &ItemEnum) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("enum "); + self.ident(&item.ident); + self.generics(&item.generics); + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + for variant in &item.variants { + self.variant(variant); + self.word(","); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_extern_crate(&mut self, item: &ItemExternCrate) { + self.outer_attrs(&item.attrs); + self.visibility(&item.vis); + self.word("extern crate "); + self.ident(&item.ident); + if let Some((_as_token, rename)) = &item.rename { + self.word(" as "); + self.ident(rename); + } + self.word(";"); + self.hardbreak(); + } + + fn item_fn(&mut self, item: &ItemFn) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.signature(&item.sig); + self.where_clause_for_body(&item.sig.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for stmt in &item.block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_foreign_mod(&mut self, item: &ItemForeignMod) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.abi(&item.abi); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for foreign_item in &item.items { + self.foreign_item(foreign_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_impl(&mut self, item: &ItemImpl) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.ibox(-INDENT); + self.cbox(INDENT); + if item.defaultness.is_some() { + self.word("default "); + } + if item.unsafety.is_some() { + self.word("unsafe "); + } + self.word("impl"); + self.generics(&item.generics); + self.end(); + self.nbsp(); + if let Some((negative_polarity, path, _for_token)) = &item.trait_ { + if negative_polarity.is_some() { + self.word("!"); + } + self.path(path); + self.space(); + self.word("for "); + } + self.ty(&item.self_ty); + self.end(); + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for impl_item in &item.items { + self.impl_item(impl_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_macro(&mut self, item: &ItemMacro) { + self.outer_attrs(&item.attrs); + self.mac(&item.mac, item.ident.as_ref()); + self.mac_semi_if_needed(&item.mac.delimiter); + self.hardbreak(); + } + + fn item_macro2(&mut self, item: &ItemMacro2) { + unimplemented!("Item::Macro2 `macro {} {}`", item.ident, item.rules); + } + + fn item_mod(&mut self, item: &ItemMod) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("mod "); + self.ident(&item.ident); + if let Some((_brace, items)) = &item.content { + self.word(" {"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for item in items { + self.item(item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } else { + self.word(";"); + self.end(); + } + self.hardbreak(); + } + + fn item_static(&mut self, item: &ItemStatic) { + self.outer_attrs(&item.attrs); + self.cbox(0); + self.visibility(&item.vis); + self.word("static "); + if item.mutability.is_some() { + self.word("mut "); + } + self.ident(&item.ident); + self.word(": "); + self.ty(&item.ty); + self.word(" = "); + self.neverbreak(); + self.expr(&item.expr); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn item_struct(&mut self, item: &ItemStruct) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("struct "); + self.ident(&item.ident); + self.generics(&item.generics); + match &item.fields { + Fields::Named(fields) => { + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + for field in &fields.named { + self.field(field); + self.word(","); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + Fields::Unnamed(fields) => { + self.fields_unnamed(fields); + self.where_clause_semi(&item.generics.where_clause); + self.end(); + } + Fields::Unit => { + self.where_clause_semi(&item.generics.where_clause); + self.end(); + } + } + self.hardbreak(); + } + + fn item_trait(&mut self, item: &ItemTrait) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + if item.unsafety.is_some() { + self.word("unsafe "); + } + if item.auto_token.is_some() { + self.word("auto "); + } + self.word("trait "); + self.ident(&item.ident); + self.generics(&item.generics); + for supertrait in item.supertraits.iter().delimited() { + if supertrait.is_first { + self.word(": "); + } else { + self.word(" + "); + } + self.type_param_bound(&supertrait); + } + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&item.attrs); + for trait_item in &item.items { + self.trait_item(trait_item); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_trait_alias(&mut self, item: &ItemTraitAlias) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("trait "); + self.ident(&item.ident); + self.generics(&item.generics); + self.word(" = "); + self.neverbreak(); + for bound in item.bounds.iter().delimited() { + if !bound.is_first { + self.space(); + self.word("+ "); + } + self.type_param_bound(&bound); + } + self.where_clause_semi(&item.generics.where_clause); + self.end(); + self.hardbreak(); + } + + fn item_type(&mut self, item: &ItemType) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("type "); + self.ident(&item.ident); + self.generics(&item.generics); + self.where_clause_oneline(&item.generics.where_clause); + self.word("= "); + self.neverbreak(); + self.ibox(-INDENT); + self.ty(&item.ty); + self.end(); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn item_union(&mut self, item: &ItemUnion) { + self.outer_attrs(&item.attrs); + self.cbox(INDENT); + self.visibility(&item.vis); + self.word("union "); + self.ident(&item.ident); + self.generics(&item.generics); + self.where_clause_for_body(&item.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + for field in &item.fields.named { + self.field(field); + self.word(","); + self.hardbreak(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn item_use(&mut self, item: &ItemUse) { + self.outer_attrs(&item.attrs); + self.visibility(&item.vis); + self.word("use "); + if item.leading_colon.is_some() { + self.word("::"); + } + self.use_tree(&item.tree); + self.word(";"); + self.hardbreak(); + } + + fn item_verbatim(&mut self, item: &TokenStream) { + if !item.is_empty() { + unimplemented!("Item::Verbatim `{}`", item); + } + self.hardbreak(); + } + + fn use_tree(&mut self, use_tree: &UseTree) { + match use_tree { + UseTree::Path(use_path) => self.use_path(use_path), + UseTree::Name(use_name) => self.use_name(use_name), + UseTree::Rename(use_rename) => self.use_rename(use_rename), + UseTree::Glob(use_glob) => self.use_glob(use_glob), + UseTree::Group(use_group) => self.use_group(use_group), + } + } + + fn use_path(&mut self, use_path: &UsePath) { + self.ident(&use_path.ident); + self.word("::"); + self.use_tree(&use_path.tree); + } + + fn use_name(&mut self, use_name: &UseName) { + self.ident(&use_name.ident); + } + + fn use_rename(&mut self, use_rename: &UseRename) { + self.ident(&use_rename.ident); + self.word(" as "); + self.ident(&use_rename.rename); + } + + fn use_glob(&mut self, use_glob: &UseGlob) { + let _ = use_glob; + self.word("*"); + } + + fn use_group(&mut self, use_group: &UseGroup) { + if use_group.items.is_empty() { + self.word("{}"); + } else if use_group.items.len() == 1 { + self.use_tree(&use_group.items[0]); + } else { + self.cbox(INDENT); + self.word("{"); + self.zerobreak(); + self.ibox(0); + for use_tree in use_group.items.iter().delimited() { + self.use_tree(&use_tree); + if !use_tree.is_last { + self.word(","); + let mut use_tree = *use_tree; + while let UseTree::Path(use_path) = use_tree { + use_tree = &use_path.tree; + } + if let UseTree::Group(_) = use_tree { + self.hardbreak(); + } else { + self.space(); + } + } + } + self.end(); + self.trailing_comma(true); + self.offset(-INDENT); + self.word("}"); + self.end(); + } + } + + fn foreign_item(&mut self, foreign_item: &ForeignItem) { + match foreign_item { + ForeignItem::Fn(item) => self.foreign_item_fn(item), + ForeignItem::Static(item) => self.foreign_item_static(item), + ForeignItem::Type(item) => self.foreign_item_type(item), + ForeignItem::Macro(item) => self.foreign_item_macro(item), + ForeignItem::Verbatim(item) => self.foreign_item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown ForeignItem"), + } + } + + fn foreign_item_fn(&mut self, foreign_item: &ForeignItemFn) { + self.outer_attrs(&foreign_item.attrs); + self.cbox(INDENT); + self.visibility(&foreign_item.vis); + self.signature(&foreign_item.sig); + self.where_clause_semi(&foreign_item.sig.generics.where_clause); + self.end(); + self.hardbreak(); + } + + fn foreign_item_static(&mut self, foreign_item: &ForeignItemStatic) { + self.outer_attrs(&foreign_item.attrs); + self.cbox(0); + self.visibility(&foreign_item.vis); + self.word("static "); + if foreign_item.mutability.is_some() { + self.word("mut "); + } + self.ident(&foreign_item.ident); + self.word(": "); + self.ty(&foreign_item.ty); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn foreign_item_type(&mut self, foreign_item: &ForeignItemType) { + self.outer_attrs(&foreign_item.attrs); + self.cbox(0); + self.visibility(&foreign_item.vis); + self.word("type "); + self.ident(&foreign_item.ident); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn foreign_item_macro(&mut self, foreign_item: &ForeignItemMacro) { + self.outer_attrs(&foreign_item.attrs); + self.mac(&foreign_item.mac, None); + self.mac_semi_if_needed(&foreign_item.mac.delimiter); + self.hardbreak(); + } + + fn foreign_item_verbatim(&mut self, foreign_item: &TokenStream) { + if !foreign_item.is_empty() { + unimplemented!("ForeignItem::Verbatim `{}`", foreign_item); + } + self.hardbreak(); + } + + fn trait_item(&mut self, trait_item: &TraitItem) { + match trait_item { + TraitItem::Const(item) => self.trait_item_const(item), + TraitItem::Method(item) => self.trait_item_method(item), + TraitItem::Type(item) => self.trait_item_type(item), + TraitItem::Macro(item) => self.trait_item_macro(item), + TraitItem::Verbatim(item) => self.trait_item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown TraitItem"), + } + } + + fn trait_item_const(&mut self, trait_item: &TraitItemConst) { + self.outer_attrs(&trait_item.attrs); + self.cbox(0); + self.word("const "); + self.ident(&trait_item.ident); + self.word(": "); + self.ty(&trait_item.ty); + if let Some((_eq_token, default)) = &trait_item.default { + self.word(" = "); + self.neverbreak(); + self.expr(default); + } + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn trait_item_method(&mut self, trait_item: &TraitItemMethod) { + self.outer_attrs(&trait_item.attrs); + self.cbox(INDENT); + self.signature(&trait_item.sig); + if let Some(block) = &trait_item.default { + self.where_clause_for_body(&trait_item.sig.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&trait_item.attrs); + for stmt in &block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } else { + self.where_clause_semi(&trait_item.sig.generics.where_clause); + self.end(); + } + self.hardbreak(); + } + + fn trait_item_type(&mut self, trait_item: &TraitItemType) { + self.outer_attrs(&trait_item.attrs); + self.cbox(INDENT); + self.word("type "); + self.ident(&trait_item.ident); + self.generics(&trait_item.generics); + for bound in trait_item.bounds.iter().delimited() { + if bound.is_first { + self.word(": "); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&bound); + } + if let Some((_eq_token, default)) = &trait_item.default { + self.where_clause_oneline(&trait_item.generics.where_clause); + self.word("= "); + self.neverbreak(); + self.ty(default); + } else { + self.where_clause_oneline_semi(&trait_item.generics.where_clause); + } + self.end(); + self.hardbreak(); + } + + fn trait_item_macro(&mut self, trait_item: &TraitItemMacro) { + self.outer_attrs(&trait_item.attrs); + self.mac(&trait_item.mac, None); + self.mac_semi_if_needed(&trait_item.mac.delimiter); + self.hardbreak(); + } + + fn trait_item_verbatim(&mut self, trait_item: &TokenStream) { + if !trait_item.is_empty() { + unimplemented!("TraitItem::Verbatim `{}`", trait_item); + } + self.hardbreak(); + } + + fn impl_item(&mut self, impl_item: &ImplItem) { + match impl_item { + ImplItem::Const(item) => self.impl_item_const(item), + ImplItem::Method(item) => self.impl_item_method(item), + ImplItem::Type(item) => self.impl_item_type(item), + ImplItem::Macro(item) => self.impl_item_macro(item), + ImplItem::Verbatim(item) => self.impl_item_verbatim(item), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown ImplItem"), + } + } + + fn impl_item_const(&mut self, impl_item: &ImplItemConst) { + self.outer_attrs(&impl_item.attrs); + self.cbox(0); + self.visibility(&impl_item.vis); + if impl_item.defaultness.is_some() { + self.word("default "); + } + self.word("const "); + self.ident(&impl_item.ident); + self.word(": "); + self.ty(&impl_item.ty); + self.word(" = "); + self.neverbreak(); + self.expr(&impl_item.expr); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn impl_item_method(&mut self, impl_item: &ImplItemMethod) { + self.outer_attrs(&impl_item.attrs); + self.cbox(INDENT); + self.visibility(&impl_item.vis); + if impl_item.defaultness.is_some() { + self.word("default "); + } + self.signature(&impl_item.sig); + if impl_item.block.stmts.len() == 1 { + if let Stmt::Item(Item::Verbatim(verbatim)) = &impl_item.block.stmts[0] { + if verbatim.to_string() == ";" { + self.where_clause_semi(&impl_item.sig.generics.where_clause); + self.end(); + self.hardbreak(); + return; + } + } + } + self.where_clause_for_body(&impl_item.sig.generics.where_clause); + self.word("{"); + self.hardbreak_if_nonempty(); + self.inner_attrs(&impl_item.attrs); + for stmt in &impl_item.block.stmts { + self.stmt(stmt); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + self.hardbreak(); + } + + fn impl_item_type(&mut self, impl_item: &ImplItemType) { + self.outer_attrs(&impl_item.attrs); + self.cbox(INDENT); + self.visibility(&impl_item.vis); + if impl_item.defaultness.is_some() { + self.word("default "); + } + self.word("type "); + self.ident(&impl_item.ident); + self.generics(&impl_item.generics); + self.where_clause_oneline(&impl_item.generics.where_clause); + self.word("= "); + self.neverbreak(); + self.ibox(-INDENT); + self.ty(&impl_item.ty); + self.end(); + self.word(";"); + self.end(); + self.hardbreak(); + } + + fn impl_item_macro(&mut self, impl_item: &ImplItemMacro) { + self.outer_attrs(&impl_item.attrs); + self.mac(&impl_item.mac, None); + self.mac_semi_if_needed(&impl_item.mac.delimiter); + self.hardbreak(); + } + + fn impl_item_verbatim(&mut self, impl_item: &TokenStream) { + if !impl_item.is_empty() { + unimplemented!("ImplItem::Verbatim `{}`", impl_item); + } + self.hardbreak(); + } + + fn maybe_variadic(&mut self, arg: &FnArg) -> bool { + let pat_type = match arg { + FnArg::Typed(pat_type) => pat_type, + FnArg::Receiver(receiver) => { + self.receiver(receiver); + return false; + } + }; + + match pat_type.ty.as_ref() { + Type::Verbatim(ty) if ty.to_string() == "..." => { + match pat_type.pat.as_ref() { + Pat::Verbatim(pat) if pat.to_string() == "..." => { + self.outer_attrs(&pat_type.attrs); + self.word("..."); + } + _ => self.pat_type(pat_type), + } + true + } + _ => { + self.pat_type(pat_type); + false + } + } + } + + fn signature(&mut self, signature: &Signature) { + if signature.constness.is_some() { + self.word("const "); + } + if signature.asyncness.is_some() { + self.word("async "); + } + if signature.unsafety.is_some() { + self.word("unsafe "); + } + if let Some(abi) = &signature.abi { + self.abi(abi); + } + self.word("fn "); + self.ident(&signature.ident); + self.generics(&signature.generics); + self.word("("); + self.neverbreak(); + self.cbox(0); + self.zerobreak(); + let mut last_is_variadic = false; + for input in signature.inputs.iter().delimited() { + last_is_variadic = self.maybe_variadic(&input); + if last_is_variadic { + self.zerobreak(); + } else { + self.trailing_comma(input.is_last); + } + } + if signature.variadic.is_some() && !last_is_variadic { + self.word("..."); + self.zerobreak(); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + self.return_type(&signature.output); + } + + fn receiver(&mut self, receiver: &Receiver) { + self.outer_attrs(&receiver.attrs); + if let Some((_ampersand, lifetime)) = &receiver.reference { + self.word("&"); + if let Some(lifetime) = lifetime { + self.lifetime(lifetime); + self.nbsp(); + } + } + if receiver.mutability.is_some() { + self.word("mut "); + } + self.word("self"); + } +} diff --git a/packages/autofmt/src/prettyplease/iter.rs b/packages/autofmt/src/prettyplease/iter.rs new file mode 100644 index 000000000..702c653f5 --- /dev/null +++ b/packages/autofmt/src/prettyplease/iter.rs @@ -0,0 +1,46 @@ +use std::iter::Peekable; +use std::ops::Deref; + +pub struct Delimited { + is_first: bool, + iter: Peekable, +} + +pub trait IterDelimited: Iterator + Sized { + fn delimited(self) -> Delimited { + Delimited { + is_first: true, + iter: self.peekable(), + } + } +} + +impl IterDelimited for I {} + +pub struct IteratorItem { + value: T, + pub is_first: bool, + pub is_last: bool, +} + +impl Iterator for Delimited { + type Item = IteratorItem; + + fn next(&mut self) -> Option { + let item = IteratorItem { + value: self.iter.next()?, + is_first: self.is_first, + is_last: self.iter.peek().is_none(), + }; + self.is_first = false; + Some(item) + } +} + +impl Deref for IteratorItem { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.value + } +} diff --git a/packages/autofmt/src/prettyplease/lifetime.rs b/packages/autofmt/src/prettyplease/lifetime.rs new file mode 100644 index 000000000..760d5776d --- /dev/null +++ b/packages/autofmt/src/prettyplease/lifetime.rs @@ -0,0 +1,9 @@ +use super::algorithm::Printer; +use syn::Lifetime; + +impl Printer { + pub fn lifetime(&mut self, lifetime: &Lifetime) { + self.word("'"); + self.ident(&lifetime.ident); + } +} diff --git a/packages/autofmt/src/prettyplease/lit.rs b/packages/autofmt/src/prettyplease/lit.rs new file mode 100644 index 000000000..37f2160ad --- /dev/null +++ b/packages/autofmt/src/prettyplease/lit.rs @@ -0,0 +1,50 @@ +use super::algorithm::Printer; +use proc_macro2::Literal; +use syn::{Lit, LitBool, LitByte, LitByteStr, LitChar, LitFloat, LitInt, LitStr}; + +impl Printer { + pub fn lit(&mut self, lit: &Lit) { + match lit { + Lit::Str(lit) => self.lit_str(lit), + Lit::ByteStr(lit) => self.lit_byte_str(lit), + Lit::Byte(lit) => self.lit_byte(lit), + Lit::Char(lit) => self.lit_char(lit), + Lit::Int(lit) => self.lit_int(lit), + Lit::Float(lit) => self.lit_float(lit), + Lit::Bool(lit) => self.lit_bool(lit), + Lit::Verbatim(lit) => self.lit_verbatim(lit), + } + } + + pub fn lit_str(&mut self, lit: &LitStr) { + self.word(lit.token().to_string()); + } + + fn lit_byte_str(&mut self, lit: &LitByteStr) { + self.word(lit.token().to_string()); + } + + fn lit_byte(&mut self, lit: &LitByte) { + self.word(lit.token().to_string()); + } + + fn lit_char(&mut self, lit: &LitChar) { + self.word(lit.token().to_string()); + } + + fn lit_int(&mut self, lit: &LitInt) { + self.word(lit.token().to_string()); + } + + fn lit_float(&mut self, lit: &LitFloat) { + self.word(lit.token().to_string()); + } + + fn lit_bool(&mut self, lit: &LitBool) { + self.word(if lit.value { "true" } else { "false" }); + } + + fn lit_verbatim(&mut self, token: &Literal) { + self.word(token.to_string()); + } +} diff --git a/packages/autofmt/src/prettyplease/mac.rs b/packages/autofmt/src/prettyplease/mac.rs new file mode 100644 index 000000000..4608bc303 --- /dev/null +++ b/packages/autofmt/src/prettyplease/mac.rs @@ -0,0 +1,220 @@ +use super::algorithm::Printer; +use super::token::Token; +use super::INDENT; +use proc_macro2::{Delimiter, Spacing, TokenStream}; +use syn::{Ident, Macro, MacroDelimiter, PathArguments}; + +impl Printer { + pub fn mac(&mut self, mac: &Macro, ident: Option<&Ident>) { + let is_macro_rules = mac.path.leading_colon.is_none() + && mac.path.segments.len() == 1 + && matches!(mac.path.segments[0].arguments, PathArguments::None) + && mac.path.segments[0].ident == "macro_rules"; + if is_macro_rules { + if let Some(ident) = ident { + self.macro_rules(ident, &mac.tokens); + return; + } + } + self.path(&mac.path); + self.word("!"); + if let Some(ident) = ident { + self.nbsp(); + self.ident(ident); + } + let (open, close, delimiter_break) = match mac.delimiter { + MacroDelimiter::Paren(_) => ("(", ")", Self::zerobreak as fn(&mut Self)), + MacroDelimiter::Brace(_) => (" {", "}", Self::hardbreak as fn(&mut Self)), + MacroDelimiter::Bracket(_) => ("[", "]", Self::zerobreak as fn(&mut Self)), + }; + self.word(open); + self.cbox(INDENT); + delimiter_break(self); + self.ibox(0); + self.macro_rules_tokens(mac.tokens.clone(), false); + self.end(); + delimiter_break(self); + self.offset(-INDENT); + self.end(); + self.word(close); + } + + pub fn mac_semi_if_needed(&mut self, delimiter: &MacroDelimiter) { + match delimiter { + MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => self.word(";"), + MacroDelimiter::Brace(_) => {} + } + } + + fn macro_rules(&mut self, name: &Ident, rules: &TokenStream) { + enum State { + Start, + Matcher, + Equal, + Greater, + Expander, + } + + use State::*; + + self.word("macro_rules! "); + self.ident(name); + self.word(" {"); + self.cbox(INDENT); + self.hardbreak_if_nonempty(); + let mut state = State::Start; + for tt in rules.clone() { + let token = Token::from(tt); + match (state, token) { + (Start, Token::Group(delimiter, stream)) => { + self.delimiter_open(delimiter); + if !stream.is_empty() { + self.cbox(INDENT); + self.zerobreak(); + self.ibox(0); + self.macro_rules_tokens(stream, true); + self.end(); + self.zerobreak(); + self.offset(-INDENT); + self.end(); + } + self.delimiter_close(delimiter); + state = Matcher; + } + (Matcher, Token::Punct('=', Spacing::Joint)) => { + self.word(" ="); + state = Equal; + } + (Equal, Token::Punct('>', Spacing::Alone)) => { + self.word(">"); + state = Greater; + } + (Greater, Token::Group(_delimiter, stream)) => { + self.word(" {"); + self.neverbreak(); + if !stream.is_empty() { + self.cbox(INDENT); + self.hardbreak(); + self.ibox(0); + self.macro_rules_tokens(stream, false); + self.end(); + self.hardbreak(); + self.offset(-INDENT); + self.end(); + } + self.word("}"); + state = Expander; + } + (Expander, Token::Punct(';', Spacing::Alone)) => { + self.word(";"); + self.hardbreak(); + state = Start; + } + _ => unimplemented!("bad macro_rules syntax"), + } + } + match state { + Start => {} + Expander => { + self.word(";"); + self.hardbreak(); + } + _ => self.hardbreak(), + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn macro_rules_tokens(&mut self, stream: TokenStream, matcher: bool) { + #[derive(PartialEq)] + enum State { + Start, + Dollar, + DollarIdent, + DollarIdentColon, + DollarParen, + DollarParenSep, + Pound, + PoundBang, + Dot, + Colon, + Colon2, + Ident, + IdentBang, + Delim, + Other, + } + + use State::*; + + let mut state = Start; + let mut previous_is_joint = true; + for tt in stream { + let token = Token::from(tt); + let (needs_space, next_state) = match (&state, &token) { + (Dollar, Token::Ident(_)) => (false, if matcher { DollarIdent } else { Other }), + (DollarIdent, Token::Punct(':', Spacing::Alone)) => (false, DollarIdentColon), + (DollarIdentColon, Token::Ident(_)) => (false, Other), + (DollarParen, Token::Punct('+' | '*' | '?', Spacing::Alone)) => (false, Other), + (DollarParen, Token::Ident(_) | Token::Literal(_)) => (false, DollarParenSep), + (DollarParen, Token::Punct(_, Spacing::Joint)) => (false, DollarParen), + (DollarParen, Token::Punct(_, Spacing::Alone)) => (false, DollarParenSep), + (DollarParenSep, Token::Punct('+' | '*', _)) => (false, Other), + (Pound, Token::Punct('!', _)) => (false, PoundBang), + (Dollar, Token::Group(Delimiter::Parenthesis, _)) => (false, DollarParen), + (Pound | PoundBang, Token::Group(Delimiter::Bracket, _)) => (false, Other), + (Ident, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => { + (false, Delim) + } + (Ident, Token::Punct('!', Spacing::Alone)) => (false, IdentBang), + (IdentBang, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => { + (false, Other) + } + (Colon, Token::Punct(':', _)) => (false, Colon2), + (_, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => (true, Delim), + (_, Token::Group(Delimiter::Brace | Delimiter::None, _)) => (true, Other), + (_, Token::Ident(ident)) if !is_keyword(ident) => { + (state != Dot && state != Colon2, Ident) + } + (_, Token::Literal(_)) => (state != Dot, Ident), + (_, Token::Punct(',' | ';', _)) => (false, Other), + (_, Token::Punct('.', _)) if !matcher => (state != Ident && state != Delim, Dot), + (_, Token::Punct(':', Spacing::Joint)) => (state != Ident, Colon), + (_, Token::Punct('$', _)) => (true, Dollar), + (_, Token::Punct('#', _)) => (true, Pound), + (_, _) => (true, Other), + }; + if !previous_is_joint { + if needs_space { + self.space(); + } else if let Token::Punct('.', _) = token { + self.zerobreak(); + } + } + previous_is_joint = match token { + Token::Punct(_, Spacing::Joint) | Token::Punct('$', _) => true, + _ => false, + }; + self.single_token( + token, + if matcher { + |printer, stream| printer.macro_rules_tokens(stream, true) + } else { + |printer, stream| printer.macro_rules_tokens(stream, false) + }, + ); + state = next_state; + } + } +} + +fn is_keyword(ident: &Ident) -> bool { + match ident.to_string().as_str() { + "as" | "box" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" + | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" + | "move" | "mut" | "pub" | "ref" | "return" | "static" | "struct" | "trait" | "type" + | "unsafe" | "use" | "where" | "while" | "yield" => true, + _ => false, + } +} diff --git a/packages/autofmt/src/prettyplease/mod.rs b/packages/autofmt/src/prettyplease/mod.rs new file mode 100644 index 000000000..68d8e1303 --- /dev/null +++ b/packages/autofmt/src/prettyplease/mod.rs @@ -0,0 +1,381 @@ +//! [![github]](https://github.com/dtolnay/prettyplease) [![crates-io]](https://crates.io/crates/prettyplease) [![docs-rs]](https://docs.rs/prettyplease) +//! +//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github +//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust +//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K +//! +//!
+//! +//! **prettyplease::unparse** — a minimal `syn` syntax tree pretty-printer +//! +//!
+//! +//! # Overview +//! +//! This is a pretty-printer to turn a `syn` syntax tree into a `String` of +//! well-formatted source code. In contrast to rustfmt, this library is intended +//! to be suitable for arbitrary generated code. +//! +//! Rustfmt prioritizes high-quality output that is impeccable enough that you'd +//! be comfortable spending your career staring at its output — but that +//! means some heavyweight algorithms, and it has a tendency to bail out on code +//! that is hard to format (for example [rustfmt#3697], and there are dozens +//! more issues like it). That's not necessarily a big deal for human-generated +//! code because when code gets highly nested, the human will naturally be +//! inclined to refactor into more easily formattable code. But for generated +//! code, having the formatter just give up leaves it totally unreadable. +//! +//! [rustfmt#3697]: https://github.com/rust-lang/rustfmt/issues/3697 +//! +//! This library is designed using the simplest possible algorithm and data +//! structures that can deliver about 95% of the quality of rustfmt-formatted +//! output. In my experience testing real-world code, approximately 97-98% of +//! output lines come out identical between rustfmt's formatting and this +//! crate's. The rest have slightly different linebreak decisions, but still +//! clearly follow the dominant modern Rust style. +//! +//! The tradeoffs made by this crate are a good fit for generated code that you +//! will *not* spend your career staring at. For example, the output of +//! `bindgen`, or the output of `cargo-expand`. In those cases it's more +//! important that the whole thing be formattable without the formatter giving +//! up, than that it be flawless. +//! +//!
+//! +//! # Feature matrix +//! +//! Here are a few superficial comparisons of this crate against the AST +//! pretty-printer built into rustc, and rustfmt. The sections below go into +//! more detail comparing the output of each of these libraries. +//! +//! | | prettyplease | rustc | rustfmt | +//! | --- | --- | --- | --- | +//! | non-pathological behavior on big or generated code | ✅ | ❌ | ❌ | +//! | idiomatic modern formatting ("locally indistinguishable from rustfmt") | ✅ | ❌ | ✅ | +//! | throughput | 60 MB/s | 39 MB/s | 2.8 MB/s | +//! | number of dependencies | 3 | 72 | 66 | +//! | compile time including dependencies | 2.4 sec | 23.1 sec | 29.8 sec | +//! | buildable using a stable Rust compiler | ✅ | ❌ | ❌ | +//! | published to crates.io | ✅ | ❌ | ❌ | +//! | extensively configurable output | ❌ | ❌ | ✅ | +//! | intended to accommodate hand-maintained source code | ❌ | ❌ | ✅ | +//! +//!
+//! +//! # Comparison to rustfmt +//! +//! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) +//! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) +//! - [output.rustfmt.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustfmt.rs) +//! +//! If you weren't told which output file is which, it would be practically +//! impossible to tell — **except** for line 435 in the rustfmt output, +//! which is more than 1000 characters long because rustfmt just gave up +//! formatting that part of the file: +//! +//! ``` +//! # const _: &str = stringify! {{{ +//! match segments[5] { +//! 0 => write!(f, "::{}", ipv4), +//! 0xffff => write!(f, "::ffff:{}", ipv4), +//! _ => unreachable!(), +//! } +//! } else { # [derive (Copy , Clone , Default)] struct Span { start : usize , len : usize , } let zeroes = { let mut longest = Span :: default () ; let mut current = Span :: default () ; for (i , & segment) in segments . iter () . enumerate () { if segment == 0 { if current . len == 0 { current . start = i ; } current . len += 1 ; if current . len > longest . len { longest = current ; } } else { current = Span :: default () ; } } longest } ; # [doc = " Write a colon-separated part of the address"] # [inline] fn fmt_subslice (f : & mut fmt :: Formatter < '_ > , chunk : & [u16]) -> fmt :: Result { if let Some ((first , tail)) = chunk . split_first () { write ! (f , "{:x}" , first) ? ; for segment in tail { f . write_char (':') ? ; write ! (f , "{:x}" , segment) ? ; } } Ok (()) } if zeroes . len > 1 { fmt_subslice (f , & segments [.. zeroes . start]) ? ; f . write_str ("::") ? ; fmt_subslice (f , & segments [zeroes . start + zeroes . len ..]) } else { fmt_subslice (f , & segments) } } +//! } else { +//! const IPV6_BUF_LEN: usize = (4 * 8) + 7; +//! let mut buf = [0u8; IPV6_BUF_LEN]; +//! let mut buf_slice = &mut buf[..]; +//! # }}; +//! ``` +//! +//! This is a pretty typical manifestation of rustfmt bailing out in generated +//! code — a chunk of the input ends up on one line. The other +//! manifestation is that you're working on some code, running rustfmt on save +//! like a conscientious developer, but after a while notice it isn't doing +//! anything. You introduce an intentional formatting issue, like a stray indent +//! or semicolon, and run rustfmt to check your suspicion. Nope, it doesn't get +//! cleaned up — rustfmt is just not formatting the part of the file you +//! are working on. +//! +//! The prettyplease library is designed to have no pathological cases that +//! force a bail out; the entire input you give it will get formatted in some +//! "good enough" form. +//! +//! Separately, rustfmt can be problematic to integrate into projects. It's +//! written using rustc's internal syntax tree, so it can't be built by a stable +//! compiler. Its releases are not regularly published to crates.io, so in Cargo +//! builds you'd need to depend on it as a git dependency, which precludes +//! publishing your crate to crates.io also. You can shell out to a `rustfmt` +//! binary, but that'll be whatever rustfmt version is installed on each +//! developer's system (if any), which can lead to spurious diffs in checked-in +//! generated code formatted by different versions. In contrast prettyplease is +//! designed to be easy to pull in as a library, and compiles fast. +//! +//!
+//! +//! # Comparison to rustc_ast_pretty +//! +//! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) +//! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) +//! - [output.rustc.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustc.rs) +//! +//! This is the pretty-printer that gets used when rustc prints source code, +//! such as `rustc -Zunpretty=expanded`. It's used also by the standard +//! library's `stringify!` when stringifying an interpolated macro_rules AST +//! fragment, like an $:expr, and transitively by `dbg!` and many macros in the +//! ecosystem. +//! +//! Rustc's formatting is mostly okay, but does not hew closely to the dominant +//! contemporary style of Rust formatting. Some things wouldn't ever be written +//! on one line, like this `match` expression, and certainly not with a comma in +//! front of the closing brace: +//! +//! ``` +//! # const _: &str = stringify! { +//! fn eq(&self, other: &IpAddr) -> bool { +//! match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } +//! } +//! # }; +//! ``` +//! +//! Some places use non-multiple-of-4 indentation, which is definitely not the +//! norm: +//! +//! ``` +//! # const _: &str = stringify! { +//! pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { +//! let [a, b, c, d] = self.octets(); +//! Ipv6Addr{inner: +//! c::in6_addr{s6_addr: +//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, +//! 0xFF, a, b, c, d],},} +//! } +//! # }; +//! ``` +//! +//! And although there isn't an egregious example of it in the link because the +//! input code is pretty tame, in general rustc_ast_pretty has pathological +//! behavior on generated code. It has a tendency to use excessive horizontal +//! indentation and rapidly run out of width: +//! +//! ``` +//! # const _: &str = stringify! { +//! ::std::io::_print(::core::fmt::Arguments::new_v1(&[""], +//! &match (&msg,) { +//! _args => +//! [::core::fmt::ArgumentV1::new(_args.0, +//! ::core::fmt::Display::fmt)], +//! })); +//! # }; +//! ``` +//! +//! The snippets above are clearly different from modern rustfmt style. In +//! contrast, prettyplease is designed to have output that is practically +//! indistinguishable from rustfmt-formatted code. +//! +//!
+//! +//! # Example +//! +//! ``` +//! // [dependencies] +//! // prettyplease = "0.1" +//! // syn = { version = "1", default-features = false, features = ["full", "parsing"] } +//! +//! const INPUT: &str = stringify! { +//! use super::{ +//! lazy::{Lazy, SyncLazy, SyncOnceCell}, panic, +//! sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, +//! mpsc::channel, Mutex, }, +//! thread, +//! }; +//! impl Into for T where U: From { +//! fn into(self) -> U { U::from(self) } +//! } +//! }; +//! +//! fn main() { +//! let syntax_tree = syn::parse_file(INPUT).unwrap(); +//! let formatted = prettyplease::unparse(&syntax_tree); +//! print!("{}", formatted); +//! } +//! ``` +//! +//!
+//! +//! # Algorithm notes +//! +//! The approach and terminology used in the implementation are derived from +//! [*Derek C. Oppen, "Pretty Printing" (1979)*][paper], on which +//! rustc_ast_pretty is also based, and from rustc_ast_pretty's implementation +//! written by Graydon Hoare in 2011 (and modernized over the years by dozens of +//! volunteer maintainers). +//! +//! [paper]: http://i.stanford.edu/pub/cstr/reports/cs/tr/79/770/CS-TR-79-770.pdf +//! +//! The paper describes two language-agnostic interacting procedures `Scan()` +//! and `Print()`. Language-specific code decomposes an input data structure +//! into a stream of `string` and `break` tokens, and `begin` and `end` tokens +//! for grouping. Each `begin`–`end` range may be identified as either +//! "consistent breaking" or "inconsistent breaking". If a group is consistently +//! breaking, then if the whole contents do not fit on the line, *every* `break` +//! token in the group will receive a linebreak. This is appropriate, for +//! example, for Rust struct literals, or arguments of a function call. If a +//! group is inconsistently breaking, then the `string` tokens in the group are +//! greedily placed on the line until out of space, and linebroken only at those +//! `break` tokens for which the next string would not fit. For example, this is +//! appropriate for the contents of a braced `use` statement in Rust. +//! +//! Scan's job is to efficiently accumulate sizing information about groups and +//! breaks. For every `begin` token we compute the distance to the matched `end` +//! token, and for every `break` we compute the distance to the next `break`. +//! The algorithm uses a ringbuffer to hold tokens whose size is not yet +//! ascertained. The maximum size of the ringbuffer is bounded by the target +//! line length and does not grow indefinitely, regardless of deep nesting in +//! the input stream. That's because once a group is sufficiently big, the +//! precise size can no longer make a difference to linebreak decisions and we +//! can effectively treat it as "infinity". +//! +//! Print's job is to use the sizing information to efficiently assign a +//! "broken" or "not broken" status to every `begin` token. At that point the +//! output is easily constructed by concatenating `string` tokens and breaking +//! at `break` tokens contained within a broken group. +//! +//! Leveraging these primitives (i.e. cleverly placing the all-or-nothing +//! consistent breaks and greedy inconsistent breaks) to yield +//! rustfmt-compatible formatting for all of Rust's syntax tree nodes is a fun +//! challenge. +//! +//! Here is a visualization of some Rust tokens fed into the pretty printing +//! algorithm. Consistently breaking `begin`—`end` pairs are represented +//! by `«`⁠`»`, inconsistently breaking by `‹`⁠`›`, `break` by `·`, +//! and the rest of the non-whitespace are `string`. +//! +//! ```text +//! use super::«{· +//! ‹ lazy::«{·‹Lazy,· SyncLazy,· SyncOnceCell›·}»,· +//! panic,· +//! sync::«{· +//! ‹ atomic::«{·‹AtomicUsize,· Ordering::SeqCst›·}»,· +//! mpsc::channel,· Mutex›,· +//! }»,· +//! thread›,· +//! }»;· +//! «‹«impl<«·T‹›,· U‹›·»>» Into<«·U·»>· for T›· +//! where· +//! U:‹ From<«·T·»>›,· +//! {· +//! « fn into(·«·self·») -> U {· +//! ‹ U::from(«·self·»)›· +//! » }· +//! »}· +//! ``` +//! +//! The algorithm described in the paper is not quite sufficient for producing +//! well-formatted Rust code that is locally indistinguishable from rustfmt's +//! style. The reason is that in the paper, the complete non-whitespace contents +//! are assumed to be independent of linebreak decisions, with Scan and Print +//! being only in control of the whitespace (spaces and line breaks). In Rust as +//! idiomatically formattted by rustfmt, that is not the case. Trailing commas +//! are one example; the punctuation is only known *after* the broken vs +//! non-broken status of the surrounding group is known: +//! +//! ``` +//! # struct Struct { x: u64, y: bool } +//! # let xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = 0; +//! # let yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy = true; +//! # +//! let _ = Struct { x: 0, y: true }; +//! +//! let _ = Struct { +//! x: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, +//! y: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, //<- trailing comma if the expression wrapped +//! }; +//! ``` +//! +//! The formatting of `match` expressions is another case; we want small arms on +//! the same line as the pattern, and big arms wrapped in a brace. The presence +//! of the brace punctuation, comma, and semicolon are all dependent on whether +//! the arm fits on the line: +//! +//! ``` +//! # struct Entry { nanos: u32 } +//! # let total_nanos = 0u64; +//! # let mut total_secs = 0u64; +//! # let tmp; +//! # let entry = Entry { nanos: 0 }; +//! # const NANOS_PER_SEC: u32 = 1_000_000_000; +//! # +//! match total_nanos.checked_add(entry.nanos as u64) { +//! Some(n) => tmp = n, //<- small arm, inline with comma +//! None => { +//! total_secs = total_secs +//! .checked_add(total_nanos / NANOS_PER_SEC as u64) +//! .expect("overflow in iter::sum over durations"); +//! } //<- big arm, needs brace added, and also semicolon^ +//! } +//! ``` +//! +//! The printing algorithm implementation in this crate accommodates all of +//! these situations with conditional punctuation tokens whose selection can be +//! deferred and populated after it's known that the group is or is not broken. + +#![allow( + clippy::cast_possible_wrap, + clippy::cast_sign_loss, + clippy::doc_markdown, + clippy::enum_glob_use, + clippy::items_after_statements, + clippy::match_like_matches_macro, + clippy::match_same_arms, + clippy::module_name_repetitions, + clippy::must_use_candidate, + clippy::needless_pass_by_value, + clippy::similar_names, + clippy::unused_self, + clippy::vec_init_then_push +)] +#![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))] + +mod algorithm; +mod attr; +mod convenience; +mod data; +mod expr; +mod file; +mod generics; +mod item; +mod iter; +mod lifetime; +mod lit; +mod mac; +mod pat; +mod path; +mod ring; +mod stmt; +mod token; +mod ty; + +use algorithm::Printer; +use syn::{Expr, File}; + +// Target line width. +const MARGIN: isize = 89; + +// Number of spaces increment at each level of block indentation. +const INDENT: isize = 4; + +// Every line is allowed at least this much space, even if highly indented. +const MIN_SPACE: isize = 60; + +pub fn unparse(file: &File) -> String { + let mut p = Printer::new(); + p.file(file); + p.eof() +} + +pub fn unparse_expr(file: &Expr) -> String { + let mut p = Printer::new(); + p.expr(file); + p.eof() +} diff --git a/packages/autofmt/src/prettyplease/pat.rs b/packages/autofmt/src/prettyplease/pat.rs new file mode 100644 index 000000000..0ca86ef06 --- /dev/null +++ b/packages/autofmt/src/prettyplease/pat.rs @@ -0,0 +1,210 @@ +use super::algorithm::Printer; +use super::iter::IterDelimited; +use super::INDENT; +use proc_macro2::TokenStream; +use syn::{ + FieldPat, Pat, PatBox, PatIdent, PatLit, PatMacro, PatOr, PatPath, PatRange, PatReference, + PatRest, PatSlice, PatStruct, PatTuple, PatTupleStruct, PatType, PatWild, RangeLimits, +}; + +impl Printer { + pub fn pat(&mut self, pat: &Pat) { + match pat { + Pat::Box(pat) => self.pat_box(pat), + Pat::Ident(pat) => self.pat_ident(pat), + Pat::Lit(pat) => self.pat_lit(pat), + Pat::Macro(pat) => self.pat_macro(pat), + Pat::Or(pat) => self.pat_or(pat), + Pat::Path(pat) => self.pat_path(pat), + Pat::Range(pat) => self.pat_range(pat), + Pat::Reference(pat) => self.pat_reference(pat), + Pat::Rest(pat) => self.pat_rest(pat), + Pat::Slice(pat) => self.pat_slice(pat), + Pat::Struct(pat) => self.pat_struct(pat), + Pat::Tuple(pat) => self.pat_tuple(pat), + Pat::TupleStruct(pat) => self.pat_tuple_struct(pat), + Pat::Type(pat) => self.pat_type(pat), + Pat::Verbatim(pat) => self.pat_verbatim(pat), + Pat::Wild(pat) => self.pat_wild(pat), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Pat"), + } + } + + fn pat_box(&mut self, pat: &PatBox) { + self.outer_attrs(&pat.attrs); + self.word("box "); + self.pat(&pat.pat); + } + + fn pat_ident(&mut self, pat: &PatIdent) { + self.outer_attrs(&pat.attrs); + if pat.by_ref.is_some() { + self.word("ref "); + } + if pat.mutability.is_some() { + self.word("mut "); + } + self.ident(&pat.ident); + if let Some((_at_token, subpat)) = &pat.subpat { + self.word(" @ "); + self.pat(subpat); + } + } + + fn pat_lit(&mut self, pat: &PatLit) { + self.outer_attrs(&pat.attrs); + self.expr(&pat.expr); + } + + fn pat_macro(&mut self, pat: &PatMacro) { + self.outer_attrs(&pat.attrs); + self.mac(&pat.mac, None); + } + + fn pat_or(&mut self, pat: &PatOr) { + self.outer_attrs(&pat.attrs); + let mut consistent_break = false; + for case in &pat.cases { + match case { + Pat::Lit(_) | Pat::Wild(_) => {} + _ => { + consistent_break = true; + break; + } + } + } + if consistent_break { + self.cbox(0); + } else { + self.ibox(0); + } + for case in pat.cases.iter().delimited() { + if !case.is_first { + self.space(); + self.word("| "); + } + self.pat(&case); + } + self.end(); + } + + fn pat_path(&mut self, pat: &PatPath) { + self.outer_attrs(&pat.attrs); + self.qpath(&pat.qself, &pat.path); + } + + fn pat_range(&mut self, pat: &PatRange) { + self.outer_attrs(&pat.attrs); + self.expr(&pat.lo); + match &pat.limits { + RangeLimits::HalfOpen(_) => self.word(".."), + RangeLimits::Closed(_) => self.word("..="), + } + self.expr(&pat.hi); + } + + fn pat_reference(&mut self, pat: &PatReference) { + self.outer_attrs(&pat.attrs); + self.word("&"); + if pat.mutability.is_some() { + self.word("mut "); + } + self.pat(&pat.pat); + } + + fn pat_rest(&mut self, pat: &PatRest) { + self.outer_attrs(&pat.attrs); + self.word(".."); + } + + fn pat_slice(&mut self, pat: &PatSlice) { + self.outer_attrs(&pat.attrs); + self.word("["); + for elem in pat.elems.iter().delimited() { + self.pat(&elem); + self.trailing_comma(elem.is_last); + } + self.word("]"); + } + + fn pat_struct(&mut self, pat: &PatStruct) { + self.outer_attrs(&pat.attrs); + self.cbox(INDENT); + self.path(&pat.path); + self.word(" {"); + self.space_if_nonempty(); + for field in pat.fields.iter().delimited() { + self.field_pat(&field); + self.trailing_comma_or_space(field.is_last && pat.dot2_token.is_none()); + } + if pat.dot2_token.is_some() { + self.word(".."); + self.space(); + } + self.offset(-INDENT); + self.end(); + self.word("}"); + } + + fn pat_tuple(&mut self, pat: &PatTuple) { + self.outer_attrs(&pat.attrs); + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in pat.elems.iter().delimited() { + self.pat(&elem); + if pat.elems.len() == 1 { + if pat.elems.trailing_punct() { + self.word(","); + } + self.zerobreak(); + } else { + self.trailing_comma(elem.is_last); + } + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + fn pat_tuple_struct(&mut self, pat: &PatTupleStruct) { + self.outer_attrs(&pat.attrs); + self.path(&pat.path); + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in pat.pat.elems.iter().delimited() { + self.pat(&elem); + self.trailing_comma(elem.is_last); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + pub fn pat_type(&mut self, pat: &PatType) { + self.outer_attrs(&pat.attrs); + self.pat(&pat.pat); + self.word(": "); + self.ty(&pat.ty); + } + + fn pat_verbatim(&mut self, pat: &TokenStream) { + unimplemented!("Pat::Verbatim `{}`", pat); + } + + fn pat_wild(&mut self, pat: &PatWild) { + self.outer_attrs(&pat.attrs); + self.word("_"); + } + + fn field_pat(&mut self, field_pat: &FieldPat) { + self.outer_attrs(&field_pat.attrs); + if field_pat.colon_token.is_some() { + self.member(&field_pat.member); + self.word(": "); + } + self.pat(&field_pat.pat); + } +} diff --git a/packages/autofmt/src/prettyplease/path.rs b/packages/autofmt/src/prettyplease/path.rs new file mode 100644 index 000000000..460fe5cb7 --- /dev/null +++ b/packages/autofmt/src/prettyplease/path.rs @@ -0,0 +1,181 @@ +use super::algorithm::Printer; +use super::iter::IterDelimited; +use super::INDENT; +use syn::{ + AngleBracketedGenericArguments, Binding, Constraint, Expr, GenericArgument, + ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf, +}; + +impl Printer { + pub fn path(&mut self, path: &Path) { + assert!(!path.segments.is_empty()); + for segment in path.segments.iter().delimited() { + if !segment.is_first || path.leading_colon.is_some() { + self.word("::"); + } + self.path_segment(&segment); + } + } + + pub fn path_segment(&mut self, segment: &PathSegment) { + self.ident(&segment.ident); + self.path_arguments(&segment.arguments); + } + + fn path_arguments(&mut self, arguments: &PathArguments) { + match arguments { + PathArguments::None => {} + PathArguments::AngleBracketed(arguments) => { + self.angle_bracketed_generic_arguments(arguments); + } + PathArguments::Parenthesized(arguments) => { + self.parenthesized_generic_arguments(arguments); + } + } + } + + fn generic_argument(&mut self, arg: &GenericArgument) { + match arg { + GenericArgument::Lifetime(lifetime) => self.lifetime(lifetime), + GenericArgument::Type(ty) => self.ty(ty), + GenericArgument::Binding(binding) => self.binding(binding), + GenericArgument::Constraint(constraint) => self.constraint(constraint), + GenericArgument::Const(expr) => { + match expr { + Expr::Lit(expr) => self.expr_lit(expr), + Expr::Block(expr) => self.expr_block(expr), + // ERROR CORRECTION: Add braces to make sure that the + // generated code is valid. + _ => { + self.word("{"); + self.expr(expr); + self.word("}"); + } + } + } + } + } + + fn angle_bracketed_generic_arguments(&mut self, generic: &AngleBracketedGenericArguments) { + if generic.colon2_token.is_some() { + self.word("::"); + } + self.word("<"); + self.cbox(INDENT); + self.zerobreak(); + + // Print lifetimes before types and consts, all before bindings, + // regardless of their order in self.args. + // + // TODO: ordering rules for const arguments vs type arguments have + // not been settled yet. https://github.com/rust-lang/rust/issues/44580 + for arg in generic.args.iter().delimited() { + match *arg { + GenericArgument::Lifetime(_) => { + self.generic_argument(&arg); + self.trailing_comma(arg.is_last); + } + GenericArgument::Type(_) + | GenericArgument::Binding(_) + | GenericArgument::Constraint(_) + | GenericArgument::Const(_) => {} + } + } + for arg in generic.args.iter().delimited() { + match *arg { + GenericArgument::Type(_) | GenericArgument::Const(_) => { + self.generic_argument(&arg); + self.trailing_comma(arg.is_last); + } + GenericArgument::Lifetime(_) + | GenericArgument::Binding(_) + | GenericArgument::Constraint(_) => {} + } + } + for arg in generic.args.iter().delimited() { + match *arg { + GenericArgument::Binding(_) | GenericArgument::Constraint(_) => { + self.generic_argument(&arg); + self.trailing_comma(arg.is_last); + } + GenericArgument::Lifetime(_) + | GenericArgument::Type(_) + | GenericArgument::Const(_) => {} + } + } + + self.offset(-INDENT); + self.end(); + self.word(">"); + } + + fn binding(&mut self, binding: &Binding) { + self.ident(&binding.ident); + self.word(" = "); + self.ty(&binding.ty); + } + + fn constraint(&mut self, constraint: &Constraint) { + self.ident(&constraint.ident); + self.ibox(INDENT); + for bound in constraint.bounds.iter().delimited() { + if bound.is_first { + self.word(": "); + } else { + self.space(); + self.word("+ "); + } + self.type_param_bound(&bound); + } + self.end(); + } + + fn parenthesized_generic_arguments(&mut self, arguments: &ParenthesizedGenericArguments) { + self.cbox(INDENT); + self.word("("); + self.zerobreak(); + for ty in arguments.inputs.iter().delimited() { + self.ty(&ty); + self.trailing_comma(ty.is_last); + } + self.offset(-INDENT); + self.word(")"); + self.return_type(&arguments.output); + self.end(); + } + + pub fn qpath(&mut self, qself: &Option, path: &Path) { + let qself = match qself { + Some(qself) => qself, + None => { + self.path(path); + return; + } + }; + + assert!(qself.position < path.segments.len()); + + self.word("<"); + self.ty(&qself.ty); + + let mut segments = path.segments.iter(); + if qself.position > 0 { + self.word(" as "); + for segment in segments.by_ref().take(qself.position).delimited() { + if !segment.is_first || path.leading_colon.is_some() { + self.word("::"); + } + self.path_segment(&segment); + if segment.is_last { + self.word(">"); + } + } + } else { + self.word(">"); + } + for segment in segments { + self.word("::"); + self.path_segment(segment); + } + } +} diff --git a/packages/autofmt/src/prettyplease/ring.rs b/packages/autofmt/src/prettyplease/ring.rs new file mode 100644 index 000000000..aff92589a --- /dev/null +++ b/packages/autofmt/src/prettyplease/ring.rs @@ -0,0 +1,81 @@ +use std::collections::VecDeque; +use std::ops::{Index, IndexMut}; + +pub struct RingBuffer { + data: VecDeque, + // Abstract index of data[0] in infinitely sized queue + offset: usize, +} + +impl RingBuffer { + pub fn new() -> Self { + RingBuffer { + data: VecDeque::new(), + offset: 0, + } + } + + pub fn is_empty(&self) -> bool { + self.data.is_empty() + } + + pub fn len(&self) -> usize { + self.data.len() + } + + pub fn push(&mut self, value: T) -> usize { + let index = self.offset + self.data.len(); + self.data.push_back(value); + index + } + + pub fn clear(&mut self) { + self.data.clear(); + } + + pub fn index_of_first(&self) -> usize { + self.offset + } + + pub fn first(&self) -> &T { + &self.data[0] + } + + pub fn first_mut(&mut self) -> &mut T { + &mut self.data[0] + } + + pub fn pop_first(&mut self) -> T { + self.offset += 1; + self.data.pop_front().unwrap() + } + + pub fn last(&self) -> &T { + self.data.back().unwrap() + } + + pub fn last_mut(&mut self) -> &mut T { + self.data.back_mut().unwrap() + } + + pub fn second_last(&self) -> &T { + &self.data[self.data.len() - 2] + } + + pub fn pop_last(&mut self) { + self.data.pop_back().unwrap(); + } +} + +impl Index for RingBuffer { + type Output = T; + fn index(&self, index: usize) -> &Self::Output { + &self.data[index.checked_sub(self.offset).unwrap()] + } +} + +impl IndexMut for RingBuffer { + fn index_mut(&mut self, index: usize) -> &mut Self::Output { + &mut self.data[index.checked_sub(self.offset).unwrap()] + } +} diff --git a/packages/autofmt/src/prettyplease/stmt.rs b/packages/autofmt/src/prettyplease/stmt.rs new file mode 100644 index 000000000..68295fa70 --- /dev/null +++ b/packages/autofmt/src/prettyplease/stmt.rs @@ -0,0 +1,85 @@ +use super::algorithm::Printer; +use syn::{Expr, Stmt}; + +impl Printer { + pub fn stmt(&mut self, stmt: &Stmt) { + match stmt { + Stmt::Local(local) => { + self.outer_attrs(&local.attrs); + self.ibox(0); + self.word("let "); + self.pat(&local.pat); + if let Some((_eq, init)) = &local.init { + self.word(" = "); + self.neverbreak(); + self.expr(init); + } + self.word(";"); + self.end(); + self.hardbreak(); + } + Stmt::Item(item) => self.item(item), + Stmt::Expr(expr) => { + if break_after(expr) { + self.ibox(0); + self.expr_beginning_of_line(expr, true); + if add_semi(expr) { + self.word(";"); + } + self.end(); + self.hardbreak(); + } else { + self.expr_beginning_of_line(expr, true); + } + } + Stmt::Semi(expr, _semi) => { + if let Expr::Verbatim(tokens) = expr { + if tokens.is_empty() { + return; + } + } + self.ibox(0); + self.expr_beginning_of_line(expr, true); + if !remove_semi(expr) { + self.word(";"); + } + self.end(); + self.hardbreak(); + } + } + } +} + +pub fn add_semi(expr: &Expr) -> bool { + match expr { + Expr::Assign(_) + | Expr::AssignOp(_) + | Expr::Break(_) + | Expr::Continue(_) + | Expr::Return(_) + | Expr::Yield(_) => true, + Expr::Group(group) => add_semi(&group.expr), + _ => false, + } +} + +pub fn break_after(expr: &Expr) -> bool { + if let Expr::Group(group) = expr { + if let Expr::Verbatim(verbatim) = group.expr.as_ref() { + return !verbatim.is_empty(); + } + } + true +} + +fn remove_semi(expr: &Expr) -> bool { + match expr { + Expr::ForLoop(_) | Expr::While(_) => true, + Expr::Group(group) => remove_semi(&group.expr), + Expr::If(expr) => match &expr.else_branch { + Some((_else_token, else_branch)) => remove_semi(else_branch), + None => true, + }, + _ => false, + } +} diff --git a/packages/autofmt/src/prettyplease/token.rs b/packages/autofmt/src/prettyplease/token.rs new file mode 100644 index 000000000..f25f0d3c8 --- /dev/null +++ b/packages/autofmt/src/prettyplease/token.rs @@ -0,0 +1,80 @@ +use super::algorithm::Printer; +use proc_macro2::{Delimiter, Ident, Literal, Spacing, TokenStream, TokenTree}; + +impl Printer { + pub fn single_token(&mut self, token: Token, group_contents: fn(&mut Self, TokenStream)) { + match token { + Token::Group(delimiter, stream) => self.token_group(delimiter, stream, group_contents), + Token::Ident(ident) => self.ident(&ident), + Token::Punct(ch, _spacing) => self.token_punct(ch), + Token::Literal(literal) => self.token_literal(&literal), + } + } + + fn token_group( + &mut self, + delimiter: Delimiter, + stream: TokenStream, + group_contents: fn(&mut Self, TokenStream), + ) { + self.delimiter_open(delimiter); + if !stream.is_empty() { + if delimiter == Delimiter::Brace { + self.space(); + } + group_contents(self, stream); + if delimiter == Delimiter::Brace { + self.space(); + } + } + self.delimiter_close(delimiter); + } + + pub fn ident(&mut self, ident: &Ident) { + self.word(ident.to_string()); + } + + pub fn token_punct(&mut self, ch: char) { + self.word(ch.to_string()); + } + + pub fn token_literal(&mut self, literal: &Literal) { + self.word(literal.to_string()); + } + + pub fn delimiter_open(&mut self, delimiter: Delimiter) { + self.word(match delimiter { + Delimiter::Parenthesis => "(", + Delimiter::Brace => "{", + Delimiter::Bracket => "[", + Delimiter::None => return, + }); + } + + pub fn delimiter_close(&mut self, delimiter: Delimiter) { + self.word(match delimiter { + Delimiter::Parenthesis => ")", + Delimiter::Brace => "}", + Delimiter::Bracket => "]", + Delimiter::None => return, + }); + } +} + +pub enum Token { + Group(Delimiter, TokenStream), + Ident(Ident), + Punct(char, Spacing), + Literal(Literal), +} + +impl From for Token { + fn from(tt: TokenTree) -> Self { + match tt { + TokenTree::Group(group) => Token::Group(group.delimiter(), group.stream()), + TokenTree::Ident(ident) => Token::Ident(ident), + TokenTree::Punct(punct) => Token::Punct(punct.as_char(), punct.spacing()), + TokenTree::Literal(literal) => Token::Literal(literal), + } + } +} diff --git a/packages/autofmt/src/prettyplease/ty.rs b/packages/autofmt/src/prettyplease/ty.rs new file mode 100644 index 000000000..93cb48c77 --- /dev/null +++ b/packages/autofmt/src/prettyplease/ty.rs @@ -0,0 +1,202 @@ +use super::algorithm::Printer; +use super::iter::IterDelimited; +use super::INDENT; +use proc_macro2::TokenStream; +use syn::{ + Abi, BareFnArg, ReturnType, Type, TypeArray, TypeBareFn, TypeGroup, TypeImplTrait, TypeInfer, + TypeMacro, TypeNever, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTraitObject, + TypeTuple, Variadic, +}; + +impl Printer { + pub fn ty(&mut self, ty: &Type) { + match ty { + Type::Array(ty) => self.type_array(ty), + Type::BareFn(ty) => self.type_bare_fn(ty), + Type::Group(ty) => self.type_group(ty), + Type::ImplTrait(ty) => self.type_impl_trait(ty), + Type::Infer(ty) => self.type_infer(ty), + Type::Macro(ty) => self.type_macro(ty), + Type::Never(ty) => self.type_never(ty), + Type::Paren(ty) => self.type_paren(ty), + Type::Path(ty) => self.type_path(ty), + Type::Ptr(ty) => self.type_ptr(ty), + Type::Reference(ty) => self.type_reference(ty), + Type::Slice(ty) => self.type_slice(ty), + Type::TraitObject(ty) => self.type_trait_object(ty), + Type::Tuple(ty) => self.type_tuple(ty), + Type::Verbatim(ty) => self.type_verbatim(ty), + #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] + _ => unimplemented!("unknown Type"), + } + } + + fn type_array(&mut self, ty: &TypeArray) { + self.word("["); + self.ty(&ty.elem); + self.word("; "); + self.expr(&ty.len); + self.word("]"); + } + + fn type_bare_fn(&mut self, ty: &TypeBareFn) { + if let Some(bound_lifetimes) = &ty.lifetimes { + self.bound_lifetimes(bound_lifetimes); + } + if ty.unsafety.is_some() { + self.word("unsafe "); + } + if let Some(abi) = &ty.abi { + self.abi(abi); + } + self.word("fn("); + self.cbox(INDENT); + self.zerobreak(); + for bare_fn_arg in ty.inputs.iter().delimited() { + self.bare_fn_arg(&bare_fn_arg); + self.trailing_comma(bare_fn_arg.is_last && ty.variadic.is_none()); + } + if let Some(variadic) = &ty.variadic { + self.variadic(variadic); + self.zerobreak(); + } + self.offset(-INDENT); + self.end(); + self.word(")"); + self.return_type(&ty.output); + } + + fn type_group(&mut self, ty: &TypeGroup) { + self.ty(&ty.elem); + } + + fn type_impl_trait(&mut self, ty: &TypeImplTrait) { + self.word("impl "); + for type_param_bound in ty.bounds.iter().delimited() { + if !type_param_bound.is_first { + self.word(" + "); + } + self.type_param_bound(&type_param_bound); + } + } + + fn type_infer(&mut self, ty: &TypeInfer) { + let _ = ty; + self.word("_"); + } + + fn type_macro(&mut self, ty: &TypeMacro) { + self.mac(&ty.mac, None); + } + + fn type_never(&mut self, ty: &TypeNever) { + let _ = ty; + self.word("!"); + } + + fn type_paren(&mut self, ty: &TypeParen) { + self.word("("); + self.ty(&ty.elem); + self.word(")"); + } + + fn type_path(&mut self, ty: &TypePath) { + self.qpath(&ty.qself, &ty.path); + } + + fn type_ptr(&mut self, ty: &TypePtr) { + self.word("*"); + if ty.mutability.is_some() { + self.word("mut "); + } else { + self.word("const "); + } + self.ty(&ty.elem); + } + + fn type_reference(&mut self, ty: &TypeReference) { + self.word("&"); + if let Some(lifetime) = &ty.lifetime { + self.lifetime(lifetime); + self.nbsp(); + } + if ty.mutability.is_some() { + self.word("mut "); + } + self.ty(&ty.elem); + } + + fn type_slice(&mut self, ty: &TypeSlice) { + self.word("["); + self.ty(&ty.elem); + self.word("]"); + } + + fn type_trait_object(&mut self, ty: &TypeTraitObject) { + self.word("dyn "); + for type_param_bound in ty.bounds.iter().delimited() { + if !type_param_bound.is_first { + self.word(" + "); + } + self.type_param_bound(&type_param_bound); + } + } + + fn type_tuple(&mut self, ty: &TypeTuple) { + self.word("("); + self.cbox(INDENT); + self.zerobreak(); + for elem in ty.elems.iter().delimited() { + self.ty(&elem); + if ty.elems.len() == 1 { + self.word(","); + self.zerobreak(); + } else { + self.trailing_comma(elem.is_last); + } + } + self.offset(-INDENT); + self.end(); + self.word(")"); + } + + fn type_verbatim(&mut self, ty: &TokenStream) { + if ty.to_string() == "..." { + self.word("..."); + } else { + unimplemented!("Type::Verbatim `{}`", ty); + } + } + + pub fn return_type(&mut self, ty: &ReturnType) { + match ty { + ReturnType::Default => {} + ReturnType::Type(_arrow, ty) => { + self.word(" -> "); + self.ty(ty); + } + } + } + + fn bare_fn_arg(&mut self, bare_fn_arg: &BareFnArg) { + self.outer_attrs(&bare_fn_arg.attrs); + if let Some((name, _colon)) = &bare_fn_arg.name { + self.ident(name); + self.word(": "); + } + self.ty(&bare_fn_arg.ty); + } + + fn variadic(&mut self, variadic: &Variadic) { + self.outer_attrs(&variadic.attrs); + self.word("..."); + } + + pub fn abi(&mut self, abi: &Abi) { + self.word("extern "); + if let Some(name) = &abi.name { + self.lit_str(name); + self.nbsp(); + } + } +} diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs new file mode 100644 index 000000000..736abc7dc --- /dev/null +++ b/packages/autofmt/tests/sink.rs @@ -0,0 +1,471 @@ +use dioxus_autofmt::*; +use proc_macro2::TokenStream as TokenStream2; +use syn::{Attribute, Meta}; + +#[test] +fn formats_block() { + let block = r#" + div { + div { + class: "asd", + class: "asd",class: "asd",class: "asd",class: "asd",class: "asd", + key: "ddd", + onclick: move |_| { + let blah = 120; + true + }, + blah: 123, + onclick: move |_| { + let blah = 120; + true + }, + onclick: move |_| { + let blah = 120; + true + }, + onclick: move |_| { + let blah = 120; + true + }, + + div { + div { + "hi" + } + h2 { + class: "asd", + } + } + } + } + "#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn parse_comment() { + let block = r#" + div { + adsasd: "asd", // this is a comment + } + "#; + + let parsed: TokenStream2 = syn::parse_str(block).unwrap(); + + dbg!(parsed); +} + +#[test] +fn formats_component() { + let block = r#" + Component { + adsasd: "asd", // this is a comment + onclick: move |_| { + let blah = 120; + let blah = 120; + }, + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element() { + let block = r#" + div { + a: "1234567891012345678910123456789101234567891012345678910123456789101234567891012345678910123456789101234567891012345678910", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_short() { + let block = r#" + div { + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_nested() { + let block = r#" + h3 { + class: "mb-2 text-xl font-bold", + "Invite Member" + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_nested_no_trailing_tabs() { + let block = r#" + img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "", } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_with_correct_indent() { + let block = r###" + div { + + a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", + "Send invitation" + } + } + +"###; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn small_elements_and_text_are_small() { + let block = r###" + a { class: " text-white", + "Send invitation" + } +"###; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_component_man_props() { + let block = r#" + Component { + ..MyProps { + val: 123 + }, + adsasd: "asd", // this is a comment + onclick: move |_| { + let blah = 120; + }, + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_component_tiny() { + let block = r#" + Component { a: 123 + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_exprs() { + let block = r#" + ul { + (0..10).map(|f| rsx!{ + li { + "hi" + } + }) + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_exprs_neg_indent() { + let block = r#" + ul { + (0..10).map(|f| rsx!{ + li { + "hi" + } + }) + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_exprs_handlers() { + let block = r#" + button { + class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded", + onclick: move |evt| { + show_user_menu.set(!show_user_menu.get()); evt.cancel_bubble(); }, + + onclick: move |evt| + + show_user_menu.set(!show_user_menu.get()), + span { class: "inline-block mr-4", + icons::icon_14 {} + } + span { "Settings" } + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_complex() { + let block = r#" + li { + Link { + class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", + to: "{to}", + span { class: "inline-block mr-3", + icons::icon_0 {} + } + span { "{name}" } + children.is_some().then(|| rsx!{ + span { + class: "inline-block ml-auto hover:bg-gray-500", + onclick: move |evt| { + // open.set(!open.get()); + evt.cancel_bubble(); + }, + icons::icon_8 {} + } + }) + } + div { + class: "px-4", + is_current.then(|| rsx!{ children }) + // open.then(|| rsx!{ children }) + } + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_document() { + let block = r#" +rsx!{ + Component { + adsasd: "asd", // this is a comment + onclick: move |_| { + let blah = 120; + }, + } +} + + +"#; + + let formatted = get_format_blocks(block); + + print!("{formatted:?}"); +} + +#[test] +fn component_path_mod_style() { + let block = r#" +rsx!{ + my::thing::Component { + adsasd: "asd", // this is a comment + onclick: move |_| { + let blah = 120; + }, + } +} +"#; + + let formatted = get_format_blocks(block); + + print!("{formatted:?}"); +} + +#[test] +fn formats_valid_rust_src() { + let src = r#" +// +rsx! { + div {} + div { + h3 {"asd" + } + } +} +"#; + + let formatted = get_format_blocks(src); + + println!("{formatted:?}"); +} + +#[test] +fn formats_valid_rust_src_with_indents() { + let mut src = r#" +#[inline_props] +fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) -> Element { + const ICON_SIZE: u32 = 36; + + rsx! { + div { + + h1 {"thing"} + + + } + + + + + } +} +"# + .to_string(); + + let formatted = get_format_blocks(&src); + + let block = formatted.into_iter().next().unwrap(); + + src.replace_range( + block.start - 1..block.end + 1, + &format!("{{ {} }}", &block.formatted), + ); +} + +#[test] +fn formats_multiple_blocks() { + let mut src = r#" +#[inline_props] +fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) -> Element { + const ICON_SIZE: u32 = 36; + + rsx! { + div { + + h1 {"thing"} + + + } + + + } + + rsx! { + div { + + Ball { + a: rsx!{ + "asdasd" + } + } + } + } +} +#[inline_props] +fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) -> Element { + const ICON_SIZE: u32 = 36; + + rsx! { + div { + + h1 {"thing"} + + + } + + + } + + rsx! { + div { + + Ball { + a: rsx!{ + "asdasd" + } + } + } + } +} +"# + .to_string(); + + let formatted = get_format_blocks(&src); + + dbg!(&formatted); + + let block = formatted.into_iter().next().unwrap(); + + src.replace_range( + block.start - 1..block.end + 1, + &format!("{{ {} }}", &block.formatted), + ); +} + +#[test] +fn empty_blocks() { + let mut src = r###" +pub fn Alert(cx: Scope) -> Element { + cx.render(rsx! { + div { } + }) +} +"### + .to_string(); + + let formatted = get_format_blocks(&src); + + dbg!(&formatted); +} From 398cbc6534ed3ea0b95f562b96e773dbea28f085 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 25 Jun 2022 00:55:52 -0400 Subject: [PATCH 02/13] wip: rely on git version of prettyplease, refactor autofmt --- examples/readme.rs | 8 + packages/autofmt/Cargo.toml | 1 + packages/autofmt/src/block.rs | 94 ++ packages/autofmt/src/children.rs | 4 + packages/autofmt/src/component.rs | 78 ++ packages/autofmt/src/element.rs | 160 +++ packages/autofmt/src/expr.rs | 39 + packages/autofmt/src/ident.rs | 23 + packages/autofmt/src/lib.rs | 419 +------ .../autofmt/src/prettyplease/algorithm.rs | 376 ------ packages/autofmt/src/prettyplease/attr.rs | 208 ---- .../autofmt/src/prettyplease/convenience.rs | 98 -- packages/autofmt/src/prettyplease/data.rs | 95 -- packages/autofmt/src/prettyplease/expr.rs | 1028 ----------------- packages/autofmt/src/prettyplease/file.rs | 17 - packages/autofmt/src/prettyplease/generics.rs | 280 ----- packages/autofmt/src/prettyplease/item.rs | 757 ------------ packages/autofmt/src/prettyplease/iter.rs | 46 - packages/autofmt/src/prettyplease/lifetime.rs | 9 - packages/autofmt/src/prettyplease/lit.rs | 50 - packages/autofmt/src/prettyplease/mac.rs | 220 ---- packages/autofmt/src/prettyplease/mod.rs | 381 ------ packages/autofmt/src/prettyplease/pat.rs | 210 ---- packages/autofmt/src/prettyplease/path.rs | 181 --- packages/autofmt/src/prettyplease/ring.rs | 81 -- packages/autofmt/src/prettyplease/stmt.rs | 85 -- packages/autofmt/src/prettyplease/token.rs | 80 -- packages/autofmt/src/prettyplease/ty.rs | 202 ---- packages/autofmt/src/util.rs | 43 + packages/autofmt/tests/sink.rs | 4 + packages/core-macro/src/lib.rs | 1 + 31 files changed, 466 insertions(+), 4812 deletions(-) create mode 100644 packages/autofmt/src/block.rs create mode 100644 packages/autofmt/src/children.rs create mode 100644 packages/autofmt/src/component.rs create mode 100644 packages/autofmt/src/element.rs create mode 100644 packages/autofmt/src/expr.rs create mode 100644 packages/autofmt/src/ident.rs delete mode 100644 packages/autofmt/src/prettyplease/algorithm.rs delete mode 100644 packages/autofmt/src/prettyplease/attr.rs delete mode 100644 packages/autofmt/src/prettyplease/convenience.rs delete mode 100644 packages/autofmt/src/prettyplease/data.rs delete mode 100644 packages/autofmt/src/prettyplease/expr.rs delete mode 100644 packages/autofmt/src/prettyplease/file.rs delete mode 100644 packages/autofmt/src/prettyplease/generics.rs delete mode 100644 packages/autofmt/src/prettyplease/item.rs delete mode 100644 packages/autofmt/src/prettyplease/iter.rs delete mode 100644 packages/autofmt/src/prettyplease/lifetime.rs delete mode 100644 packages/autofmt/src/prettyplease/lit.rs delete mode 100644 packages/autofmt/src/prettyplease/mac.rs delete mode 100644 packages/autofmt/src/prettyplease/mod.rs delete mode 100644 packages/autofmt/src/prettyplease/pat.rs delete mode 100644 packages/autofmt/src/prettyplease/path.rs delete mode 100644 packages/autofmt/src/prettyplease/ring.rs delete mode 100644 packages/autofmt/src/prettyplease/stmt.rs delete mode 100644 packages/autofmt/src/prettyplease/token.rs delete mode 100644 packages/autofmt/src/prettyplease/ty.rs create mode 100644 packages/autofmt/src/util.rs diff --git a/examples/readme.rs b/examples/readme.rs index 4f4250343..d7a2363c9 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -11,6 +11,14 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = use_state(&cx, || 0); + let a = rsx! { + div { + + + key: "a" + } + }; + cx.render(rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } diff --git a/packages/autofmt/Cargo.toml b/packages/autofmt/Cargo.toml index 8fb7a128f..c7bf01dfa 100644 --- a/packages/autofmt/Cargo.toml +++ b/packages/autofmt/Cargo.toml @@ -12,3 +12,4 @@ syn = { version = "1.0.11", features = ["full", "extra-traits"] } dioxus-rsx = { path = "../rsx" } triple_accel = "0.4.0" serde = { version = "1.0.136", features = ["derive"] } +prettyplease = { git = "https://github.com/DioxusLabs/prettyplease-macro-fmt.git", features = ["verbatim"] } diff --git a/packages/autofmt/src/block.rs b/packages/autofmt/src/block.rs new file mode 100644 index 000000000..d3ef135d9 --- /dev/null +++ b/packages/autofmt/src/block.rs @@ -0,0 +1,94 @@ +use crate::{util::*, write_ident}; +use dioxus_rsx::*; +use std::fmt::Write; +use triple_accel::{levenshtein_search, Match}; + +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)] +pub struct FormattedBlock { + pub formatted: String, + pub start: usize, + pub end: usize, +} + +pub fn get_format_blocks(contents: &str) -> Vec { + let matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable(); + + let mut formatted_blocks = Vec::new(); + let mut last_bracket_end = 0; + + // find the rsx! marker + for Match { start, end, k } in matches { + // ensure the marker is not nested + if start < last_bracket_end { + continue; + } + + let remaining = &contents[end - 1..]; + let bracket_end = find_bracket_end(remaining).unwrap(); + let sub_string = &contents[end..bracket_end + end - 1]; + last_bracket_end = bracket_end + end - 1; + + let new = fmt_block(sub_string).unwrap(); + + let stripped = &contents[end + 1..bracket_end + end - 1]; + + if stripped == new { + continue; + } + + // if we have code to push, we want the code to end up on the right lines with the right indentation + let mut output = String::new(); + writeln!(output).unwrap(); + + for line in new.lines() { + writeln!(output, "{}", line).ok(); + } + + formatted_blocks.push(FormattedBlock { + formatted: output, + start: end, + end: end + bracket_end - 1, + }); + } + + formatted_blocks +} + +struct Isolate<'a> { + contents: &'a str, + start: usize, + end: usize, +} +fn isolate_body_of_rsx(contents: &str, Match { start, end, k }: triple_accel::Match) -> Isolate { + todo!() +} + +pub fn fmt_block(block: &str) -> Option { + let mut buf = String::new(); + let lines = block.split('\n').collect::>(); + + for node in &syn::parse_str::(block).ok()?.roots { + write_ident(&mut buf, &lines, node, 0).ok()?; + } + + Some(buf) +} + +#[test] +fn format_block_basic() { + let block = r#" + let a = 120; + + rsx! { + div { + h1 { "thing" } + h1 { "thing" } + h1 { "thing" } + } + } + "#; + + let edits = get_format_blocks(block); + + println!("{}", edits[0].formatted); +} diff --git a/packages/autofmt/src/children.rs b/packages/autofmt/src/children.rs new file mode 100644 index 000000000..3b56590bc --- /dev/null +++ b/packages/autofmt/src/children.rs @@ -0,0 +1,4 @@ +// write the body of a component or element +pub fn write_children() { + +} diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs new file mode 100644 index 000000000..df4e14006 --- /dev/null +++ b/packages/autofmt/src/component.rs @@ -0,0 +1,78 @@ +use crate::{util::*, write_ident}; +use dioxus_rsx::*; +use quote::ToTokens; +use std::fmt::{self, Write}; + +pub fn write_component( + component: &Component, + buf: &mut String, + indent: usize, + lines: &[&str], +) -> Result<(), fmt::Error> { + let Component { + name, + body, + children, + manual_props, + prop_gen_args, + } = component; + let mut name = name.to_token_stream().to_string(); + name.retain(|c| !c.is_whitespace()); + write_tabs(buf, indent)?; + write!(buf, "{name}")?; + if let Some(generics) = prop_gen_args { + let mut written = generics.to_token_stream().to_string(); + written.retain(|c| !c.is_whitespace()); + write!(buf, "{}", written)?; + } + write!(buf, " {{")?; + if !body.is_empty() || !children.is_empty() { + writeln!(buf)?; + } + for field in body { + write_tabs(buf, indent + 1)?; + let name = &field.name; + match &field.content { + ContentField::ManExpr(exp) => { + let out = prettyplease::unparse_expr(exp); + writeln!(buf, "{}: {},", name, out)?; + } + ContentField::Formatted(s) => { + writeln!(buf, "{}: \"{}\",", name, s.value())?; + } + ContentField::OnHandlerRaw(exp) => { + let out = prettyplease::unparse_expr(exp); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(buf, "{}: {}", name, first)?; + for line in lines { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + } + writeln!(buf, ",")?; + } + } + } + if let Some(exp) = manual_props { + write_tabs(buf, indent + 1)?; + let out = prettyplease::unparse_expr(exp); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(buf, "..{}", first)?; + for line in lines { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + } + writeln!(buf)?; + } + for child in children { + write_ident(buf, lines, child, indent + 1)?; + } + if !body.is_empty() || !children.is_empty() { + write_tabs(buf, indent)?; + } + writeln!(buf, "}}")?; + Ok(()) +} diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs new file mode 100644 index 000000000..bde1c33f8 --- /dev/null +++ b/packages/autofmt/src/element.rs @@ -0,0 +1,160 @@ +use crate::{util::*, write_ident}; +use dioxus_rsx::*; +use std::{fmt, fmt::Write}; + +pub fn write_element( + Element { + name, + key, + attributes, + children, + _is_static, + }: &Element, + buf: &mut String, + lines: &[&str], + node: &BodyNode, + indent: usize, +) -> fmt::Result { + /* + Write the tag + Write the key + Write the attributes + Write the children + */ + + write!(buf, "{name} {{")?; + + let total_attr_len = extract_attr_len(attributes); + let is_long_attr_list = total_attr_len > 80; + + if let Some(key) = key.as_ref().map(|f| f.value()) { + if is_long_attr_list { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + } else { + write!(buf, " ")?; + } + write!(buf, "key: \"{key}\"")?; + + if !attributes.is_empty() { + write!(buf, ",")?; + } + } + + let mut attr_iter = attributes.iter().peekable(); + + while let Some(attr) = attr_iter.next() { + if is_long_attr_list { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + } else { + write!(buf, " ")?; + } + + match &attr.attr { + ElementAttr::AttrText { name, value } => { + write!(buf, "{name}: \"{value}\"", value = value.value())?; + } + ElementAttr::AttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "{}: {}", name, out)?; + } + + ElementAttr::CustomAttrText { name, value } => { + write!( + buf, + "\"{name}\": \"{value}\"", + name = name.value(), + value = value.value() + )?; + } + + ElementAttr::CustomAttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "\"{}\": {}", name.value(), out)?; + } + + ElementAttr::EventTokens { name, tokens } => { + let out = prettyplease::unparse_expr(tokens); + + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(buf, "{}: {}", name, first)?; + } else { + writeln!(buf, "{}: {}", name, first)?; + + while let Some(line) = lines.next() { + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + if lines.peek().is_none() { + write!(buf, "")?; + } else { + writeln!(buf)?; + } + } + } + } + } + + if attr_iter.peek().is_some() || !children.is_empty() { + write!(buf, ",")?; + } + } + + // If the attr list is short, then we want to optimize for some cases + let is_child_optimized = match children.as_slice() { + // No children, just close the tag + [] => true, + + // Only a text node, just write it out + [BodyNode::Text(_)] => true, + + // If these have zero children and attributes, then we can just write out the tag + [BodyNode::Component(ref comp)] => comp.body.is_empty() && comp.children.is_empty(), + [BodyNode::Element(ref el)] => el.attributes.is_empty() && el.children.is_empty(), + + // Nothing else is optimized + _ => false, + }; + + if !is_long_attr_list && is_child_optimized { + write_ident(buf, lines, &children[0], indent)?; + } else { + for child in children { + writeln!(buf)?; + write_ident(buf, lines, child, indent + 1)?; + } + } + + // let text_val = text.value(); + // if total_attr_len + text_val.len() > 80 { + // writeln!(buf)?; + // write_tabs(buf, indent + 1)?; + // writeln!(buf, "\"{}\"", text.value())?; + // write_tabs(buf, indent)?; + // } else { + // write!(buf, " \"{}\" ", text.value())?; + // } + + // if is_long_attr_list { + // writeln!(buf)?; + // } + + // for child in children { + // write_ident(buf, lines, child, indent + 1)?; + // } + + // if is_long_attr_list { + // write_tabs(buf, indent)?; + // } else { + // write!(buf, " ")?; + // } + + writeln!(buf, "}}")?; + + Ok(()) +} diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs new file mode 100644 index 000000000..7cc0ed17a --- /dev/null +++ b/packages/autofmt/src/expr.rs @@ -0,0 +1,39 @@ +//! pretty printer for rsx! +use std::fmt::{self, Write}; + +pub fn write_raw_expr( + exp: &syn::Expr, + indent: usize, + lines: &[&str], + buf: &mut String, +) -> Result<(), fmt::Error> { + use syn::spanned::Spanned; + let placement = exp.span(); + let start = placement.start(); + let end = placement.end(); + let num_spaces_desired = (indent * 4) as isize; + let first = lines[start.line - 1]; + let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; + let offset = num_spaces_real - num_spaces_desired; + + for line_id in start.line - 1..end.line { + let line = lines[line_id]; + + // trim the leading whitespace + + if offset < 0 { + for _ in 0..-offset { + write!(buf, " ")?; + } + + writeln!(buf, "{}", line)?; + } else { + let offset = offset as usize; + + let right = &line[offset..]; + writeln!(buf, "{}", right)?; + } + } + + Ok(()) +} diff --git a/packages/autofmt/src/ident.rs b/packages/autofmt/src/ident.rs new file mode 100644 index 000000000..19fd3cb8d --- /dev/null +++ b/packages/autofmt/src/ident.rs @@ -0,0 +1,23 @@ +use crate::{ + component::write_component, element::write_element, expr::write_raw_expr, util::write_tabs, +}; +use dioxus_rsx::*; +use std::fmt::{self, Write}; + +pub fn write_ident( + buf: &mut String, + lines: &[&str], + node: &BodyNode, + indent: usize, +) -> fmt::Result { + match node { + BodyNode::Element(el) => write_element(el, buf, lines, node, indent), + BodyNode::Component(component) => write_component(component, buf, indent, lines), + BodyNode::Text(text) => write_text(text, buf, indent), + BodyNode::RawExpr(exp) => write_raw_expr(exp, indent, lines, buf), + } +} + +fn write_text(text: &syn::LitStr, buf: &mut String, indent: usize) -> fmt::Result { + write!(buf, "\"{}\"", text.value()) +} diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 4f467e139..c6fd2eeec 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -1,413 +1,16 @@ -//! pretty printer for rsx! +//! pretty printer for rsx code + use dioxus_rsx::*; use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; -use std::{ - fmt::{self, Write}, - ptr::NonNull, -}; -use syn::{ - buffer::TokenBuffer, - parse::{ParseBuffer, ParseStream}, -}; -use triple_accel::{levenshtein_search, Match}; -mod prettyplease; +mod block; +mod children; +mod component; +mod element; +mod expr; +mod ident; +mod util; -#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)] -pub struct FormattedBlock { - pub formatted: String, - pub start: usize, - pub end: usize, -} - -/* - - -*/ -pub fn get_format_blocks(contents: &str) -> Vec { - let mut matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable(); - - let mut cur_match: Option = None; - - let mut formatted_blocks = Vec::new(); - - let mut last_bracket_end = 0; - - for item in matches { - let Match { start, end, k } = item; - - if start < last_bracket_end { - continue; - } - - let remaining = &contents[end - 1..]; - - if let Some(bracket_end) = find_bracket_end(remaining) { - let sub_string = &contents[end..bracket_end + end - 1]; - - last_bracket_end = bracket_end + end - 1; - - // with the edge brackets - // println!("{}", &contents[end - 1..bracket_end + end]); - - if let Some(new) = fmt_block(sub_string) { - if !new.is_empty() { - println!("{}", &contents[end + 1..bracket_end + end - 1]); - println!("{}", new); - - let stripped = &contents[end + 1..bracket_end + end - 1]; - if stripped == new { - println!("no changes necessary"); - } - - // if we have code to push, we want the code to end up on the right lines with the right indentation - - let mut output = String::new(); - writeln!(output).unwrap(); - - for line in new.lines() { - writeln!(output, " {}", line).ok(); - } - - formatted_blocks.push(FormattedBlock { - formatted: output, - start: end, - end: end + bracket_end - 1, - }); - } - } else { - panic!("failed to format block: {}", sub_string); - } - } else { - panic!("failed to find end of block: {}", remaining); - } - } - - formatted_blocks -} - -pub fn fmt_block(block: &str) -> Option { - let mut raw_lines = block.split('\n').collect::>(); - - let parsed: CallBody = syn::parse_str(block).ok()?; - - let mut buf = String::new(); - - for node in parsed.roots.iter() { - write_ident(&mut buf, &raw_lines, node, 0).ok()?; - } - - Some(buf) -} - -pub fn write_ident( - buf: &mut String, - lines: &[&str], - node: &BodyNode, - indent: usize, -) -> fmt::Result { - match node { - BodyNode::Element(el) => { - let Element { - name, - key, - attributes, - children, - _is_static, - } = el; - - write_tabs(buf, indent)?; - write!(buf, "{name} {{")?; - - let total_attr_len = attributes - .iter() - .map(|attr| match &attr.attr { - ElementAttr::AttrText { name, value } => value.value().len(), - ElementAttr::AttrExpression { name, value } => 10, - ElementAttr::CustomAttrText { name, value } => value.value().len(), - ElementAttr::CustomAttrExpression { name, value } => 10, - ElementAttr::EventTokens { name, tokens } => 1000000, - ElementAttr::Meta(_) => todo!(), - }) - .sum::(); - - let is_long_attr_list = total_attr_len > 80; - - if let Some(key) = key { - let key = key.value(); - if is_long_attr_list { - write_tabs(buf, indent + 1)?; - } else { - write!(buf, " ")?; - } - write!(buf, "key: \"{key}\"")?; - - if !attributes.is_empty() { - writeln!(buf, ",")?; - } - } - - let mut attr_iter = attributes.iter().peekable(); - while let Some(attr) = attr_iter.next() { - if is_long_attr_list { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - } else { - write!(buf, " ")?; - } - - match &attr.attr { - ElementAttr::AttrText { name, value } => { - write!(buf, "{name}: \"{value}\"", value = value.value())?; - } - ElementAttr::AttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "{}: {}", name, out)?; - } - - ElementAttr::CustomAttrText { name, value } => { - write!( - buf, - "\"{name}\": \"{value}\"", - name = name.value(), - value = value.value() - )?; - } - - ElementAttr::CustomAttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "\"{}\": {}", name.value(), out)?; - } - - ElementAttr::EventTokens { name, tokens } => { - let out = prettyplease::unparse_expr(tokens); - - dbg!(&out); - - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - - // a one-liner for whatever reason - // Does not need a new line - if lines.peek().is_none() { - write!(buf, "{}: {}", name, first)?; - } else { - writeln!(buf, "{}: {}", name, first)?; - - while let Some(line) = lines.next() { - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; - if lines.peek().is_none() { - write!(buf, "")?; - } else { - writeln!(buf)?; - } - } - } - } - ElementAttr::Meta(_) => {} - } - - if attr_iter.peek().is_some() || !children.is_empty() { - write!(buf, ",")?; - } - } - - if children.len() == 1 && children[0].is_litstr() && !is_long_attr_list { - if let BodyNode::Text(text) = &children[0] { - let text_val = text.value(); - if total_attr_len + text_val.len() > 80 { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - writeln!(buf, "\"{}\"", text.value())?; - write_tabs(buf, indent)?; - } else { - write!(buf, " \"{}\" ", text.value())?; - } - } - } else { - if is_long_attr_list || !children.is_empty() { - writeln!(buf)?; - } - - for child in children { - write_ident(buf, lines, child, indent + 1)?; - } - - if is_long_attr_list || !children.is_empty() { - write_tabs(buf, indent)?; - } else { - write!(buf, " ")?; - } - } - - writeln!(buf, "}}")?; - } - BodyNode::Component(component) => { - let Component { - name, - body, - children, - manual_props, - } = component; - - let mut name = name.to_token_stream().to_string(); - name.retain(|c| !c.is_whitespace()); - - write_tabs(buf, indent)?; - write!(buf, "{name} {{")?; - - if !body.is_empty() || !children.is_empty() { - writeln!(buf)?; - } - - for field in body { - write_tabs(buf, indent + 1)?; - let name = &field.name; - match &field.content { - ContentField::ManExpr(exp) => { - let out = prettyplease::unparse_expr(exp); - writeln!(buf, "{}: {},", name, out)?; - } - ContentField::Formatted(s) => { - writeln!(buf, "{}: \"{}\",", name, s.value())?; - } - ContentField::OnHandlerRaw(exp) => { - let out = prettyplease::unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(buf, "{}: {}", name, first)?; - for line in lines { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; - } - writeln!(buf, ",")?; - } - } - } - - if let Some(exp) = manual_props { - write_tabs(buf, indent + 1)?; - let out = prettyplease::unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(buf, "..{}", first)?; - for line in lines { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; - } - writeln!(buf)?; - } - - for child in children { - write_ident(buf, lines, child, indent + 1)?; - } - - if !body.is_empty() || !children.is_empty() { - write_tabs(buf, indent)?; - } - - writeln!(buf, "}}")?; - - // - // write!(buf, "{}", " ".repeat(ident)) - } - BodyNode::Text(t) => { - // - // write!(buf, "{}", " ".repeat(ident)) - write_tabs(buf, indent)?; - writeln!(buf, "\"{}\"", t.value())?; - } - BodyNode::RawExpr(exp) => { - use syn::spanned::Spanned; - - let placement = exp.span(); - - let start = placement.start(); - let end = placement.end(); - - let num_spaces_desired = (indent * 4) as isize; - let first = lines[start.line - 1]; - let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; - let offset = num_spaces_real - num_spaces_desired; - - for line_id in start.line - 1..end.line { - let line = lines[line_id]; - - // trim the leading whitespace - - if offset < 0 { - for _ in 0..-offset { - write!(buf, " ")?; - } - - writeln!(buf, "{}", line)?; - } else { - let offset = offset as usize; - - let right = &line[offset..]; - writeln!(buf, "{}", right)?; - } - } - - // let toks = exp.to_token_stream(); - - // let out = prettyplease::unparse_expr(exp); - // let mut lines = out.split('\n').peekable(); - // for line in lines { - // write_tabs(buf, indent)?; - // writeln!(buf, "{}", line)?; - // // writeln!(buf)?; - // } - // write_tabs(buf, indent)?; - // let first = lines.next().unwrap(); - // write!(buf, "{}", name, first)?; - // writeln!(buf)?; - - // - // write!(buf, "{}", " ".repeat(ident)) - } - BodyNode::Meta(att) => { - // - // if att.path.segments.last().unwrap().ident == "doc" { - let val = att.to_string(); - write_tabs(buf, indent)?; - writeln!(buf, "{}", val)?; - // } - // match att {} - } - } - - Ok(()) -} - -pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result { - for _ in 0..num { - write!(f, " ")? - } - Ok(()) -} - -fn find_bracket_end(contents: &str) -> Option { - let mut depth = 0; - let mut i = 0; - - for c in contents.chars() { - if c == '{' { - depth += 1; - } else if c == '}' { - depth -= 1; - } - - if depth == 0 { - return Some(i); - } - - i += 1; - } - - None -} +pub use block::{fmt_block, get_format_blocks}; +pub use ident::write_ident; diff --git a/packages/autofmt/src/prettyplease/algorithm.rs b/packages/autofmt/src/prettyplease/algorithm.rs deleted file mode 100644 index b70bd57ea..000000000 --- a/packages/autofmt/src/prettyplease/algorithm.rs +++ /dev/null @@ -1,376 +0,0 @@ -// Adapted from https://github.com/rust-lang/rust/blob/1.57.0/compiler/rustc_ast_pretty/src/pp.rs. -// See "Algorithm notes" in the crate-level rustdoc. - -use super::ring::RingBuffer; -use super::{MARGIN, MIN_SPACE}; -use std::borrow::Cow; -use std::cmp; -use std::collections::VecDeque; -use std::iter; - -#[derive(Clone, Copy, PartialEq)] -pub enum Breaks { - Consistent, - Inconsistent, -} - -#[derive(Clone, Copy, Default)] -pub struct BreakToken { - pub offset: isize, - pub blank_space: usize, - pub pre_break: Option, - pub post_break: Option, - pub no_break: Option, - pub if_nonempty: bool, - pub never_break: bool, -} - -#[derive(Clone, Copy)] -pub struct BeginToken { - pub offset: isize, - pub breaks: Breaks, -} - -#[derive(Clone)] -pub enum Token { - String(Cow<'static, str>), - Break(BreakToken), - Begin(BeginToken), - End, -} - -#[derive(Copy, Clone)] -enum PrintFrame { - Fits(Breaks), - Broken(usize, Breaks), -} - -pub const SIZE_INFINITY: isize = 0xffff; - -pub struct Printer { - out: String, - // Number of spaces left on line - space: isize, - // Ring-buffer of tokens and calculated sizes - buf: RingBuffer, - // Total size of tokens already printed - left_total: isize, - // Total size of tokens enqueued, including printed and not yet printed - right_total: isize, - // Holds the ring-buffer index of the Begin that started the current block, - // possibly with the most recent Break after that Begin (if there is any) on - // top of it. Values are pushed and popped on the back of the queue using it - // like stack, and elsewhere old values are popped from the front of the - // queue as they become irrelevant due to the primary ring-buffer advancing. - scan_stack: VecDeque, - // Stack of blocks-in-progress being flushed by print - print_stack: Vec, - // Level of indentation of current line - indent: usize, - // Buffered indentation to avoid writing trailing whitespace - pending_indentation: usize, -} - -#[derive(Clone)] -struct BufEntry { - token: Token, - size: isize, -} - -impl Printer { - pub fn new() -> Self { - Printer { - out: String::new(), - space: MARGIN, - buf: RingBuffer::new(), - left_total: 0, - right_total: 0, - scan_stack: VecDeque::new(), - print_stack: Vec::new(), - indent: 0, - pending_indentation: 0, - } - } - - pub fn eof(mut self) -> String { - if !self.scan_stack.is_empty() { - self.check_stack(0); - self.advance_left(); - } - self.out - } - - pub fn scan_begin(&mut self, token: BeginToken) { - if self.scan_stack.is_empty() { - self.left_total = 1; - self.right_total = 1; - self.buf.clear(); - } - let right = self.buf.push(BufEntry { - token: Token::Begin(token), - size: -self.right_total, - }); - self.scan_stack.push_back(right); - } - - pub fn scan_end(&mut self) { - if self.scan_stack.is_empty() { - self.print_end(); - } else { - if !self.buf.is_empty() { - if let Token::Break(break_token) = self.buf.last().token { - if self.buf.len() >= 2 { - if let Token::Begin(_) = self.buf.second_last().token { - self.buf.pop_last(); - self.buf.pop_last(); - self.scan_stack.pop_back(); - self.scan_stack.pop_back(); - self.right_total -= break_token.blank_space as isize; - return; - } - } - if break_token.if_nonempty { - self.buf.pop_last(); - self.scan_stack.pop_back(); - self.right_total -= break_token.blank_space as isize; - } - } - } - let right = self.buf.push(BufEntry { - token: Token::End, - size: -1, - }); - self.scan_stack.push_back(right); - } - } - - pub fn scan_break(&mut self, token: BreakToken) { - if self.scan_stack.is_empty() { - self.left_total = 1; - self.right_total = 1; - self.buf.clear(); - } else { - self.check_stack(0); - } - let right = self.buf.push(BufEntry { - token: Token::Break(token), - size: -self.right_total, - }); - self.scan_stack.push_back(right); - self.right_total += token.blank_space as isize; - } - - pub fn scan_string(&mut self, string: Cow<'static, str>) { - if self.scan_stack.is_empty() { - self.print_string(string); - } else { - let len = string.len() as isize; - self.buf.push(BufEntry { - token: Token::String(string), - size: len, - }); - self.right_total += len; - self.check_stream(); - } - } - - pub fn offset(&mut self, offset: isize) { - match &mut self.buf.last_mut().token { - Token::Break(token) => token.offset += offset, - Token::Begin(_) => {} - Token::String(_) | Token::End => unreachable!(), - } - } - - pub fn end_with_max_width(&mut self, max: isize) { - let mut depth = 1; - for &index in self.scan_stack.iter().rev() { - let entry = &self.buf[index]; - match entry.token { - Token::Begin(_) => { - depth -= 1; - if depth == 0 { - if entry.size < 0 { - let actual_width = entry.size + self.right_total; - if actual_width > max { - self.buf.push(BufEntry { - token: Token::String(Cow::Borrowed("")), - size: SIZE_INFINITY, - }); - self.right_total += SIZE_INFINITY; - } - } - break; - } - } - Token::End => depth += 1, - Token::Break(_) => {} - Token::String(_) => unreachable!(), - } - } - self.scan_end(); - } - - fn check_stream(&mut self) { - while self.right_total - self.left_total > self.space { - if *self.scan_stack.front().unwrap() == self.buf.index_of_first() { - self.scan_stack.pop_front().unwrap(); - self.buf.first_mut().size = SIZE_INFINITY; - } - - self.advance_left(); - - if self.buf.is_empty() { - break; - } - } - } - - fn advance_left(&mut self) { - while self.buf.first().size >= 0 { - let left = self.buf.pop_first(); - - match left.token { - Token::String(string) => { - self.left_total += left.size; - self.print_string(string); - } - Token::Break(token) => { - self.left_total += token.blank_space as isize; - self.print_break(token, left.size); - } - Token::Begin(token) => self.print_begin(token, left.size), - Token::End => self.print_end(), - } - - if self.buf.is_empty() { - break; - } - } - } - - fn check_stack(&mut self, mut depth: usize) { - while let Some(&index) = self.scan_stack.back() { - let mut entry = &mut self.buf[index]; - match entry.token { - Token::Begin(_) => { - if depth == 0 { - break; - } - self.scan_stack.pop_back().unwrap(); - entry.size += self.right_total; - depth -= 1; - } - Token::End => { - self.scan_stack.pop_back().unwrap(); - entry.size = 1; - depth += 1; - } - Token::Break(_) => { - self.scan_stack.pop_back().unwrap(); - entry.size += self.right_total; - if depth == 0 { - break; - } - } - Token::String(_) => unreachable!(), - } - } - } - - fn get_top(&self) -> PrintFrame { - const OUTER: PrintFrame = PrintFrame::Broken(0, Breaks::Inconsistent); - self.print_stack.last().map_or(OUTER, PrintFrame::clone) - } - - fn print_begin(&mut self, token: BeginToken, size: isize) { - if cfg!(prettyplease_debug) { - self.out.push(match token.breaks { - Breaks::Consistent => '«', - Breaks::Inconsistent => '‹', - }); - if cfg!(prettyplease_debug_indent) { - self.out - .extend(token.offset.to_string().chars().map(|ch| match ch { - '0'..='9' => ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'] - [(ch as u8 - b'0') as usize] - as char, - '-' => '₋', - _ => unreachable!(), - })); - } - } - if size > self.space { - self.print_stack - .push(PrintFrame::Broken(self.indent, token.breaks)); - self.indent = usize::try_from(self.indent as isize + token.offset).unwrap(); - } else { - self.print_stack.push(PrintFrame::Fits(token.breaks)); - } - } - - fn print_end(&mut self) { - let breaks = match self.print_stack.pop().unwrap() { - PrintFrame::Broken(indent, breaks) => { - self.indent = indent; - breaks - } - PrintFrame::Fits(breaks) => breaks, - }; - if cfg!(prettyplease_debug) { - self.out.push(match breaks { - Breaks::Consistent => '»', - Breaks::Inconsistent => '›', - }); - } - } - - fn print_break(&mut self, token: BreakToken, size: isize) { - let fits = token.never_break - || match self.get_top() { - PrintFrame::Fits(..) => true, - PrintFrame::Broken(.., Breaks::Consistent) => false, - PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, - }; - if fits { - self.pending_indentation += token.blank_space; - self.space -= token.blank_space as isize; - if let Some(no_break) = token.no_break { - self.out.push(no_break); - self.space -= no_break.len_utf8() as isize; - } - if cfg!(prettyplease_debug) { - self.out.push('·'); - } - } else { - if let Some(pre_break) = token.pre_break { - self.out.push(pre_break); - } - if cfg!(prettyplease_debug) { - self.out.push('·'); - } - self.out.push('\n'); - let indent = self.indent as isize + token.offset; - self.pending_indentation = usize::try_from(indent).unwrap(); - self.space = cmp::max(MARGIN - indent, MIN_SPACE); - if let Some(post_break) = token.post_break { - self.print_indent(); - self.out.push(post_break); - self.space -= post_break.len_utf8() as isize; - } - } - } - - fn print_string(&mut self, string: Cow<'static, str>) { - self.print_indent(); - self.out.push_str(&string); - self.space -= string.len() as isize; - } - - fn print_indent(&mut self) { - self.out.reserve(self.pending_indentation); - self.out - .extend(iter::repeat(' ').take(self.pending_indentation)); - self.pending_indentation = 0; - } -} diff --git a/packages/autofmt/src/prettyplease/attr.rs b/packages/autofmt/src/prettyplease/attr.rs deleted file mode 100644 index d934a1c1f..000000000 --- a/packages/autofmt/src/prettyplease/attr.rs +++ /dev/null @@ -1,208 +0,0 @@ -use super::algorithm::Printer; -use super::INDENT; -use proc_macro2::{Delimiter, TokenStream, TokenTree}; -use syn::{AttrStyle, Attribute, Lit, PathArguments}; - -impl Printer { - pub fn outer_attrs(&mut self, attrs: &[Attribute]) { - for attr in attrs { - if let AttrStyle::Outer = attr.style { - self.attr(attr); - } - } - } - - pub fn inner_attrs(&mut self, attrs: &[Attribute]) { - for attr in attrs { - if let AttrStyle::Inner(_) = attr.style { - self.attr(attr); - } - } - } - - fn attr(&mut self, attr: &Attribute) { - if let Some(doc) = value_of_attribute("doc", attr) { - if doc.contains('\n') { - self.word(match attr.style { - AttrStyle::Outer => "/**", - AttrStyle::Inner(_) => "/*!", - }); - self.word(doc); - self.word("*/"); - } else { - self.word(match attr.style { - AttrStyle::Outer => "///", - AttrStyle::Inner(_) => "//!", - }); - self.word(doc); - } - self.hardbreak(); - } else if let Some(comment) = value_of_attribute("comment", attr) { - if comment.contains('\n') { - self.word("/*"); - self.word(comment); - self.word("*/"); - } else { - self.word("//"); - self.word(comment); - } - self.hardbreak(); - } else { - self.word(match attr.style { - AttrStyle::Outer => "#", - AttrStyle::Inner(_) => "#!", - }); - self.word("["); - self.path(&attr.path); - self.attr_tokens(attr.tokens.clone()); - self.word("]"); - self.space(); - } - } - - fn attr_tokens(&mut self, tokens: TokenStream) { - let mut stack = Vec::new(); - stack.push((tokens.into_iter().peekable(), Delimiter::None)); - let mut space = Self::nbsp as fn(&mut Self); - - #[derive(PartialEq)] - enum State { - Word, - Punct, - TrailingComma, - } - - use State::*; - let mut state = Word; - - while let Some((tokens, delimiter)) = stack.last_mut() { - match tokens.next() { - Some(TokenTree::Ident(ident)) => { - if let Word = state { - space(self); - } - self.ident(&ident); - state = Word; - } - Some(TokenTree::Punct(punct)) => { - let ch = punct.as_char(); - if let (Word, '=') = (state, ch) { - self.nbsp(); - } - if ch == ',' && tokens.peek().is_none() { - self.trailing_comma(true); - state = TrailingComma; - } else { - self.token_punct(ch); - if ch == '=' { - self.nbsp(); - } else if ch == ',' { - space(self); - } - state = Punct; - } - } - Some(TokenTree::Literal(literal)) => { - if let Word = state { - space(self); - } - self.token_literal(&literal); - state = Word; - } - Some(TokenTree::Group(group)) => { - let delimiter = group.delimiter(); - let stream = group.stream(); - match delimiter { - Delimiter::Parenthesis => { - self.word("("); - self.cbox(INDENT); - self.zerobreak(); - state = Punct; - } - Delimiter::Brace => { - self.word("{"); - state = Punct; - } - Delimiter::Bracket => { - self.word("["); - state = Punct; - } - Delimiter::None => {} - } - stack.push((stream.into_iter().peekable(), delimiter)); - space = Self::space; - } - None => { - match delimiter { - Delimiter::Parenthesis => { - if state != TrailingComma { - self.zerobreak(); - } - self.offset(-INDENT); - self.end(); - self.word(")"); - state = Punct; - } - Delimiter::Brace => { - self.word("}"); - state = Punct; - } - Delimiter::Bracket => { - self.word("]"); - state = Punct; - } - Delimiter::None => {} - } - stack.pop(); - if stack.is_empty() { - space = Self::nbsp; - } - } - } - } - } -} - -fn value_of_attribute(requested: &str, attr: &Attribute) -> Option { - let is_doc = attr.path.leading_colon.is_none() - && attr.path.segments.len() == 1 - && matches!(attr.path.segments[0].arguments, PathArguments::None) - && attr.path.segments[0].ident == requested; - if !is_doc { - return None; - } - let mut tokens = attr.tokens.clone().into_iter(); - match tokens.next() { - Some(TokenTree::Punct(punct)) if punct.as_char() == '=' => {} - _ => return None, - } - let literal = match tokens.next() { - Some(TokenTree::Literal(literal)) => literal, - _ => return None, - }; - if tokens.next().is_some() { - return None; - } - match Lit::new(literal) { - Lit::Str(string) => Some(string.value()), - _ => None, - } -} - -pub fn has_outer(attrs: &[Attribute]) -> bool { - for attr in attrs { - if let AttrStyle::Outer = attr.style { - return true; - } - } - false -} - -pub fn has_inner(attrs: &[Attribute]) -> bool { - for attr in attrs { - if let AttrStyle::Inner(_) = attr.style { - return true; - } - } - false -} diff --git a/packages/autofmt/src/prettyplease/convenience.rs b/packages/autofmt/src/prettyplease/convenience.rs deleted file mode 100644 index f87206320..000000000 --- a/packages/autofmt/src/prettyplease/convenience.rs +++ /dev/null @@ -1,98 +0,0 @@ -use super::algorithm::{self, BeginToken, BreakToken, Breaks, Printer}; -use std::borrow::Cow; - -impl Printer { - pub fn ibox(&mut self, indent: isize) { - self.scan_begin(BeginToken { - offset: indent, - breaks: Breaks::Inconsistent, - }); - } - - pub fn cbox(&mut self, indent: isize) { - self.scan_begin(BeginToken { - offset: indent, - breaks: Breaks::Consistent, - }); - } - - pub fn end(&mut self) { - self.scan_end(); - } - - pub fn word>>(&mut self, wrd: S) { - let s = wrd.into(); - self.scan_string(s); - } - - fn spaces(&mut self, n: usize) { - self.scan_break(BreakToken { - blank_space: n, - ..BreakToken::default() - }); - } - - pub fn zerobreak(&mut self) { - self.spaces(0); - } - - pub fn space(&mut self) { - self.spaces(1); - } - - pub fn nbsp(&mut self) { - self.word(" "); - } - - pub fn hardbreak(&mut self) { - self.spaces(algorithm::SIZE_INFINITY as usize); - } - - pub fn space_if_nonempty(&mut self) { - self.scan_break(BreakToken { - blank_space: 1, - if_nonempty: true, - ..BreakToken::default() - }); - } - - pub fn hardbreak_if_nonempty(&mut self) { - self.scan_break(BreakToken { - blank_space: algorithm::SIZE_INFINITY as usize, - if_nonempty: true, - ..BreakToken::default() - }); - } - - pub fn trailing_comma(&mut self, is_last: bool) { - if is_last { - self.scan_break(BreakToken { - pre_break: Some(','), - ..BreakToken::default() - }); - } else { - self.word(","); - self.space(); - } - } - - pub fn trailing_comma_or_space(&mut self, is_last: bool) { - if is_last { - self.scan_break(BreakToken { - blank_space: 1, - pre_break: Some(','), - ..BreakToken::default() - }); - } else { - self.word(","); - self.space(); - } - } - - pub fn neverbreak(&mut self) { - self.scan_break(BreakToken { - never_break: true, - ..BreakToken::default() - }); - } -} diff --git a/packages/autofmt/src/prettyplease/data.rs b/packages/autofmt/src/prettyplease/data.rs deleted file mode 100644 index be20eae05..000000000 --- a/packages/autofmt/src/prettyplease/data.rs +++ /dev/null @@ -1,95 +0,0 @@ -use super::algorithm::Printer; -use super::iter::IterDelimited; -use super::INDENT; -use syn::{ - Field, Fields, FieldsUnnamed, PathArguments, Variant, VisCrate, VisPublic, VisRestricted, - Visibility, -}; - -impl Printer { - pub fn variant(&mut self, variant: &Variant) { - self.outer_attrs(&variant.attrs); - self.ident(&variant.ident); - match &variant.fields { - Fields::Named(fields) => { - self.nbsp(); - self.word("{"); - self.cbox(INDENT); - self.space(); - for field in fields.named.iter().delimited() { - self.field(&field); - self.trailing_comma_or_space(field.is_last); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - Fields::Unnamed(fields) => { - self.cbox(INDENT); - self.fields_unnamed(fields); - self.end(); - } - Fields::Unit => {} - } - if let Some((_eq_token, discriminant)) = &variant.discriminant { - self.word(" = "); - self.expr(discriminant); - } - } - - pub fn fields_unnamed(&mut self, fields: &FieldsUnnamed) { - self.word("("); - self.zerobreak(); - for field in fields.unnamed.iter().delimited() { - self.field(&field); - self.trailing_comma(field.is_last); - } - self.offset(-INDENT); - self.word(")"); - } - - pub fn field(&mut self, field: &Field) { - self.outer_attrs(&field.attrs); - self.visibility(&field.vis); - if let Some(ident) = &field.ident { - self.ident(ident); - self.word(": "); - } - self.ty(&field.ty); - } - - pub fn visibility(&mut self, vis: &Visibility) { - match vis { - Visibility::Public(vis) => self.vis_public(vis), - Visibility::Crate(vis) => self.vis_crate(vis), - Visibility::Restricted(vis) => self.vis_restricted(vis), - Visibility::Inherited => {} - } - } - - fn vis_public(&mut self, vis: &VisPublic) { - let _ = vis; - self.word("pub "); - } - - fn vis_crate(&mut self, vis: &VisCrate) { - let _ = vis; - self.word("crate "); - } - - fn vis_restricted(&mut self, vis: &VisRestricted) { - self.word("pub("); - let omit_in = vis.path.leading_colon.is_none() - && vis.path.segments.len() == 1 - && matches!(vis.path.segments[0].arguments, PathArguments::None) - && matches!( - vis.path.segments[0].ident.to_string().as_str(), - "self" | "super" | "crate", - ); - if !omit_in { - self.word("in "); - } - self.path(&vis.path); - self.word(") "); - } -} diff --git a/packages/autofmt/src/prettyplease/expr.rs b/packages/autofmt/src/prettyplease/expr.rs deleted file mode 100644 index e659118a4..000000000 --- a/packages/autofmt/src/prettyplease/expr.rs +++ /dev/null @@ -1,1028 +0,0 @@ -use super::algorithm::{BreakToken, Printer}; -use super::attr; -use super::iter::IterDelimited; -use super::stmt; -use super::INDENT; -use proc_macro2::TokenStream; -use syn::punctuated::Punctuated; -use syn::{ - token, Arm, Attribute, BinOp, Block, Expr, ExprArray, ExprAssign, ExprAssignOp, ExprAsync, - ExprAwait, ExprBinary, ExprBlock, ExprBox, ExprBreak, ExprCall, ExprCast, ExprClosure, - ExprContinue, ExprField, ExprForLoop, ExprGroup, ExprIf, ExprIndex, ExprLet, ExprLit, ExprLoop, - ExprMacro, ExprMatch, ExprMethodCall, ExprParen, ExprPath, ExprRange, ExprReference, - ExprRepeat, ExprReturn, ExprStruct, ExprTry, ExprTryBlock, ExprTuple, ExprType, ExprUnary, - ExprUnsafe, ExprWhile, ExprYield, FieldValue, GenericMethodArgument, Index, Label, Member, - MethodTurbofish, PathArguments, RangeLimits, ReturnType, Stmt, Token, UnOp, -}; - -impl Printer { - pub fn expr(&mut self, expr: &Expr) { - match expr { - Expr::Array(expr) => self.expr_array(expr), - Expr::Assign(expr) => self.expr_assign(expr), - Expr::AssignOp(expr) => self.expr_assign_op(expr), - Expr::Async(expr) => self.expr_async(expr), - Expr::Await(expr) => self.expr_await(expr, false), - Expr::Binary(expr) => self.expr_binary(expr), - Expr::Block(expr) => self.expr_block(expr), - Expr::Box(expr) => self.expr_box(expr), - Expr::Break(expr) => self.expr_break(expr), - Expr::Call(expr) => self.expr_call(expr, false), - Expr::Cast(expr) => self.expr_cast(expr), - Expr::Closure(expr) => self.expr_closure(expr), - Expr::Continue(expr) => self.expr_continue(expr), - Expr::Field(expr) => self.expr_field(expr, false), - Expr::ForLoop(expr) => self.expr_for_loop(expr), - Expr::Group(expr) => self.expr_group(expr), - Expr::If(expr) => self.expr_if(expr), - Expr::Index(expr) => self.expr_index(expr, false), - Expr::Let(expr) => self.expr_let(expr), - Expr::Lit(expr) => self.expr_lit(expr), - Expr::Loop(expr) => self.expr_loop(expr), - Expr::Macro(expr) => self.expr_macro(expr), - Expr::Match(expr) => self.expr_match(expr), - Expr::MethodCall(expr) => self.expr_method_call(expr, false), - Expr::Paren(expr) => self.expr_paren(expr), - Expr::Path(expr) => self.expr_path(expr), - Expr::Range(expr) => self.expr_range(expr), - Expr::Reference(expr) => self.expr_reference(expr), - Expr::Repeat(expr) => self.expr_repeat(expr), - Expr::Return(expr) => self.expr_return(expr), - Expr::Struct(expr) => self.expr_struct(expr), - Expr::Try(expr) => self.expr_try(expr, false), - Expr::TryBlock(expr) => self.expr_try_block(expr), - Expr::Tuple(expr) => self.expr_tuple(expr), - Expr::Type(expr) => self.expr_type(expr), - Expr::Unary(expr) => self.expr_unary(expr), - Expr::Unsafe(expr) => self.expr_unsafe(expr), - Expr::Verbatim(expr) => self.expr_verbatim(expr), - Expr::While(expr) => self.expr_while(expr), - Expr::Yield(expr) => self.expr_yield(expr), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown Expr"), - } - } - - pub fn expr_beginning_of_line(&mut self, expr: &Expr, beginning_of_line: bool) { - match expr { - Expr::Await(expr) => self.expr_await(expr, beginning_of_line), - Expr::Field(expr) => self.expr_field(expr, beginning_of_line), - Expr::Index(expr) => self.expr_index(expr, beginning_of_line), - Expr::MethodCall(expr) => self.expr_method_call(expr, beginning_of_line), - Expr::Try(expr) => self.expr_try(expr, beginning_of_line), - _ => self.expr(expr), - } - } - - fn subexpr(&mut self, expr: &Expr, beginning_of_line: bool) { - match expr { - Expr::Await(expr) => self.subexpr_await(expr, beginning_of_line), - Expr::Call(expr) => self.subexpr_call(expr), - Expr::Field(expr) => self.subexpr_field(expr, beginning_of_line), - Expr::Index(expr) => self.subexpr_index(expr, beginning_of_line), - Expr::MethodCall(expr) => self.subexpr_method_call(expr, beginning_of_line, false), - Expr::Try(expr) => self.subexpr_try(expr, beginning_of_line), - _ => { - self.cbox(-INDENT); - self.expr(expr); - self.end(); - } - } - } - - // If the given expression is a bare `ExprStruct`, wraps it in parenthesis - // before appending it to `TokenStream`. - fn wrap_exterior_struct(&mut self, expr: &Expr) { - let needs_paren = contains_exterior_struct_lit(expr); - if needs_paren { - self.word("("); - } - self.cbox(0); - self.expr(expr); - if needs_paren { - self.word(")"); - } - if needs_newline_if_wrap(expr) { - self.space(); - } else { - self.nbsp(); - } - self.end(); - } - - fn expr_array(&mut self, expr: &ExprArray) { - self.outer_attrs(&expr.attrs); - self.word("["); - self.cbox(INDENT); - self.zerobreak(); - for element in expr.elems.iter().delimited() { - self.expr(&element); - self.trailing_comma(element.is_last); - } - self.offset(-INDENT); - self.end(); - self.word("]"); - } - - fn expr_assign(&mut self, expr: &ExprAssign) { - self.outer_attrs(&expr.attrs); - self.ibox(0); - self.expr(&expr.left); - self.word(" = "); - self.expr(&expr.right); - self.end(); - } - - fn expr_assign_op(&mut self, expr: &ExprAssignOp) { - self.outer_attrs(&expr.attrs); - self.ibox(INDENT); - self.ibox(-INDENT); - self.expr(&expr.left); - self.end(); - self.space(); - self.binary_operator(&expr.op); - self.nbsp(); - self.expr(&expr.right); - self.end(); - } - - fn expr_async(&mut self, expr: &ExprAsync) { - self.outer_attrs(&expr.attrs); - self.word("async "); - if expr.capture.is_some() { - self.word("move "); - } - self.cbox(INDENT); - self.small_block(&expr.block, &expr.attrs); - self.end(); - } - - fn expr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) { - self.outer_attrs(&expr.attrs); - self.cbox(INDENT); - self.subexpr_await(expr, beginning_of_line); - self.end(); - } - - fn subexpr_await(&mut self, expr: &ExprAwait, beginning_of_line: bool) { - self.subexpr(&expr.base, beginning_of_line); - self.zerobreak_unless_short_ident(beginning_of_line, &expr.base); - self.word(".await"); - } - - fn expr_binary(&mut self, expr: &ExprBinary) { - self.outer_attrs(&expr.attrs); - self.ibox(INDENT); - self.ibox(-INDENT); - self.expr(&expr.left); - self.end(); - self.space(); - self.binary_operator(&expr.op); - self.nbsp(); - self.expr(&expr.right); - self.end(); - } - - pub fn expr_block(&mut self, expr: &ExprBlock) { - self.outer_attrs(&expr.attrs); - if let Some(label) = &expr.label { - self.label(label); - } - self.cbox(INDENT); - self.small_block(&expr.block, &expr.attrs); - self.end(); - } - - fn expr_box(&mut self, expr: &ExprBox) { - self.outer_attrs(&expr.attrs); - self.word("box "); - self.expr(&expr.expr); - } - - fn expr_break(&mut self, expr: &ExprBreak) { - self.outer_attrs(&expr.attrs); - self.word("break"); - if let Some(lifetime) = &expr.label { - self.nbsp(); - self.lifetime(lifetime); - } - if let Some(value) = &expr.expr { - self.nbsp(); - self.expr(value); - } - } - - fn expr_call(&mut self, expr: &ExprCall, beginning_of_line: bool) { - self.outer_attrs(&expr.attrs); - self.expr_beginning_of_line(&expr.func, beginning_of_line); - self.call_args(&expr.args); - } - - fn subexpr_call(&mut self, expr: &ExprCall) { - self.subexpr(&expr.func, false); - self.call_args(&expr.args); - } - - fn expr_cast(&mut self, expr: &ExprCast) { - self.outer_attrs(&expr.attrs); - self.ibox(INDENT); - self.ibox(-INDENT); - self.expr(&expr.expr); - self.end(); - self.space(); - self.word("as "); - self.ty(&expr.ty); - self.end(); - } - - fn expr_closure(&mut self, expr: &ExprClosure) { - self.outer_attrs(&expr.attrs); - self.ibox(0); - if expr.asyncness.is_some() { - self.word("async "); - } - if expr.movability.is_some() { - self.word("static "); - } - if expr.capture.is_some() { - self.word("move "); - } - self.cbox(INDENT); - self.word("|"); - for pat in expr.inputs.iter().delimited() { - if pat.is_first { - self.zerobreak(); - } - self.pat(&pat); - if !pat.is_last { - self.word(","); - self.space(); - } - } - match &expr.output { - ReturnType::Default => { - self.word("|"); - self.space(); - self.offset(-INDENT); - self.end(); - } - ReturnType::Type(_arrow, ty) => { - if !expr.inputs.is_empty() { - self.trailing_comma(true); - self.offset(-INDENT); - } - self.word("|"); - self.end(); - self.word(" -> "); - self.ty(ty); - self.nbsp(); - } - } - self.neverbreak(); - self.expr(&expr.body); - self.end(); - } - - fn expr_continue(&mut self, expr: &ExprContinue) { - self.outer_attrs(&expr.attrs); - self.word("continue"); - if let Some(lifetime) = &expr.label { - self.nbsp(); - self.lifetime(lifetime); - } - } - - fn expr_field(&mut self, expr: &ExprField, beginning_of_line: bool) { - self.outer_attrs(&expr.attrs); - self.cbox(INDENT); - self.subexpr_field(expr, beginning_of_line); - self.end(); - } - - fn subexpr_field(&mut self, expr: &ExprField, beginning_of_line: bool) { - self.subexpr(&expr.base, beginning_of_line); - self.zerobreak_unless_short_ident(beginning_of_line, &expr.base); - self.word("."); - self.member(&expr.member); - } - - fn expr_for_loop(&mut self, expr: &ExprForLoop) { - self.outer_attrs(&expr.attrs); - self.ibox(0); - if let Some(label) = &expr.label { - self.label(label); - } - self.word("for "); - self.pat(&expr.pat); - self.word(" in "); - self.neverbreak(); - self.wrap_exterior_struct(&expr.expr); - self.word("{"); - self.neverbreak(); - self.cbox(INDENT); - self.hardbreak_if_nonempty(); - self.inner_attrs(&expr.attrs); - for stmt in &expr.body.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.end(); - } - - fn expr_group(&mut self, expr: &ExprGroup) { - self.outer_attrs(&expr.attrs); - self.expr(&expr.expr); - } - - fn expr_if(&mut self, expr: &ExprIf) { - self.outer_attrs(&expr.attrs); - self.cbox(INDENT); - self.word("if "); - self.cbox(-INDENT); - self.wrap_exterior_struct(&expr.cond); - self.end(); - if let Some((_else_token, else_branch)) = &expr.else_branch { - let mut else_branch = &**else_branch; - self.small_block(&expr.then_branch, &[]); - loop { - self.word(" else "); - match else_branch { - Expr::If(expr) => { - self.word("if "); - self.wrap_exterior_struct(&expr.cond); - self.small_block(&expr.then_branch, &[]); - if let Some((_else_token, next)) = &expr.else_branch { - else_branch = next; - continue; - } - } - Expr::Block(expr) => { - self.small_block(&expr.block, &[]); - } - // If not one of the valid expressions to exist in an else - // clause, wrap in a block. - other => { - self.word("{"); - self.space(); - self.ibox(INDENT); - self.expr(other); - self.end(); - self.space(); - self.offset(-INDENT); - self.word("}"); - } - } - break; - } - } else if expr.then_branch.stmts.is_empty() { - self.word("{}"); - } else { - self.word("{"); - self.hardbreak(); - for stmt in &expr.then_branch.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.word("}"); - } - self.end(); - } - - fn expr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) { - self.outer_attrs(&expr.attrs); - self.expr_beginning_of_line(&expr.expr, beginning_of_line); - self.word("["); - self.expr(&expr.index); - self.word("]"); - } - - fn subexpr_index(&mut self, expr: &ExprIndex, beginning_of_line: bool) { - self.subexpr(&expr.expr, beginning_of_line); - self.word("["); - self.expr(&expr.index); - self.word("]"); - } - - fn expr_let(&mut self, expr: &ExprLet) { - self.outer_attrs(&expr.attrs); - self.ibox(INDENT); - self.word("let "); - self.ibox(-INDENT); - self.pat(&expr.pat); - self.end(); - self.space(); - self.word("= "); - let needs_paren = contains_exterior_struct_lit(&expr.expr); - if needs_paren { - self.word("("); - } - self.expr(&expr.expr); - if needs_paren { - self.word(")"); - } - self.end(); - } - - pub fn expr_lit(&mut self, expr: &ExprLit) { - self.outer_attrs(&expr.attrs); - self.lit(&expr.lit); - } - - fn expr_loop(&mut self, expr: &ExprLoop) { - self.outer_attrs(&expr.attrs); - if let Some(label) = &expr.label { - self.label(label); - } - self.word("loop {"); - self.cbox(INDENT); - self.hardbreak_if_nonempty(); - self.inner_attrs(&expr.attrs); - for stmt in &expr.body.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - - fn expr_macro(&mut self, expr: &ExprMacro) { - self.outer_attrs(&expr.attrs); - self.mac(&expr.mac, None); - } - - fn expr_match(&mut self, expr: &ExprMatch) { - self.outer_attrs(&expr.attrs); - self.ibox(0); - self.word("match "); - self.wrap_exterior_struct(&expr.expr); - self.word("{"); - self.neverbreak(); - self.cbox(INDENT); - self.hardbreak_if_nonempty(); - self.inner_attrs(&expr.attrs); - for arm in &expr.arms { - self.arm(arm); - self.hardbreak(); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.end(); - } - - fn expr_method_call(&mut self, expr: &ExprMethodCall, beginning_of_line: bool) { - self.outer_attrs(&expr.attrs); - self.cbox(INDENT); - let unindent_call_args = beginning_of_line && is_short_ident(&expr.receiver); - self.subexpr_method_call(expr, beginning_of_line, unindent_call_args); - self.end(); - } - - fn subexpr_method_call( - &mut self, - expr: &ExprMethodCall, - beginning_of_line: bool, - unindent_call_args: bool, - ) { - self.subexpr(&expr.receiver, beginning_of_line); - self.zerobreak_unless_short_ident(beginning_of_line, &expr.receiver); - self.word("."); - self.ident(&expr.method); - if let Some(turbofish) = &expr.turbofish { - self.method_turbofish(turbofish); - } - self.cbox(if unindent_call_args { -INDENT } else { 0 }); - self.call_args(&expr.args); - self.end(); - } - - fn expr_paren(&mut self, expr: &ExprParen) { - self.outer_attrs(&expr.attrs); - self.word("("); - self.expr(&expr.expr); - self.word(")"); - } - - fn expr_path(&mut self, expr: &ExprPath) { - self.outer_attrs(&expr.attrs); - self.qpath(&expr.qself, &expr.path); - } - - fn expr_range(&mut self, expr: &ExprRange) { - self.outer_attrs(&expr.attrs); - if let Some(from) = &expr.from { - self.expr(from); - } - self.word(match expr.limits { - RangeLimits::HalfOpen(_) => "..", - RangeLimits::Closed(_) => "..=", - }); - if let Some(to) = &expr.to { - self.expr(to); - } - } - - fn expr_reference(&mut self, expr: &ExprReference) { - self.outer_attrs(&expr.attrs); - self.word("&"); - if expr.mutability.is_some() { - self.word("mut "); - } - self.expr(&expr.expr); - } - - fn expr_repeat(&mut self, expr: &ExprRepeat) { - self.outer_attrs(&expr.attrs); - self.word("["); - self.expr(&expr.expr); - self.word("; "); - self.expr(&expr.len); - self.word("]"); - } - - fn expr_return(&mut self, expr: &ExprReturn) { - self.outer_attrs(&expr.attrs); - self.word("return"); - if let Some(value) = &expr.expr { - self.nbsp(); - self.expr(value); - } - } - - fn expr_struct(&mut self, expr: &ExprStruct) { - self.outer_attrs(&expr.attrs); - self.cbox(INDENT); - self.ibox(-INDENT); - self.path(&expr.path); - self.end(); - self.word(" {"); - self.space_if_nonempty(); - for field_value in expr.fields.iter().delimited() { - self.field_value(&field_value); - self.trailing_comma_or_space(field_value.is_last && expr.rest.is_none()); - } - if let Some(rest) = &expr.rest { - self.word(".."); - self.expr(rest); - self.space(); - } - self.offset(-INDENT); - self.end_with_max_width(34); - self.word("}"); - } - - fn expr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) { - self.outer_attrs(&expr.attrs); - self.expr_beginning_of_line(&expr.expr, beginning_of_line); - self.word("?"); - } - - fn subexpr_try(&mut self, expr: &ExprTry, beginning_of_line: bool) { - self.subexpr(&expr.expr, beginning_of_line); - self.word("?"); - } - - fn expr_try_block(&mut self, expr: &ExprTryBlock) { - self.outer_attrs(&expr.attrs); - self.word("try "); - self.cbox(INDENT); - self.small_block(&expr.block, &expr.attrs); - self.end(); - } - - fn expr_tuple(&mut self, expr: &ExprTuple) { - self.outer_attrs(&expr.attrs); - self.word("("); - self.cbox(INDENT); - self.zerobreak(); - for elem in expr.elems.iter().delimited() { - self.expr(&elem); - if expr.elems.len() == 1 { - self.word(","); - self.zerobreak(); - } else { - self.trailing_comma(elem.is_last); - } - } - self.offset(-INDENT); - self.end(); - self.word(")"); - } - - fn expr_type(&mut self, expr: &ExprType) { - self.outer_attrs(&expr.attrs); - self.ibox(INDENT); - self.ibox(-INDENT); - self.expr(&expr.expr); - self.end(); - self.space(); - self.word(": "); - self.ty(&expr.ty); - self.end(); - } - - fn expr_unary(&mut self, expr: &ExprUnary) { - self.outer_attrs(&expr.attrs); - self.unary_operator(&expr.op); - self.expr(&expr.expr); - } - - fn expr_unsafe(&mut self, expr: &ExprUnsafe) { - self.outer_attrs(&expr.attrs); - self.word("unsafe {"); - self.cbox(INDENT); - self.space_if_nonempty(); - self.inner_attrs(&expr.attrs); - for stmt in expr.block.stmts.iter().delimited() { - if stmt.is_first && stmt.is_last { - if let Stmt::Expr(expr) = &*stmt { - self.expr(expr); - self.space(); - continue; - } - } - self.stmt(&stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - - fn expr_verbatim(&mut self, expr: &TokenStream) { - if !expr.is_empty() { - unimplemented!("Expr::Verbatim `{}`", expr); - } - } - - fn expr_while(&mut self, expr: &ExprWhile) { - self.outer_attrs(&expr.attrs); - if let Some(label) = &expr.label { - self.label(label); - } - self.word("while "); - self.wrap_exterior_struct(&expr.cond); - self.word("{"); - self.neverbreak(); - self.cbox(INDENT); - self.hardbreak_if_nonempty(); - self.inner_attrs(&expr.attrs); - for stmt in &expr.body.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - - fn expr_yield(&mut self, expr: &ExprYield) { - self.outer_attrs(&expr.attrs); - self.word("yield"); - if let Some(value) = &expr.expr { - self.nbsp(); - self.expr(value); - } - } - - fn label(&mut self, label: &Label) { - self.lifetime(&label.name); - self.word(": "); - } - - fn field_value(&mut self, field_value: &FieldValue) { - self.outer_attrs(&field_value.attrs); - self.member(&field_value.member); - if field_value.colon_token.is_some() { - self.word(": "); - self.ibox(0); - self.expr(&field_value.expr); - self.end(); - } - } - - fn arm(&mut self, arm: &Arm) { - self.outer_attrs(&arm.attrs); - self.ibox(0); - self.pat(&arm.pat); - if let Some((_if_token, guard)) = &arm.guard { - self.word(" if "); - self.expr(guard); - } - self.word(" =>"); - let empty_block; - let mut body = &*arm.body; - while let Expr::Block(expr) = body { - if expr.attrs.is_empty() && expr.label.is_none() { - let mut stmts = expr.block.stmts.iter(); - if let (Some(Stmt::Expr(inner)), None) = (stmts.next(), stmts.next()) { - body = inner; - continue; - } - } - break; - } - if let Expr::Tuple(expr) = body { - if expr.elems.is_empty() && expr.attrs.is_empty() { - empty_block = Expr::Block(ExprBlock { - attrs: Vec::new(), - label: None, - block: Block { - brace_token: token::Brace::default(), - stmts: Vec::new(), - }, - }); - body = &empty_block; - } - } - if let Expr::Block(body) = body { - self.nbsp(); - if let Some(label) = &body.label { - self.label(label); - } - self.word("{"); - self.neverbreak(); - self.cbox(INDENT); - self.hardbreak_if_nonempty(); - self.inner_attrs(&body.attrs); - for stmt in &body.block.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.end(); - } else { - self.nbsp(); - self.neverbreak(); - self.cbox(INDENT); - self.scan_break(BreakToken { - pre_break: Some('{'), - ..BreakToken::default() - }); - self.expr(body); - self.scan_break(BreakToken { - offset: -INDENT, - pre_break: stmt::add_semi(body).then(|| ';'), - post_break: Some('}'), - no_break: requires_terminator(body).then(|| ','), - ..BreakToken::default() - }); - self.end(); - self.end(); - } - } - - fn method_turbofish(&mut self, turbofish: &MethodTurbofish) { - self.word("::<"); - self.cbox(0); - self.zerobreak(); - for arg in turbofish.args.iter().delimited() { - self.generic_method_argument(&arg); - self.trailing_comma(arg.is_last); - } - self.offset(-INDENT); - self.end(); - self.word(">"); - } - - fn generic_method_argument(&mut self, generic: &GenericMethodArgument) { - match generic { - GenericMethodArgument::Type(arg) => self.ty(arg), - GenericMethodArgument::Const(arg) => self.expr(arg), - } - } - - fn call_args(&mut self, args: &Punctuated) { - self.word("("); - let mut iter = args.iter(); - match (iter.next(), iter.next()) { - ( - Some( - expr @ (Expr::Array(ExprArray { attrs, .. }) - | Expr::Async(ExprAsync { attrs, .. }) - | Expr::Block(ExprBlock { attrs, .. }) - | Expr::Closure(ExprClosure { attrs, .. }) - | Expr::Struct(ExprStruct { attrs, .. }) - | Expr::TryBlock(ExprTryBlock { attrs, .. }) - | Expr::Tuple(ExprTuple { attrs, .. }) - | Expr::Unsafe(ExprUnsafe { attrs, .. })), - ), - None, - ) if !attr::has_outer(attrs) => { - self.expr(expr); - } - _ => { - self.cbox(INDENT); - self.zerobreak(); - for arg in args.iter().delimited() { - self.expr(&arg); - self.trailing_comma(arg.is_last); - } - self.offset(-INDENT); - self.end(); - } - } - self.word(")"); - } - - fn small_block(&mut self, block: &Block, attrs: &[Attribute]) { - self.word("{"); - if attr::has_inner(attrs) || !block.stmts.is_empty() { - self.space(); - self.inner_attrs(attrs); - match (block.stmts.get(0), block.stmts.get(1)) { - (Some(Stmt::Expr(expr)), None) if stmt::break_after(expr) => { - self.ibox(0); - self.expr_beginning_of_line(expr, true); - self.end(); - self.space(); - } - _ => { - for stmt in &block.stmts { - self.stmt(stmt); - } - } - } - self.offset(-INDENT); - } - self.word("}"); - } - - pub fn member(&mut self, member: &Member) { - match member { - Member::Named(ident) => self.ident(ident), - Member::Unnamed(index) => self.index(index), - } - } - - fn index(&mut self, member: &Index) { - self.word(member.index.to_string()); - } - - fn binary_operator(&mut self, op: &BinOp) { - self.word(match op { - BinOp::Add(_) => "+", - BinOp::Sub(_) => "-", - BinOp::Mul(_) => "*", - BinOp::Div(_) => "/", - BinOp::Rem(_) => "%", - BinOp::And(_) => "&&", - BinOp::Or(_) => "||", - BinOp::BitXor(_) => "^", - BinOp::BitAnd(_) => "&", - BinOp::BitOr(_) => "|", - BinOp::Shl(_) => "<<", - BinOp::Shr(_) => ">>", - BinOp::Eq(_) => "==", - BinOp::Lt(_) => "<", - BinOp::Le(_) => "<=", - BinOp::Ne(_) => "!=", - BinOp::Ge(_) => ">=", - BinOp::Gt(_) => ">", - BinOp::AddEq(_) => "+=", - BinOp::SubEq(_) => "-=", - BinOp::MulEq(_) => "*=", - BinOp::DivEq(_) => "/=", - BinOp::RemEq(_) => "%=", - BinOp::BitXorEq(_) => "^=", - BinOp::BitAndEq(_) => "&=", - BinOp::BitOrEq(_) => "|=", - BinOp::ShlEq(_) => "<<=", - BinOp::ShrEq(_) => ">>=", - }); - } - - fn unary_operator(&mut self, op: &UnOp) { - self.word(match op { - UnOp::Deref(_) => "*", - UnOp::Not(_) => "!", - UnOp::Neg(_) => "-", - }); - } - - fn zerobreak_unless_short_ident(&mut self, beginning_of_line: bool, expr: &Expr) { - if beginning_of_line && is_short_ident(expr) { - return; - } - self.zerobreak(); - } -} - -pub fn requires_terminator(expr: &Expr) -> bool { - // see https://github.com/rust-lang/rust/blob/2679c38fc/src/librustc_ast/util/classify.rs#L7-L25 - match expr { - Expr::Unsafe(_) - | Expr::Block(_) - | Expr::If(_) - | Expr::Match(_) - | Expr::While(_) - | Expr::Loop(_) - | Expr::ForLoop(_) - | Expr::Async(_) - | Expr::TryBlock(_) => false, - _ => true, - } -} - -// Expressions that syntactically contain an "exterior" struct literal i.e. not -// surrounded by any parens or other delimiters. For example `X { y: 1 }`, `X { -// y: 1 }.method()`, `foo == X { y: 1 }` and `X { y: 1 } == foo` all do, but `(X -// { y: 1 }) == foo` does not. -fn contains_exterior_struct_lit(expr: &Expr) -> bool { - match expr { - Expr::Struct(_) => true, - - Expr::Assign(ExprAssign { left, right, .. }) - | Expr::AssignOp(ExprAssignOp { left, right, .. }) - | Expr::Binary(ExprBinary { left, right, .. }) => { - // X { y: 1 } + X { y: 2 } - contains_exterior_struct_lit(left) || contains_exterior_struct_lit(right) - } - - Expr::Await(ExprAwait { base: e, .. }) - | Expr::Box(ExprBox { expr: e, .. }) - | Expr::Cast(ExprCast { expr: e, .. }) - | Expr::Field(ExprField { base: e, .. }) - | Expr::Index(ExprIndex { expr: e, .. }) - | Expr::MethodCall(ExprMethodCall { receiver: e, .. }) - | Expr::Reference(ExprReference { expr: e, .. }) - | Expr::Type(ExprType { expr: e, .. }) - | Expr::Unary(ExprUnary { expr: e, .. }) => { - // &X { y: 1 }, X { y: 1 }.y - contains_exterior_struct_lit(e) - } - - _ => false, - } -} - -fn needs_newline_if_wrap(expr: &Expr) -> bool { - match expr { - Expr::Array(_) - | Expr::Async(_) - | Expr::Block(_) - | Expr::Break(ExprBreak { expr: None, .. }) - | Expr::Closure(_) - | Expr::Continue(_) - | Expr::ForLoop(_) - | Expr::If(_) - | Expr::Lit(_) - | Expr::Loop(_) - | Expr::Macro(_) - | Expr::Match(_) - | Expr::Path(_) - | Expr::Range(ExprRange { to: None, .. }) - | Expr::Repeat(_) - | Expr::Return(ExprReturn { expr: None, .. }) - | Expr::Struct(_) - | Expr::TryBlock(_) - | Expr::Tuple(_) - | Expr::Unsafe(_) - | Expr::Verbatim(_) - | Expr::While(_) - | Expr::Yield(ExprYield { expr: None, .. }) => false, - - Expr::Assign(_) - | Expr::AssignOp(_) - | Expr::Await(_) - | Expr::Binary(_) - | Expr::Cast(_) - | Expr::Field(_) - | Expr::Index(_) - | Expr::MethodCall(_) - | Expr::Type(_) => true, - - Expr::Box(ExprBox { expr: e, .. }) - | Expr::Break(ExprBreak { expr: Some(e), .. }) - | Expr::Call(ExprCall { func: e, .. }) - | Expr::Group(ExprGroup { expr: e, .. }) - | Expr::Let(ExprLet { expr: e, .. }) - | Expr::Paren(ExprParen { expr: e, .. }) - | Expr::Range(ExprRange { to: Some(e), .. }) - | Expr::Reference(ExprReference { expr: e, .. }) - | Expr::Return(ExprReturn { expr: Some(e), .. }) - | Expr::Try(ExprTry { expr: e, .. }) - | Expr::Unary(ExprUnary { expr: e, .. }) - | Expr::Yield(ExprYield { expr: Some(e), .. }) => needs_newline_if_wrap(e), - - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => false, - } -} - -fn is_short_ident(expr: &Expr) -> bool { - if let Expr::Path(expr) = expr { - if expr.attrs.is_empty() - && expr.qself.is_none() - && expr.path.leading_colon.is_none() - && expr.path.segments.len() == 1 - && expr.path.segments[0].ident.to_string().len() as isize <= INDENT - { - if let PathArguments::None = expr.path.segments[0].arguments { - return true; - } - } - } - false -} diff --git a/packages/autofmt/src/prettyplease/file.rs b/packages/autofmt/src/prettyplease/file.rs deleted file mode 100644 index 51eece54c..000000000 --- a/packages/autofmt/src/prettyplease/file.rs +++ /dev/null @@ -1,17 +0,0 @@ -use super::algorithm::Printer; -use syn::File; - -impl Printer { - pub fn file(&mut self, file: &File) { - self.cbox(0); - if let Some(shebang) = &file.shebang { - self.word(shebang.clone()); - self.hardbreak(); - } - self.inner_attrs(&file.attrs); - for item in &file.items { - self.item(item); - } - self.end(); - } -} diff --git a/packages/autofmt/src/prettyplease/generics.rs b/packages/autofmt/src/prettyplease/generics.rs deleted file mode 100644 index ded973b08..000000000 --- a/packages/autofmt/src/prettyplease/generics.rs +++ /dev/null @@ -1,280 +0,0 @@ -use super::algorithm::Printer; -use super::iter::IterDelimited; -use super::INDENT; -use syn::{ - BoundLifetimes, ConstParam, GenericParam, Generics, LifetimeDef, PredicateEq, - PredicateLifetime, PredicateType, TraitBound, TraitBoundModifier, TypeParam, TypeParamBound, - WhereClause, WherePredicate, -}; - -impl Printer { - pub fn generics(&mut self, generics: &Generics) { - if generics.params.is_empty() { - return; - } - - self.word("<"); - self.cbox(0); - self.zerobreak(); - - // Print lifetimes before types and consts, regardless of their - // order in self.params. - // - // TODO: ordering rules for const parameters vs type parameters have - // not been settled yet. https://github.com/rust-lang/rust/issues/44580 - for param in generics.params.iter().delimited() { - if let GenericParam::Lifetime(_) = *param { - self.generic_param(¶m); - self.trailing_comma(param.is_last); - } - } - for param in generics.params.iter().delimited() { - match *param { - GenericParam::Type(_) | GenericParam::Const(_) => { - self.generic_param(¶m); - self.trailing_comma(param.is_last); - } - GenericParam::Lifetime(_) => {} - } - } - - self.offset(-INDENT); - self.end(); - self.word(">"); - } - - fn generic_param(&mut self, generic_param: &GenericParam) { - match generic_param { - GenericParam::Type(type_param) => self.type_param(type_param), - GenericParam::Lifetime(lifetime_def) => self.lifetime_def(lifetime_def), - GenericParam::Const(const_param) => self.const_param(const_param), - } - } - - pub fn bound_lifetimes(&mut self, bound_lifetimes: &BoundLifetimes) { - self.word("for<"); - for lifetime_def in bound_lifetimes.lifetimes.iter().delimited() { - self.lifetime_def(&lifetime_def); - if !lifetime_def.is_last { - self.word(", "); - } - } - self.word("> "); - } - - fn lifetime_def(&mut self, lifetime_def: &LifetimeDef) { - self.outer_attrs(&lifetime_def.attrs); - self.lifetime(&lifetime_def.lifetime); - for lifetime in lifetime_def.bounds.iter().delimited() { - if lifetime.is_first { - self.word(": "); - } else { - self.word(" + "); - } - self.lifetime(&lifetime); - } - } - - fn type_param(&mut self, type_param: &TypeParam) { - self.outer_attrs(&type_param.attrs); - self.ident(&type_param.ident); - self.ibox(INDENT); - for type_param_bound in type_param.bounds.iter().delimited() { - if type_param_bound.is_first { - self.word(": "); - } else { - self.space(); - self.word("+ "); - } - self.type_param_bound(&type_param_bound); - } - if let Some(default) = &type_param.default { - self.space(); - self.word("= "); - self.ty(default); - } - self.end(); - } - - pub fn type_param_bound(&mut self, type_param_bound: &TypeParamBound) { - match type_param_bound { - TypeParamBound::Trait(trait_bound) => self.trait_bound(trait_bound), - TypeParamBound::Lifetime(lifetime) => self.lifetime(lifetime), - } - } - - fn trait_bound(&mut self, trait_bound: &TraitBound) { - if trait_bound.paren_token.is_some() { - self.word("("); - } - let skip = match trait_bound.path.segments.first() { - Some(segment) if segment.ident == "const" => { - self.word("~const "); - 1 - } - _ => 0, - }; - self.trait_bound_modifier(&trait_bound.modifier); - if let Some(bound_lifetimes) = &trait_bound.lifetimes { - self.bound_lifetimes(bound_lifetimes); - } - for segment in trait_bound.path.segments.iter().skip(skip).delimited() { - if !segment.is_first || trait_bound.path.leading_colon.is_some() { - self.word("::"); - } - self.path_segment(&segment); - } - if trait_bound.paren_token.is_some() { - self.word(")"); - } - } - - fn trait_bound_modifier(&mut self, trait_bound_modifier: &TraitBoundModifier) { - match trait_bound_modifier { - TraitBoundModifier::None => {} - TraitBoundModifier::Maybe(_question_mark) => self.word("?"), - } - } - - fn const_param(&mut self, const_param: &ConstParam) { - self.outer_attrs(&const_param.attrs); - self.word("const "); - self.ident(&const_param.ident); - self.word(": "); - self.ty(&const_param.ty); - if let Some(default) = &const_param.default { - self.word(" = "); - self.expr(default); - } - } - - pub fn where_clause_for_body(&mut self, where_clause: &Option) { - let hardbreaks = true; - let semi = false; - self.where_clause_impl(where_clause, hardbreaks, semi); - } - - pub fn where_clause_semi(&mut self, where_clause: &Option) { - let hardbreaks = true; - let semi = true; - self.where_clause_impl(where_clause, hardbreaks, semi); - } - - pub fn where_clause_oneline(&mut self, where_clause: &Option) { - let hardbreaks = false; - let semi = false; - self.where_clause_impl(where_clause, hardbreaks, semi); - } - - pub fn where_clause_oneline_semi(&mut self, where_clause: &Option) { - let hardbreaks = false; - let semi = true; - self.where_clause_impl(where_clause, hardbreaks, semi); - } - - fn where_clause_impl( - &mut self, - where_clause: &Option, - hardbreaks: bool, - semi: bool, - ) { - let where_clause = match where_clause { - Some(where_clause) if !where_clause.predicates.is_empty() => where_clause, - _ => { - if semi { - self.word(";"); - } else { - self.nbsp(); - } - return; - } - }; - if hardbreaks { - self.hardbreak(); - self.offset(-INDENT); - self.word("where"); - self.hardbreak(); - for predicate in where_clause.predicates.iter().delimited() { - self.where_predicate(&predicate); - if predicate.is_last && semi { - self.word(";"); - } else { - self.word(","); - self.hardbreak(); - } - } - if !semi { - self.offset(-INDENT); - } - } else { - self.space(); - self.offset(-INDENT); - self.word("where"); - self.space(); - for predicate in where_clause.predicates.iter().delimited() { - self.where_predicate(&predicate); - if predicate.is_last && semi { - self.word(";"); - } else { - self.trailing_comma_or_space(predicate.is_last); - } - } - if !semi { - self.offset(-INDENT); - } - } - } - - fn where_predicate(&mut self, predicate: &WherePredicate) { - match predicate { - WherePredicate::Type(predicate) => self.predicate_type(predicate), - WherePredicate::Lifetime(predicate) => self.predicate_lifetime(predicate), - WherePredicate::Eq(predicate) => self.predicate_eq(predicate), - } - } - - fn predicate_type(&mut self, predicate: &PredicateType) { - if let Some(bound_lifetimes) = &predicate.lifetimes { - self.bound_lifetimes(bound_lifetimes); - } - self.ty(&predicate.bounded_ty); - self.word(":"); - if predicate.bounds.len() == 1 { - self.ibox(0); - } else { - self.ibox(INDENT); - } - for type_param_bound in predicate.bounds.iter().delimited() { - if type_param_bound.is_first { - self.nbsp(); - } else { - self.space(); - self.word("+ "); - } - self.type_param_bound(&type_param_bound); - } - self.end(); - } - - fn predicate_lifetime(&mut self, predicate: &PredicateLifetime) { - self.lifetime(&predicate.lifetime); - self.word(":"); - self.ibox(INDENT); - for lifetime in predicate.bounds.iter().delimited() { - if lifetime.is_first { - self.nbsp(); - } else { - self.space(); - self.word("+ "); - } - self.lifetime(&lifetime); - } - self.end(); - } - - fn predicate_eq(&mut self, predicate: &PredicateEq) { - self.ty(&predicate.lhs_ty); - self.word(" = "); - self.ty(&predicate.rhs_ty); - } -} diff --git a/packages/autofmt/src/prettyplease/item.rs b/packages/autofmt/src/prettyplease/item.rs deleted file mode 100644 index 5512137d9..000000000 --- a/packages/autofmt/src/prettyplease/item.rs +++ /dev/null @@ -1,757 +0,0 @@ -use super::algorithm::Printer; -use super::iter::IterDelimited; -use super::INDENT; -use proc_macro2::TokenStream; -use syn::{ - Fields, FnArg, ForeignItem, ForeignItemFn, ForeignItemMacro, ForeignItemStatic, - ForeignItemType, ImplItem, ImplItemConst, ImplItemMacro, ImplItemMethod, ImplItemType, Item, - ItemConst, ItemEnum, ItemExternCrate, ItemFn, ItemForeignMod, ItemImpl, ItemMacro, ItemMacro2, - ItemMod, ItemStatic, ItemStruct, ItemTrait, ItemTraitAlias, ItemType, ItemUnion, ItemUse, Pat, - Receiver, Signature, Stmt, TraitItem, TraitItemConst, TraitItemMacro, TraitItemMethod, - TraitItemType, Type, UseGlob, UseGroup, UseName, UsePath, UseRename, UseTree, -}; - -impl Printer { - pub fn item(&mut self, item: &Item) { - match item { - Item::Const(item) => self.item_const(item), - Item::Enum(item) => self.item_enum(item), - Item::ExternCrate(item) => self.item_extern_crate(item), - Item::Fn(item) => self.item_fn(item), - Item::ForeignMod(item) => self.item_foreign_mod(item), - Item::Impl(item) => self.item_impl(item), - Item::Macro(item) => self.item_macro(item), - Item::Macro2(item) => self.item_macro2(item), - Item::Mod(item) => self.item_mod(item), - Item::Static(item) => self.item_static(item), - Item::Struct(item) => self.item_struct(item), - Item::Trait(item) => self.item_trait(item), - Item::TraitAlias(item) => self.item_trait_alias(item), - Item::Type(item) => self.item_type(item), - Item::Union(item) => self.item_union(item), - Item::Use(item) => self.item_use(item), - Item::Verbatim(item) => self.item_verbatim(item), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown Item"), - } - } - - fn item_const(&mut self, item: &ItemConst) { - self.outer_attrs(&item.attrs); - self.cbox(0); - self.visibility(&item.vis); - self.word("const "); - self.ident(&item.ident); - self.word(": "); - self.ty(&item.ty); - self.word(" = "); - self.neverbreak(); - self.expr(&item.expr); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn item_enum(&mut self, item: &ItemEnum) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.word("enum "); - self.ident(&item.ident); - self.generics(&item.generics); - self.where_clause_for_body(&item.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - for variant in &item.variants { - self.variant(variant); - self.word(","); - self.hardbreak(); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn item_extern_crate(&mut self, item: &ItemExternCrate) { - self.outer_attrs(&item.attrs); - self.visibility(&item.vis); - self.word("extern crate "); - self.ident(&item.ident); - if let Some((_as_token, rename)) = &item.rename { - self.word(" as "); - self.ident(rename); - } - self.word(";"); - self.hardbreak(); - } - - fn item_fn(&mut self, item: &ItemFn) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.signature(&item.sig); - self.where_clause_for_body(&item.sig.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&item.attrs); - for stmt in &item.block.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn item_foreign_mod(&mut self, item: &ItemForeignMod) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.abi(&item.abi); - self.word("{"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&item.attrs); - for foreign_item in &item.items { - self.foreign_item(foreign_item); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn item_impl(&mut self, item: &ItemImpl) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.ibox(-INDENT); - self.cbox(INDENT); - if item.defaultness.is_some() { - self.word("default "); - } - if item.unsafety.is_some() { - self.word("unsafe "); - } - self.word("impl"); - self.generics(&item.generics); - self.end(); - self.nbsp(); - if let Some((negative_polarity, path, _for_token)) = &item.trait_ { - if negative_polarity.is_some() { - self.word("!"); - } - self.path(path); - self.space(); - self.word("for "); - } - self.ty(&item.self_ty); - self.end(); - self.where_clause_for_body(&item.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&item.attrs); - for impl_item in &item.items { - self.impl_item(impl_item); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn item_macro(&mut self, item: &ItemMacro) { - self.outer_attrs(&item.attrs); - self.mac(&item.mac, item.ident.as_ref()); - self.mac_semi_if_needed(&item.mac.delimiter); - self.hardbreak(); - } - - fn item_macro2(&mut self, item: &ItemMacro2) { - unimplemented!("Item::Macro2 `macro {} {}`", item.ident, item.rules); - } - - fn item_mod(&mut self, item: &ItemMod) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.word("mod "); - self.ident(&item.ident); - if let Some((_brace, items)) = &item.content { - self.word(" {"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&item.attrs); - for item in items { - self.item(item); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } else { - self.word(";"); - self.end(); - } - self.hardbreak(); - } - - fn item_static(&mut self, item: &ItemStatic) { - self.outer_attrs(&item.attrs); - self.cbox(0); - self.visibility(&item.vis); - self.word("static "); - if item.mutability.is_some() { - self.word("mut "); - } - self.ident(&item.ident); - self.word(": "); - self.ty(&item.ty); - self.word(" = "); - self.neverbreak(); - self.expr(&item.expr); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn item_struct(&mut self, item: &ItemStruct) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.word("struct "); - self.ident(&item.ident); - self.generics(&item.generics); - match &item.fields { - Fields::Named(fields) => { - self.where_clause_for_body(&item.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - for field in &fields.named { - self.field(field); - self.word(","); - self.hardbreak(); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - Fields::Unnamed(fields) => { - self.fields_unnamed(fields); - self.where_clause_semi(&item.generics.where_clause); - self.end(); - } - Fields::Unit => { - self.where_clause_semi(&item.generics.where_clause); - self.end(); - } - } - self.hardbreak(); - } - - fn item_trait(&mut self, item: &ItemTrait) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - if item.unsafety.is_some() { - self.word("unsafe "); - } - if item.auto_token.is_some() { - self.word("auto "); - } - self.word("trait "); - self.ident(&item.ident); - self.generics(&item.generics); - for supertrait in item.supertraits.iter().delimited() { - if supertrait.is_first { - self.word(": "); - } else { - self.word(" + "); - } - self.type_param_bound(&supertrait); - } - self.where_clause_for_body(&item.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&item.attrs); - for trait_item in &item.items { - self.trait_item(trait_item); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn item_trait_alias(&mut self, item: &ItemTraitAlias) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.word("trait "); - self.ident(&item.ident); - self.generics(&item.generics); - self.word(" = "); - self.neverbreak(); - for bound in item.bounds.iter().delimited() { - if !bound.is_first { - self.space(); - self.word("+ "); - } - self.type_param_bound(&bound); - } - self.where_clause_semi(&item.generics.where_clause); - self.end(); - self.hardbreak(); - } - - fn item_type(&mut self, item: &ItemType) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.word("type "); - self.ident(&item.ident); - self.generics(&item.generics); - self.where_clause_oneline(&item.generics.where_clause); - self.word("= "); - self.neverbreak(); - self.ibox(-INDENT); - self.ty(&item.ty); - self.end(); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn item_union(&mut self, item: &ItemUnion) { - self.outer_attrs(&item.attrs); - self.cbox(INDENT); - self.visibility(&item.vis); - self.word("union "); - self.ident(&item.ident); - self.generics(&item.generics); - self.where_clause_for_body(&item.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - for field in &item.fields.named { - self.field(field); - self.word(","); - self.hardbreak(); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn item_use(&mut self, item: &ItemUse) { - self.outer_attrs(&item.attrs); - self.visibility(&item.vis); - self.word("use "); - if item.leading_colon.is_some() { - self.word("::"); - } - self.use_tree(&item.tree); - self.word(";"); - self.hardbreak(); - } - - fn item_verbatim(&mut self, item: &TokenStream) { - if !item.is_empty() { - unimplemented!("Item::Verbatim `{}`", item); - } - self.hardbreak(); - } - - fn use_tree(&mut self, use_tree: &UseTree) { - match use_tree { - UseTree::Path(use_path) => self.use_path(use_path), - UseTree::Name(use_name) => self.use_name(use_name), - UseTree::Rename(use_rename) => self.use_rename(use_rename), - UseTree::Glob(use_glob) => self.use_glob(use_glob), - UseTree::Group(use_group) => self.use_group(use_group), - } - } - - fn use_path(&mut self, use_path: &UsePath) { - self.ident(&use_path.ident); - self.word("::"); - self.use_tree(&use_path.tree); - } - - fn use_name(&mut self, use_name: &UseName) { - self.ident(&use_name.ident); - } - - fn use_rename(&mut self, use_rename: &UseRename) { - self.ident(&use_rename.ident); - self.word(" as "); - self.ident(&use_rename.rename); - } - - fn use_glob(&mut self, use_glob: &UseGlob) { - let _ = use_glob; - self.word("*"); - } - - fn use_group(&mut self, use_group: &UseGroup) { - if use_group.items.is_empty() { - self.word("{}"); - } else if use_group.items.len() == 1 { - self.use_tree(&use_group.items[0]); - } else { - self.cbox(INDENT); - self.word("{"); - self.zerobreak(); - self.ibox(0); - for use_tree in use_group.items.iter().delimited() { - self.use_tree(&use_tree); - if !use_tree.is_last { - self.word(","); - let mut use_tree = *use_tree; - while let UseTree::Path(use_path) = use_tree { - use_tree = &use_path.tree; - } - if let UseTree::Group(_) = use_tree { - self.hardbreak(); - } else { - self.space(); - } - } - } - self.end(); - self.trailing_comma(true); - self.offset(-INDENT); - self.word("}"); - self.end(); - } - } - - fn foreign_item(&mut self, foreign_item: &ForeignItem) { - match foreign_item { - ForeignItem::Fn(item) => self.foreign_item_fn(item), - ForeignItem::Static(item) => self.foreign_item_static(item), - ForeignItem::Type(item) => self.foreign_item_type(item), - ForeignItem::Macro(item) => self.foreign_item_macro(item), - ForeignItem::Verbatim(item) => self.foreign_item_verbatim(item), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown ForeignItem"), - } - } - - fn foreign_item_fn(&mut self, foreign_item: &ForeignItemFn) { - self.outer_attrs(&foreign_item.attrs); - self.cbox(INDENT); - self.visibility(&foreign_item.vis); - self.signature(&foreign_item.sig); - self.where_clause_semi(&foreign_item.sig.generics.where_clause); - self.end(); - self.hardbreak(); - } - - fn foreign_item_static(&mut self, foreign_item: &ForeignItemStatic) { - self.outer_attrs(&foreign_item.attrs); - self.cbox(0); - self.visibility(&foreign_item.vis); - self.word("static "); - if foreign_item.mutability.is_some() { - self.word("mut "); - } - self.ident(&foreign_item.ident); - self.word(": "); - self.ty(&foreign_item.ty); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn foreign_item_type(&mut self, foreign_item: &ForeignItemType) { - self.outer_attrs(&foreign_item.attrs); - self.cbox(0); - self.visibility(&foreign_item.vis); - self.word("type "); - self.ident(&foreign_item.ident); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn foreign_item_macro(&mut self, foreign_item: &ForeignItemMacro) { - self.outer_attrs(&foreign_item.attrs); - self.mac(&foreign_item.mac, None); - self.mac_semi_if_needed(&foreign_item.mac.delimiter); - self.hardbreak(); - } - - fn foreign_item_verbatim(&mut self, foreign_item: &TokenStream) { - if !foreign_item.is_empty() { - unimplemented!("ForeignItem::Verbatim `{}`", foreign_item); - } - self.hardbreak(); - } - - fn trait_item(&mut self, trait_item: &TraitItem) { - match trait_item { - TraitItem::Const(item) => self.trait_item_const(item), - TraitItem::Method(item) => self.trait_item_method(item), - TraitItem::Type(item) => self.trait_item_type(item), - TraitItem::Macro(item) => self.trait_item_macro(item), - TraitItem::Verbatim(item) => self.trait_item_verbatim(item), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown TraitItem"), - } - } - - fn trait_item_const(&mut self, trait_item: &TraitItemConst) { - self.outer_attrs(&trait_item.attrs); - self.cbox(0); - self.word("const "); - self.ident(&trait_item.ident); - self.word(": "); - self.ty(&trait_item.ty); - if let Some((_eq_token, default)) = &trait_item.default { - self.word(" = "); - self.neverbreak(); - self.expr(default); - } - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn trait_item_method(&mut self, trait_item: &TraitItemMethod) { - self.outer_attrs(&trait_item.attrs); - self.cbox(INDENT); - self.signature(&trait_item.sig); - if let Some(block) = &trait_item.default { - self.where_clause_for_body(&trait_item.sig.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&trait_item.attrs); - for stmt in &block.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } else { - self.where_clause_semi(&trait_item.sig.generics.where_clause); - self.end(); - } - self.hardbreak(); - } - - fn trait_item_type(&mut self, trait_item: &TraitItemType) { - self.outer_attrs(&trait_item.attrs); - self.cbox(INDENT); - self.word("type "); - self.ident(&trait_item.ident); - self.generics(&trait_item.generics); - for bound in trait_item.bounds.iter().delimited() { - if bound.is_first { - self.word(": "); - } else { - self.space(); - self.word("+ "); - } - self.type_param_bound(&bound); - } - if let Some((_eq_token, default)) = &trait_item.default { - self.where_clause_oneline(&trait_item.generics.where_clause); - self.word("= "); - self.neverbreak(); - self.ty(default); - } else { - self.where_clause_oneline_semi(&trait_item.generics.where_clause); - } - self.end(); - self.hardbreak(); - } - - fn trait_item_macro(&mut self, trait_item: &TraitItemMacro) { - self.outer_attrs(&trait_item.attrs); - self.mac(&trait_item.mac, None); - self.mac_semi_if_needed(&trait_item.mac.delimiter); - self.hardbreak(); - } - - fn trait_item_verbatim(&mut self, trait_item: &TokenStream) { - if !trait_item.is_empty() { - unimplemented!("TraitItem::Verbatim `{}`", trait_item); - } - self.hardbreak(); - } - - fn impl_item(&mut self, impl_item: &ImplItem) { - match impl_item { - ImplItem::Const(item) => self.impl_item_const(item), - ImplItem::Method(item) => self.impl_item_method(item), - ImplItem::Type(item) => self.impl_item_type(item), - ImplItem::Macro(item) => self.impl_item_macro(item), - ImplItem::Verbatim(item) => self.impl_item_verbatim(item), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown ImplItem"), - } - } - - fn impl_item_const(&mut self, impl_item: &ImplItemConst) { - self.outer_attrs(&impl_item.attrs); - self.cbox(0); - self.visibility(&impl_item.vis); - if impl_item.defaultness.is_some() { - self.word("default "); - } - self.word("const "); - self.ident(&impl_item.ident); - self.word(": "); - self.ty(&impl_item.ty); - self.word(" = "); - self.neverbreak(); - self.expr(&impl_item.expr); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn impl_item_method(&mut self, impl_item: &ImplItemMethod) { - self.outer_attrs(&impl_item.attrs); - self.cbox(INDENT); - self.visibility(&impl_item.vis); - if impl_item.defaultness.is_some() { - self.word("default "); - } - self.signature(&impl_item.sig); - if impl_item.block.stmts.len() == 1 { - if let Stmt::Item(Item::Verbatim(verbatim)) = &impl_item.block.stmts[0] { - if verbatim.to_string() == ";" { - self.where_clause_semi(&impl_item.sig.generics.where_clause); - self.end(); - self.hardbreak(); - return; - } - } - } - self.where_clause_for_body(&impl_item.sig.generics.where_clause); - self.word("{"); - self.hardbreak_if_nonempty(); - self.inner_attrs(&impl_item.attrs); - for stmt in &impl_item.block.stmts { - self.stmt(stmt); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - self.hardbreak(); - } - - fn impl_item_type(&mut self, impl_item: &ImplItemType) { - self.outer_attrs(&impl_item.attrs); - self.cbox(INDENT); - self.visibility(&impl_item.vis); - if impl_item.defaultness.is_some() { - self.word("default "); - } - self.word("type "); - self.ident(&impl_item.ident); - self.generics(&impl_item.generics); - self.where_clause_oneline(&impl_item.generics.where_clause); - self.word("= "); - self.neverbreak(); - self.ibox(-INDENT); - self.ty(&impl_item.ty); - self.end(); - self.word(";"); - self.end(); - self.hardbreak(); - } - - fn impl_item_macro(&mut self, impl_item: &ImplItemMacro) { - self.outer_attrs(&impl_item.attrs); - self.mac(&impl_item.mac, None); - self.mac_semi_if_needed(&impl_item.mac.delimiter); - self.hardbreak(); - } - - fn impl_item_verbatim(&mut self, impl_item: &TokenStream) { - if !impl_item.is_empty() { - unimplemented!("ImplItem::Verbatim `{}`", impl_item); - } - self.hardbreak(); - } - - fn maybe_variadic(&mut self, arg: &FnArg) -> bool { - let pat_type = match arg { - FnArg::Typed(pat_type) => pat_type, - FnArg::Receiver(receiver) => { - self.receiver(receiver); - return false; - } - }; - - match pat_type.ty.as_ref() { - Type::Verbatim(ty) if ty.to_string() == "..." => { - match pat_type.pat.as_ref() { - Pat::Verbatim(pat) if pat.to_string() == "..." => { - self.outer_attrs(&pat_type.attrs); - self.word("..."); - } - _ => self.pat_type(pat_type), - } - true - } - _ => { - self.pat_type(pat_type); - false - } - } - } - - fn signature(&mut self, signature: &Signature) { - if signature.constness.is_some() { - self.word("const "); - } - if signature.asyncness.is_some() { - self.word("async "); - } - if signature.unsafety.is_some() { - self.word("unsafe "); - } - if let Some(abi) = &signature.abi { - self.abi(abi); - } - self.word("fn "); - self.ident(&signature.ident); - self.generics(&signature.generics); - self.word("("); - self.neverbreak(); - self.cbox(0); - self.zerobreak(); - let mut last_is_variadic = false; - for input in signature.inputs.iter().delimited() { - last_is_variadic = self.maybe_variadic(&input); - if last_is_variadic { - self.zerobreak(); - } else { - self.trailing_comma(input.is_last); - } - } - if signature.variadic.is_some() && !last_is_variadic { - self.word("..."); - self.zerobreak(); - } - self.offset(-INDENT); - self.end(); - self.word(")"); - self.return_type(&signature.output); - } - - fn receiver(&mut self, receiver: &Receiver) { - self.outer_attrs(&receiver.attrs); - if let Some((_ampersand, lifetime)) = &receiver.reference { - self.word("&"); - if let Some(lifetime) = lifetime { - self.lifetime(lifetime); - self.nbsp(); - } - } - if receiver.mutability.is_some() { - self.word("mut "); - } - self.word("self"); - } -} diff --git a/packages/autofmt/src/prettyplease/iter.rs b/packages/autofmt/src/prettyplease/iter.rs deleted file mode 100644 index 702c653f5..000000000 --- a/packages/autofmt/src/prettyplease/iter.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::iter::Peekable; -use std::ops::Deref; - -pub struct Delimited { - is_first: bool, - iter: Peekable, -} - -pub trait IterDelimited: Iterator + Sized { - fn delimited(self) -> Delimited { - Delimited { - is_first: true, - iter: self.peekable(), - } - } -} - -impl IterDelimited for I {} - -pub struct IteratorItem { - value: T, - pub is_first: bool, - pub is_last: bool, -} - -impl Iterator for Delimited { - type Item = IteratorItem; - - fn next(&mut self) -> Option { - let item = IteratorItem { - value: self.iter.next()?, - is_first: self.is_first, - is_last: self.iter.peek().is_none(), - }; - self.is_first = false; - Some(item) - } -} - -impl Deref for IteratorItem { - type Target = T; - - fn deref(&self) -> &Self::Target { - &self.value - } -} diff --git a/packages/autofmt/src/prettyplease/lifetime.rs b/packages/autofmt/src/prettyplease/lifetime.rs deleted file mode 100644 index 760d5776d..000000000 --- a/packages/autofmt/src/prettyplease/lifetime.rs +++ /dev/null @@ -1,9 +0,0 @@ -use super::algorithm::Printer; -use syn::Lifetime; - -impl Printer { - pub fn lifetime(&mut self, lifetime: &Lifetime) { - self.word("'"); - self.ident(&lifetime.ident); - } -} diff --git a/packages/autofmt/src/prettyplease/lit.rs b/packages/autofmt/src/prettyplease/lit.rs deleted file mode 100644 index 37f2160ad..000000000 --- a/packages/autofmt/src/prettyplease/lit.rs +++ /dev/null @@ -1,50 +0,0 @@ -use super::algorithm::Printer; -use proc_macro2::Literal; -use syn::{Lit, LitBool, LitByte, LitByteStr, LitChar, LitFloat, LitInt, LitStr}; - -impl Printer { - pub fn lit(&mut self, lit: &Lit) { - match lit { - Lit::Str(lit) => self.lit_str(lit), - Lit::ByteStr(lit) => self.lit_byte_str(lit), - Lit::Byte(lit) => self.lit_byte(lit), - Lit::Char(lit) => self.lit_char(lit), - Lit::Int(lit) => self.lit_int(lit), - Lit::Float(lit) => self.lit_float(lit), - Lit::Bool(lit) => self.lit_bool(lit), - Lit::Verbatim(lit) => self.lit_verbatim(lit), - } - } - - pub fn lit_str(&mut self, lit: &LitStr) { - self.word(lit.token().to_string()); - } - - fn lit_byte_str(&mut self, lit: &LitByteStr) { - self.word(lit.token().to_string()); - } - - fn lit_byte(&mut self, lit: &LitByte) { - self.word(lit.token().to_string()); - } - - fn lit_char(&mut self, lit: &LitChar) { - self.word(lit.token().to_string()); - } - - fn lit_int(&mut self, lit: &LitInt) { - self.word(lit.token().to_string()); - } - - fn lit_float(&mut self, lit: &LitFloat) { - self.word(lit.token().to_string()); - } - - fn lit_bool(&mut self, lit: &LitBool) { - self.word(if lit.value { "true" } else { "false" }); - } - - fn lit_verbatim(&mut self, token: &Literal) { - self.word(token.to_string()); - } -} diff --git a/packages/autofmt/src/prettyplease/mac.rs b/packages/autofmt/src/prettyplease/mac.rs deleted file mode 100644 index 4608bc303..000000000 --- a/packages/autofmt/src/prettyplease/mac.rs +++ /dev/null @@ -1,220 +0,0 @@ -use super::algorithm::Printer; -use super::token::Token; -use super::INDENT; -use proc_macro2::{Delimiter, Spacing, TokenStream}; -use syn::{Ident, Macro, MacroDelimiter, PathArguments}; - -impl Printer { - pub fn mac(&mut self, mac: &Macro, ident: Option<&Ident>) { - let is_macro_rules = mac.path.leading_colon.is_none() - && mac.path.segments.len() == 1 - && matches!(mac.path.segments[0].arguments, PathArguments::None) - && mac.path.segments[0].ident == "macro_rules"; - if is_macro_rules { - if let Some(ident) = ident { - self.macro_rules(ident, &mac.tokens); - return; - } - } - self.path(&mac.path); - self.word("!"); - if let Some(ident) = ident { - self.nbsp(); - self.ident(ident); - } - let (open, close, delimiter_break) = match mac.delimiter { - MacroDelimiter::Paren(_) => ("(", ")", Self::zerobreak as fn(&mut Self)), - MacroDelimiter::Brace(_) => (" {", "}", Self::hardbreak as fn(&mut Self)), - MacroDelimiter::Bracket(_) => ("[", "]", Self::zerobreak as fn(&mut Self)), - }; - self.word(open); - self.cbox(INDENT); - delimiter_break(self); - self.ibox(0); - self.macro_rules_tokens(mac.tokens.clone(), false); - self.end(); - delimiter_break(self); - self.offset(-INDENT); - self.end(); - self.word(close); - } - - pub fn mac_semi_if_needed(&mut self, delimiter: &MacroDelimiter) { - match delimiter { - MacroDelimiter::Paren(_) | MacroDelimiter::Bracket(_) => self.word(";"), - MacroDelimiter::Brace(_) => {} - } - } - - fn macro_rules(&mut self, name: &Ident, rules: &TokenStream) { - enum State { - Start, - Matcher, - Equal, - Greater, - Expander, - } - - use State::*; - - self.word("macro_rules! "); - self.ident(name); - self.word(" {"); - self.cbox(INDENT); - self.hardbreak_if_nonempty(); - let mut state = State::Start; - for tt in rules.clone() { - let token = Token::from(tt); - match (state, token) { - (Start, Token::Group(delimiter, stream)) => { - self.delimiter_open(delimiter); - if !stream.is_empty() { - self.cbox(INDENT); - self.zerobreak(); - self.ibox(0); - self.macro_rules_tokens(stream, true); - self.end(); - self.zerobreak(); - self.offset(-INDENT); - self.end(); - } - self.delimiter_close(delimiter); - state = Matcher; - } - (Matcher, Token::Punct('=', Spacing::Joint)) => { - self.word(" ="); - state = Equal; - } - (Equal, Token::Punct('>', Spacing::Alone)) => { - self.word(">"); - state = Greater; - } - (Greater, Token::Group(_delimiter, stream)) => { - self.word(" {"); - self.neverbreak(); - if !stream.is_empty() { - self.cbox(INDENT); - self.hardbreak(); - self.ibox(0); - self.macro_rules_tokens(stream, false); - self.end(); - self.hardbreak(); - self.offset(-INDENT); - self.end(); - } - self.word("}"); - state = Expander; - } - (Expander, Token::Punct(';', Spacing::Alone)) => { - self.word(";"); - self.hardbreak(); - state = Start; - } - _ => unimplemented!("bad macro_rules syntax"), - } - } - match state { - Start => {} - Expander => { - self.word(";"); - self.hardbreak(); - } - _ => self.hardbreak(), - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - - fn macro_rules_tokens(&mut self, stream: TokenStream, matcher: bool) { - #[derive(PartialEq)] - enum State { - Start, - Dollar, - DollarIdent, - DollarIdentColon, - DollarParen, - DollarParenSep, - Pound, - PoundBang, - Dot, - Colon, - Colon2, - Ident, - IdentBang, - Delim, - Other, - } - - use State::*; - - let mut state = Start; - let mut previous_is_joint = true; - for tt in stream { - let token = Token::from(tt); - let (needs_space, next_state) = match (&state, &token) { - (Dollar, Token::Ident(_)) => (false, if matcher { DollarIdent } else { Other }), - (DollarIdent, Token::Punct(':', Spacing::Alone)) => (false, DollarIdentColon), - (DollarIdentColon, Token::Ident(_)) => (false, Other), - (DollarParen, Token::Punct('+' | '*' | '?', Spacing::Alone)) => (false, Other), - (DollarParen, Token::Ident(_) | Token::Literal(_)) => (false, DollarParenSep), - (DollarParen, Token::Punct(_, Spacing::Joint)) => (false, DollarParen), - (DollarParen, Token::Punct(_, Spacing::Alone)) => (false, DollarParenSep), - (DollarParenSep, Token::Punct('+' | '*', _)) => (false, Other), - (Pound, Token::Punct('!', _)) => (false, PoundBang), - (Dollar, Token::Group(Delimiter::Parenthesis, _)) => (false, DollarParen), - (Pound | PoundBang, Token::Group(Delimiter::Bracket, _)) => (false, Other), - (Ident, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => { - (false, Delim) - } - (Ident, Token::Punct('!', Spacing::Alone)) => (false, IdentBang), - (IdentBang, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => { - (false, Other) - } - (Colon, Token::Punct(':', _)) => (false, Colon2), - (_, Token::Group(Delimiter::Parenthesis | Delimiter::Bracket, _)) => (true, Delim), - (_, Token::Group(Delimiter::Brace | Delimiter::None, _)) => (true, Other), - (_, Token::Ident(ident)) if !is_keyword(ident) => { - (state != Dot && state != Colon2, Ident) - } - (_, Token::Literal(_)) => (state != Dot, Ident), - (_, Token::Punct(',' | ';', _)) => (false, Other), - (_, Token::Punct('.', _)) if !matcher => (state != Ident && state != Delim, Dot), - (_, Token::Punct(':', Spacing::Joint)) => (state != Ident, Colon), - (_, Token::Punct('$', _)) => (true, Dollar), - (_, Token::Punct('#', _)) => (true, Pound), - (_, _) => (true, Other), - }; - if !previous_is_joint { - if needs_space { - self.space(); - } else if let Token::Punct('.', _) = token { - self.zerobreak(); - } - } - previous_is_joint = match token { - Token::Punct(_, Spacing::Joint) | Token::Punct('$', _) => true, - _ => false, - }; - self.single_token( - token, - if matcher { - |printer, stream| printer.macro_rules_tokens(stream, true) - } else { - |printer, stream| printer.macro_rules_tokens(stream, false) - }, - ); - state = next_state; - } - } -} - -fn is_keyword(ident: &Ident) -> bool { - match ident.to_string().as_str() { - "as" | "box" | "break" | "const" | "continue" | "crate" | "else" | "enum" | "extern" - | "fn" | "for" | "if" | "impl" | "in" | "let" | "loop" | "macro" | "match" | "mod" - | "move" | "mut" | "pub" | "ref" | "return" | "static" | "struct" | "trait" | "type" - | "unsafe" | "use" | "where" | "while" | "yield" => true, - _ => false, - } -} diff --git a/packages/autofmt/src/prettyplease/mod.rs b/packages/autofmt/src/prettyplease/mod.rs deleted file mode 100644 index 68d8e1303..000000000 --- a/packages/autofmt/src/prettyplease/mod.rs +++ /dev/null @@ -1,381 +0,0 @@ -//! [![github]](https://github.com/dtolnay/prettyplease) [![crates-io]](https://crates.io/crates/prettyplease) [![docs-rs]](https://docs.rs/prettyplease) -//! -//! [github]: https://img.shields.io/badge/github-8da0cb?style=for-the-badge&labelColor=555555&logo=github -//! [crates-io]: https://img.shields.io/badge/crates.io-fc8d62?style=for-the-badge&labelColor=555555&logo=rust -//! [docs-rs]: https://img.shields.io/badge/docs.rs-66c2a5?style=for-the-badge&labelColor=555555&logoColor=white&logo=data:image/svg+xml;base64,PHN2ZyByb2xlPSJpbWciIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgdmlld0JveD0iMCAwIDUxMiA1MTIiPjxwYXRoIGZpbGw9IiNmNWY1ZjUiIGQ9Ik00ODguNiAyNTAuMkwzOTIgMjE0VjEwNS41YzAtMTUtOS4zLTI4LjQtMjMuNC0zMy43bC0xMDAtMzcuNWMtOC4xLTMuMS0xNy4xLTMuMS0yNS4zIDBsLTEwMCAzNy41Yy0xNC4xIDUuMy0yMy40IDE4LjctMjMuNCAzMy43VjIxNGwtOTYuNiAzNi4yQzkuMyAyNTUuNSAwIDI2OC45IDAgMjgzLjlWMzk0YzAgMTMuNiA3LjcgMjYuMSAxOS45IDMyLjJsMTAwIDUwYzEwLjEgNS4xIDIyLjEgNS4xIDMyLjIgMGwxMDMuOS01MiAxMDMuOSA1MmMxMC4xIDUuMSAyMi4xIDUuMSAzMi4yIDBsMTAwLTUwYzEyLjItNi4xIDE5LjktMTguNiAxOS45LTMyLjJWMjgzLjljMC0xNS05LjMtMjguNC0yMy40LTMzLjd6TTM1OCAyMTQuOGwtODUgMzEuOXYtNjguMmw4NS0zN3Y3My4zek0xNTQgMTA0LjFsMTAyLTM4LjIgMTAyIDM4LjJ2LjZsLTEwMiA0MS40LTEwMi00MS40di0uNnptODQgMjkxLjFsLTg1IDQyLjV2LTc5LjFsODUtMzguOHY3NS40em0wLTExMmwtMTAyIDQxLjQtMTAyLTQxLjR2LS42bDEwMi0zOC4yIDEwMiAzOC4ydi42em0yNDAgMTEybC04NSA0Mi41di03OS4xbDg1LTM4Ljh2NzUuNHptMC0xMTJsLTEwMiA0MS40LTEwMi00MS40di0uNmwxMDItMzguMiAxMDIgMzguMnYuNnoiPjwvcGF0aD48L3N2Zz4K -//! -//!
-//! -//! **prettyplease::unparse** — a minimal `syn` syntax tree pretty-printer -//! -//!
-//! -//! # Overview -//! -//! This is a pretty-printer to turn a `syn` syntax tree into a `String` of -//! well-formatted source code. In contrast to rustfmt, this library is intended -//! to be suitable for arbitrary generated code. -//! -//! Rustfmt prioritizes high-quality output that is impeccable enough that you'd -//! be comfortable spending your career staring at its output — but that -//! means some heavyweight algorithms, and it has a tendency to bail out on code -//! that is hard to format (for example [rustfmt#3697], and there are dozens -//! more issues like it). That's not necessarily a big deal for human-generated -//! code because when code gets highly nested, the human will naturally be -//! inclined to refactor into more easily formattable code. But for generated -//! code, having the formatter just give up leaves it totally unreadable. -//! -//! [rustfmt#3697]: https://github.com/rust-lang/rustfmt/issues/3697 -//! -//! This library is designed using the simplest possible algorithm and data -//! structures that can deliver about 95% of the quality of rustfmt-formatted -//! output. In my experience testing real-world code, approximately 97-98% of -//! output lines come out identical between rustfmt's formatting and this -//! crate's. The rest have slightly different linebreak decisions, but still -//! clearly follow the dominant modern Rust style. -//! -//! The tradeoffs made by this crate are a good fit for generated code that you -//! will *not* spend your career staring at. For example, the output of -//! `bindgen`, or the output of `cargo-expand`. In those cases it's more -//! important that the whole thing be formattable without the formatter giving -//! up, than that it be flawless. -//! -//!
-//! -//! # Feature matrix -//! -//! Here are a few superficial comparisons of this crate against the AST -//! pretty-printer built into rustc, and rustfmt. The sections below go into -//! more detail comparing the output of each of these libraries. -//! -//! | | prettyplease | rustc | rustfmt | -//! | --- | --- | --- | --- | -//! | non-pathological behavior on big or generated code | ✅ | ❌ | ❌ | -//! | idiomatic modern formatting ("locally indistinguishable from rustfmt") | ✅ | ❌ | ✅ | -//! | throughput | 60 MB/s | 39 MB/s | 2.8 MB/s | -//! | number of dependencies | 3 | 72 | 66 | -//! | compile time including dependencies | 2.4 sec | 23.1 sec | 29.8 sec | -//! | buildable using a stable Rust compiler | ✅ | ❌ | ❌ | -//! | published to crates.io | ✅ | ❌ | ❌ | -//! | extensively configurable output | ❌ | ❌ | ✅ | -//! | intended to accommodate hand-maintained source code | ❌ | ❌ | ✅ | -//! -//!
-//! -//! # Comparison to rustfmt -//! -//! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) -//! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) -//! - [output.rustfmt.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustfmt.rs) -//! -//! If you weren't told which output file is which, it would be practically -//! impossible to tell — **except** for line 435 in the rustfmt output, -//! which is more than 1000 characters long because rustfmt just gave up -//! formatting that part of the file: -//! -//! ``` -//! # const _: &str = stringify! {{{ -//! match segments[5] { -//! 0 => write!(f, "::{}", ipv4), -//! 0xffff => write!(f, "::ffff:{}", ipv4), -//! _ => unreachable!(), -//! } -//! } else { # [derive (Copy , Clone , Default)] struct Span { start : usize , len : usize , } let zeroes = { let mut longest = Span :: default () ; let mut current = Span :: default () ; for (i , & segment) in segments . iter () . enumerate () { if segment == 0 { if current . len == 0 { current . start = i ; } current . len += 1 ; if current . len > longest . len { longest = current ; } } else { current = Span :: default () ; } } longest } ; # [doc = " Write a colon-separated part of the address"] # [inline] fn fmt_subslice (f : & mut fmt :: Formatter < '_ > , chunk : & [u16]) -> fmt :: Result { if let Some ((first , tail)) = chunk . split_first () { write ! (f , "{:x}" , first) ? ; for segment in tail { f . write_char (':') ? ; write ! (f , "{:x}" , segment) ? ; } } Ok (()) } if zeroes . len > 1 { fmt_subslice (f , & segments [.. zeroes . start]) ? ; f . write_str ("::") ? ; fmt_subslice (f , & segments [zeroes . start + zeroes . len ..]) } else { fmt_subslice (f , & segments) } } -//! } else { -//! const IPV6_BUF_LEN: usize = (4 * 8) + 7; -//! let mut buf = [0u8; IPV6_BUF_LEN]; -//! let mut buf_slice = &mut buf[..]; -//! # }}; -//! ``` -//! -//! This is a pretty typical manifestation of rustfmt bailing out in generated -//! code — a chunk of the input ends up on one line. The other -//! manifestation is that you're working on some code, running rustfmt on save -//! like a conscientious developer, but after a while notice it isn't doing -//! anything. You introduce an intentional formatting issue, like a stray indent -//! or semicolon, and run rustfmt to check your suspicion. Nope, it doesn't get -//! cleaned up — rustfmt is just not formatting the part of the file you -//! are working on. -//! -//! The prettyplease library is designed to have no pathological cases that -//! force a bail out; the entire input you give it will get formatted in some -//! "good enough" form. -//! -//! Separately, rustfmt can be problematic to integrate into projects. It's -//! written using rustc's internal syntax tree, so it can't be built by a stable -//! compiler. Its releases are not regularly published to crates.io, so in Cargo -//! builds you'd need to depend on it as a git dependency, which precludes -//! publishing your crate to crates.io also. You can shell out to a `rustfmt` -//! binary, but that'll be whatever rustfmt version is installed on each -//! developer's system (if any), which can lead to spurious diffs in checked-in -//! generated code formatted by different versions. In contrast prettyplease is -//! designed to be easy to pull in as a library, and compiles fast. -//! -//!
-//! -//! # Comparison to rustc_ast_pretty -//! -//! - [input.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/input.rs) -//! - [output.prettyplease.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.prettyplease.rs) -//! - [output.rustc.rs](https://github.com/dtolnay/prettyplease/blob/0.1.0/examples/output.rustc.rs) -//! -//! This is the pretty-printer that gets used when rustc prints source code, -//! such as `rustc -Zunpretty=expanded`. It's used also by the standard -//! library's `stringify!` when stringifying an interpolated macro_rules AST -//! fragment, like an $:expr, and transitively by `dbg!` and many macros in the -//! ecosystem. -//! -//! Rustc's formatting is mostly okay, but does not hew closely to the dominant -//! contemporary style of Rust formatting. Some things wouldn't ever be written -//! on one line, like this `match` expression, and certainly not with a comma in -//! front of the closing brace: -//! -//! ``` -//! # const _: &str = stringify! { -//! fn eq(&self, other: &IpAddr) -> bool { -//! match other { IpAddr::V4(v4) => self == v4, IpAddr::V6(_) => false, } -//! } -//! # }; -//! ``` -//! -//! Some places use non-multiple-of-4 indentation, which is definitely not the -//! norm: -//! -//! ``` -//! # const _: &str = stringify! { -//! pub const fn to_ipv6_mapped(&self) -> Ipv6Addr { -//! let [a, b, c, d] = self.octets(); -//! Ipv6Addr{inner: -//! c::in6_addr{s6_addr: -//! [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0xFF, -//! 0xFF, a, b, c, d],},} -//! } -//! # }; -//! ``` -//! -//! And although there isn't an egregious example of it in the link because the -//! input code is pretty tame, in general rustc_ast_pretty has pathological -//! behavior on generated code. It has a tendency to use excessive horizontal -//! indentation and rapidly run out of width: -//! -//! ``` -//! # const _: &str = stringify! { -//! ::std::io::_print(::core::fmt::Arguments::new_v1(&[""], -//! &match (&msg,) { -//! _args => -//! [::core::fmt::ArgumentV1::new(_args.0, -//! ::core::fmt::Display::fmt)], -//! })); -//! # }; -//! ``` -//! -//! The snippets above are clearly different from modern rustfmt style. In -//! contrast, prettyplease is designed to have output that is practically -//! indistinguishable from rustfmt-formatted code. -//! -//!
-//! -//! # Example -//! -//! ``` -//! // [dependencies] -//! // prettyplease = "0.1" -//! // syn = { version = "1", default-features = false, features = ["full", "parsing"] } -//! -//! const INPUT: &str = stringify! { -//! use super::{ -//! lazy::{Lazy, SyncLazy, SyncOnceCell}, panic, -//! sync::{ atomic::{AtomicUsize, Ordering::SeqCst}, -//! mpsc::channel, Mutex, }, -//! thread, -//! }; -//! impl Into for T where U: From { -//! fn into(self) -> U { U::from(self) } -//! } -//! }; -//! -//! fn main() { -//! let syntax_tree = syn::parse_file(INPUT).unwrap(); -//! let formatted = prettyplease::unparse(&syntax_tree); -//! print!("{}", formatted); -//! } -//! ``` -//! -//!
-//! -//! # Algorithm notes -//! -//! The approach and terminology used in the implementation are derived from -//! [*Derek C. Oppen, "Pretty Printing" (1979)*][paper], on which -//! rustc_ast_pretty is also based, and from rustc_ast_pretty's implementation -//! written by Graydon Hoare in 2011 (and modernized over the years by dozens of -//! volunteer maintainers). -//! -//! [paper]: http://i.stanford.edu/pub/cstr/reports/cs/tr/79/770/CS-TR-79-770.pdf -//! -//! The paper describes two language-agnostic interacting procedures `Scan()` -//! and `Print()`. Language-specific code decomposes an input data structure -//! into a stream of `string` and `break` tokens, and `begin` and `end` tokens -//! for grouping. Each `begin`–`end` range may be identified as either -//! "consistent breaking" or "inconsistent breaking". If a group is consistently -//! breaking, then if the whole contents do not fit on the line, *every* `break` -//! token in the group will receive a linebreak. This is appropriate, for -//! example, for Rust struct literals, or arguments of a function call. If a -//! group is inconsistently breaking, then the `string` tokens in the group are -//! greedily placed on the line until out of space, and linebroken only at those -//! `break` tokens for which the next string would not fit. For example, this is -//! appropriate for the contents of a braced `use` statement in Rust. -//! -//! Scan's job is to efficiently accumulate sizing information about groups and -//! breaks. For every `begin` token we compute the distance to the matched `end` -//! token, and for every `break` we compute the distance to the next `break`. -//! The algorithm uses a ringbuffer to hold tokens whose size is not yet -//! ascertained. The maximum size of the ringbuffer is bounded by the target -//! line length and does not grow indefinitely, regardless of deep nesting in -//! the input stream. That's because once a group is sufficiently big, the -//! precise size can no longer make a difference to linebreak decisions and we -//! can effectively treat it as "infinity". -//! -//! Print's job is to use the sizing information to efficiently assign a -//! "broken" or "not broken" status to every `begin` token. At that point the -//! output is easily constructed by concatenating `string` tokens and breaking -//! at `break` tokens contained within a broken group. -//! -//! Leveraging these primitives (i.e. cleverly placing the all-or-nothing -//! consistent breaks and greedy inconsistent breaks) to yield -//! rustfmt-compatible formatting for all of Rust's syntax tree nodes is a fun -//! challenge. -//! -//! Here is a visualization of some Rust tokens fed into the pretty printing -//! algorithm. Consistently breaking `begin`—`end` pairs are represented -//! by `«`⁠`»`, inconsistently breaking by `‹`⁠`›`, `break` by `·`, -//! and the rest of the non-whitespace are `string`. -//! -//! ```text -//! use super::«{· -//! ‹ lazy::«{·‹Lazy,· SyncLazy,· SyncOnceCell›·}»,· -//! panic,· -//! sync::«{· -//! ‹ atomic::«{·‹AtomicUsize,· Ordering::SeqCst›·}»,· -//! mpsc::channel,· Mutex›,· -//! }»,· -//! thread›,· -//! }»;· -//! «‹«impl<«·T‹›,· U‹›·»>» Into<«·U·»>· for T›· -//! where· -//! U:‹ From<«·T·»>›,· -//! {· -//! « fn into(·«·self·») -> U {· -//! ‹ U::from(«·self·»)›· -//! » }· -//! »}· -//! ``` -//! -//! The algorithm described in the paper is not quite sufficient for producing -//! well-formatted Rust code that is locally indistinguishable from rustfmt's -//! style. The reason is that in the paper, the complete non-whitespace contents -//! are assumed to be independent of linebreak decisions, with Scan and Print -//! being only in control of the whitespace (spaces and line breaks). In Rust as -//! idiomatically formattted by rustfmt, that is not the case. Trailing commas -//! are one example; the punctuation is only known *after* the broken vs -//! non-broken status of the surrounding group is known: -//! -//! ``` -//! # struct Struct { x: u64, y: bool } -//! # let xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx = 0; -//! # let yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy = true; -//! # -//! let _ = Struct { x: 0, y: true }; -//! -//! let _ = Struct { -//! x: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx, -//! y: yyyyyyyyyyyyyyyyyyyyyyyyyyyyyy, //<- trailing comma if the expression wrapped -//! }; -//! ``` -//! -//! The formatting of `match` expressions is another case; we want small arms on -//! the same line as the pattern, and big arms wrapped in a brace. The presence -//! of the brace punctuation, comma, and semicolon are all dependent on whether -//! the arm fits on the line: -//! -//! ``` -//! # struct Entry { nanos: u32 } -//! # let total_nanos = 0u64; -//! # let mut total_secs = 0u64; -//! # let tmp; -//! # let entry = Entry { nanos: 0 }; -//! # const NANOS_PER_SEC: u32 = 1_000_000_000; -//! # -//! match total_nanos.checked_add(entry.nanos as u64) { -//! Some(n) => tmp = n, //<- small arm, inline with comma -//! None => { -//! total_secs = total_secs -//! .checked_add(total_nanos / NANOS_PER_SEC as u64) -//! .expect("overflow in iter::sum over durations"); -//! } //<- big arm, needs brace added, and also semicolon^ -//! } -//! ``` -//! -//! The printing algorithm implementation in this crate accommodates all of -//! these situations with conditional punctuation tokens whose selection can be -//! deferred and populated after it's known that the group is or is not broken. - -#![allow( - clippy::cast_possible_wrap, - clippy::cast_sign_loss, - clippy::doc_markdown, - clippy::enum_glob_use, - clippy::items_after_statements, - clippy::match_like_matches_macro, - clippy::match_same_arms, - clippy::module_name_repetitions, - clippy::must_use_candidate, - clippy::needless_pass_by_value, - clippy::similar_names, - clippy::unused_self, - clippy::vec_init_then_push -)] -#![cfg_attr(all(test, exhaustive), feature(non_exhaustive_omitted_patterns_lint))] - -mod algorithm; -mod attr; -mod convenience; -mod data; -mod expr; -mod file; -mod generics; -mod item; -mod iter; -mod lifetime; -mod lit; -mod mac; -mod pat; -mod path; -mod ring; -mod stmt; -mod token; -mod ty; - -use algorithm::Printer; -use syn::{Expr, File}; - -// Target line width. -const MARGIN: isize = 89; - -// Number of spaces increment at each level of block indentation. -const INDENT: isize = 4; - -// Every line is allowed at least this much space, even if highly indented. -const MIN_SPACE: isize = 60; - -pub fn unparse(file: &File) -> String { - let mut p = Printer::new(); - p.file(file); - p.eof() -} - -pub fn unparse_expr(file: &Expr) -> String { - let mut p = Printer::new(); - p.expr(file); - p.eof() -} diff --git a/packages/autofmt/src/prettyplease/pat.rs b/packages/autofmt/src/prettyplease/pat.rs deleted file mode 100644 index 0ca86ef06..000000000 --- a/packages/autofmt/src/prettyplease/pat.rs +++ /dev/null @@ -1,210 +0,0 @@ -use super::algorithm::Printer; -use super::iter::IterDelimited; -use super::INDENT; -use proc_macro2::TokenStream; -use syn::{ - FieldPat, Pat, PatBox, PatIdent, PatLit, PatMacro, PatOr, PatPath, PatRange, PatReference, - PatRest, PatSlice, PatStruct, PatTuple, PatTupleStruct, PatType, PatWild, RangeLimits, -}; - -impl Printer { - pub fn pat(&mut self, pat: &Pat) { - match pat { - Pat::Box(pat) => self.pat_box(pat), - Pat::Ident(pat) => self.pat_ident(pat), - Pat::Lit(pat) => self.pat_lit(pat), - Pat::Macro(pat) => self.pat_macro(pat), - Pat::Or(pat) => self.pat_or(pat), - Pat::Path(pat) => self.pat_path(pat), - Pat::Range(pat) => self.pat_range(pat), - Pat::Reference(pat) => self.pat_reference(pat), - Pat::Rest(pat) => self.pat_rest(pat), - Pat::Slice(pat) => self.pat_slice(pat), - Pat::Struct(pat) => self.pat_struct(pat), - Pat::Tuple(pat) => self.pat_tuple(pat), - Pat::TupleStruct(pat) => self.pat_tuple_struct(pat), - Pat::Type(pat) => self.pat_type(pat), - Pat::Verbatim(pat) => self.pat_verbatim(pat), - Pat::Wild(pat) => self.pat_wild(pat), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown Pat"), - } - } - - fn pat_box(&mut self, pat: &PatBox) { - self.outer_attrs(&pat.attrs); - self.word("box "); - self.pat(&pat.pat); - } - - fn pat_ident(&mut self, pat: &PatIdent) { - self.outer_attrs(&pat.attrs); - if pat.by_ref.is_some() { - self.word("ref "); - } - if pat.mutability.is_some() { - self.word("mut "); - } - self.ident(&pat.ident); - if let Some((_at_token, subpat)) = &pat.subpat { - self.word(" @ "); - self.pat(subpat); - } - } - - fn pat_lit(&mut self, pat: &PatLit) { - self.outer_attrs(&pat.attrs); - self.expr(&pat.expr); - } - - fn pat_macro(&mut self, pat: &PatMacro) { - self.outer_attrs(&pat.attrs); - self.mac(&pat.mac, None); - } - - fn pat_or(&mut self, pat: &PatOr) { - self.outer_attrs(&pat.attrs); - let mut consistent_break = false; - for case in &pat.cases { - match case { - Pat::Lit(_) | Pat::Wild(_) => {} - _ => { - consistent_break = true; - break; - } - } - } - if consistent_break { - self.cbox(0); - } else { - self.ibox(0); - } - for case in pat.cases.iter().delimited() { - if !case.is_first { - self.space(); - self.word("| "); - } - self.pat(&case); - } - self.end(); - } - - fn pat_path(&mut self, pat: &PatPath) { - self.outer_attrs(&pat.attrs); - self.qpath(&pat.qself, &pat.path); - } - - fn pat_range(&mut self, pat: &PatRange) { - self.outer_attrs(&pat.attrs); - self.expr(&pat.lo); - match &pat.limits { - RangeLimits::HalfOpen(_) => self.word(".."), - RangeLimits::Closed(_) => self.word("..="), - } - self.expr(&pat.hi); - } - - fn pat_reference(&mut self, pat: &PatReference) { - self.outer_attrs(&pat.attrs); - self.word("&"); - if pat.mutability.is_some() { - self.word("mut "); - } - self.pat(&pat.pat); - } - - fn pat_rest(&mut self, pat: &PatRest) { - self.outer_attrs(&pat.attrs); - self.word(".."); - } - - fn pat_slice(&mut self, pat: &PatSlice) { - self.outer_attrs(&pat.attrs); - self.word("["); - for elem in pat.elems.iter().delimited() { - self.pat(&elem); - self.trailing_comma(elem.is_last); - } - self.word("]"); - } - - fn pat_struct(&mut self, pat: &PatStruct) { - self.outer_attrs(&pat.attrs); - self.cbox(INDENT); - self.path(&pat.path); - self.word(" {"); - self.space_if_nonempty(); - for field in pat.fields.iter().delimited() { - self.field_pat(&field); - self.trailing_comma_or_space(field.is_last && pat.dot2_token.is_none()); - } - if pat.dot2_token.is_some() { - self.word(".."); - self.space(); - } - self.offset(-INDENT); - self.end(); - self.word("}"); - } - - fn pat_tuple(&mut self, pat: &PatTuple) { - self.outer_attrs(&pat.attrs); - self.word("("); - self.cbox(INDENT); - self.zerobreak(); - for elem in pat.elems.iter().delimited() { - self.pat(&elem); - if pat.elems.len() == 1 { - if pat.elems.trailing_punct() { - self.word(","); - } - self.zerobreak(); - } else { - self.trailing_comma(elem.is_last); - } - } - self.offset(-INDENT); - self.end(); - self.word(")"); - } - - fn pat_tuple_struct(&mut self, pat: &PatTupleStruct) { - self.outer_attrs(&pat.attrs); - self.path(&pat.path); - self.word("("); - self.cbox(INDENT); - self.zerobreak(); - for elem in pat.pat.elems.iter().delimited() { - self.pat(&elem); - self.trailing_comma(elem.is_last); - } - self.offset(-INDENT); - self.end(); - self.word(")"); - } - - pub fn pat_type(&mut self, pat: &PatType) { - self.outer_attrs(&pat.attrs); - self.pat(&pat.pat); - self.word(": "); - self.ty(&pat.ty); - } - - fn pat_verbatim(&mut self, pat: &TokenStream) { - unimplemented!("Pat::Verbatim `{}`", pat); - } - - fn pat_wild(&mut self, pat: &PatWild) { - self.outer_attrs(&pat.attrs); - self.word("_"); - } - - fn field_pat(&mut self, field_pat: &FieldPat) { - self.outer_attrs(&field_pat.attrs); - if field_pat.colon_token.is_some() { - self.member(&field_pat.member); - self.word(": "); - } - self.pat(&field_pat.pat); - } -} diff --git a/packages/autofmt/src/prettyplease/path.rs b/packages/autofmt/src/prettyplease/path.rs deleted file mode 100644 index 460fe5cb7..000000000 --- a/packages/autofmt/src/prettyplease/path.rs +++ /dev/null @@ -1,181 +0,0 @@ -use super::algorithm::Printer; -use super::iter::IterDelimited; -use super::INDENT; -use syn::{ - AngleBracketedGenericArguments, Binding, Constraint, Expr, GenericArgument, - ParenthesizedGenericArguments, Path, PathArguments, PathSegment, QSelf, -}; - -impl Printer { - pub fn path(&mut self, path: &Path) { - assert!(!path.segments.is_empty()); - for segment in path.segments.iter().delimited() { - if !segment.is_first || path.leading_colon.is_some() { - self.word("::"); - } - self.path_segment(&segment); - } - } - - pub fn path_segment(&mut self, segment: &PathSegment) { - self.ident(&segment.ident); - self.path_arguments(&segment.arguments); - } - - fn path_arguments(&mut self, arguments: &PathArguments) { - match arguments { - PathArguments::None => {} - PathArguments::AngleBracketed(arguments) => { - self.angle_bracketed_generic_arguments(arguments); - } - PathArguments::Parenthesized(arguments) => { - self.parenthesized_generic_arguments(arguments); - } - } - } - - fn generic_argument(&mut self, arg: &GenericArgument) { - match arg { - GenericArgument::Lifetime(lifetime) => self.lifetime(lifetime), - GenericArgument::Type(ty) => self.ty(ty), - GenericArgument::Binding(binding) => self.binding(binding), - GenericArgument::Constraint(constraint) => self.constraint(constraint), - GenericArgument::Const(expr) => { - match expr { - Expr::Lit(expr) => self.expr_lit(expr), - Expr::Block(expr) => self.expr_block(expr), - // ERROR CORRECTION: Add braces to make sure that the - // generated code is valid. - _ => { - self.word("{"); - self.expr(expr); - self.word("}"); - } - } - } - } - } - - fn angle_bracketed_generic_arguments(&mut self, generic: &AngleBracketedGenericArguments) { - if generic.colon2_token.is_some() { - self.word("::"); - } - self.word("<"); - self.cbox(INDENT); - self.zerobreak(); - - // Print lifetimes before types and consts, all before bindings, - // regardless of their order in self.args. - // - // TODO: ordering rules for const arguments vs type arguments have - // not been settled yet. https://github.com/rust-lang/rust/issues/44580 - for arg in generic.args.iter().delimited() { - match *arg { - GenericArgument::Lifetime(_) => { - self.generic_argument(&arg); - self.trailing_comma(arg.is_last); - } - GenericArgument::Type(_) - | GenericArgument::Binding(_) - | GenericArgument::Constraint(_) - | GenericArgument::Const(_) => {} - } - } - for arg in generic.args.iter().delimited() { - match *arg { - GenericArgument::Type(_) | GenericArgument::Const(_) => { - self.generic_argument(&arg); - self.trailing_comma(arg.is_last); - } - GenericArgument::Lifetime(_) - | GenericArgument::Binding(_) - | GenericArgument::Constraint(_) => {} - } - } - for arg in generic.args.iter().delimited() { - match *arg { - GenericArgument::Binding(_) | GenericArgument::Constraint(_) => { - self.generic_argument(&arg); - self.trailing_comma(arg.is_last); - } - GenericArgument::Lifetime(_) - | GenericArgument::Type(_) - | GenericArgument::Const(_) => {} - } - } - - self.offset(-INDENT); - self.end(); - self.word(">"); - } - - fn binding(&mut self, binding: &Binding) { - self.ident(&binding.ident); - self.word(" = "); - self.ty(&binding.ty); - } - - fn constraint(&mut self, constraint: &Constraint) { - self.ident(&constraint.ident); - self.ibox(INDENT); - for bound in constraint.bounds.iter().delimited() { - if bound.is_first { - self.word(": "); - } else { - self.space(); - self.word("+ "); - } - self.type_param_bound(&bound); - } - self.end(); - } - - fn parenthesized_generic_arguments(&mut self, arguments: &ParenthesizedGenericArguments) { - self.cbox(INDENT); - self.word("("); - self.zerobreak(); - for ty in arguments.inputs.iter().delimited() { - self.ty(&ty); - self.trailing_comma(ty.is_last); - } - self.offset(-INDENT); - self.word(")"); - self.return_type(&arguments.output); - self.end(); - } - - pub fn qpath(&mut self, qself: &Option, path: &Path) { - let qself = match qself { - Some(qself) => qself, - None => { - self.path(path); - return; - } - }; - - assert!(qself.position < path.segments.len()); - - self.word("<"); - self.ty(&qself.ty); - - let mut segments = path.segments.iter(); - if qself.position > 0 { - self.word(" as "); - for segment in segments.by_ref().take(qself.position).delimited() { - if !segment.is_first || path.leading_colon.is_some() { - self.word("::"); - } - self.path_segment(&segment); - if segment.is_last { - self.word(">"); - } - } - } else { - self.word(">"); - } - for segment in segments { - self.word("::"); - self.path_segment(segment); - } - } -} diff --git a/packages/autofmt/src/prettyplease/ring.rs b/packages/autofmt/src/prettyplease/ring.rs deleted file mode 100644 index aff92589a..000000000 --- a/packages/autofmt/src/prettyplease/ring.rs +++ /dev/null @@ -1,81 +0,0 @@ -use std::collections::VecDeque; -use std::ops::{Index, IndexMut}; - -pub struct RingBuffer { - data: VecDeque, - // Abstract index of data[0] in infinitely sized queue - offset: usize, -} - -impl RingBuffer { - pub fn new() -> Self { - RingBuffer { - data: VecDeque::new(), - offset: 0, - } - } - - pub fn is_empty(&self) -> bool { - self.data.is_empty() - } - - pub fn len(&self) -> usize { - self.data.len() - } - - pub fn push(&mut self, value: T) -> usize { - let index = self.offset + self.data.len(); - self.data.push_back(value); - index - } - - pub fn clear(&mut self) { - self.data.clear(); - } - - pub fn index_of_first(&self) -> usize { - self.offset - } - - pub fn first(&self) -> &T { - &self.data[0] - } - - pub fn first_mut(&mut self) -> &mut T { - &mut self.data[0] - } - - pub fn pop_first(&mut self) -> T { - self.offset += 1; - self.data.pop_front().unwrap() - } - - pub fn last(&self) -> &T { - self.data.back().unwrap() - } - - pub fn last_mut(&mut self) -> &mut T { - self.data.back_mut().unwrap() - } - - pub fn second_last(&self) -> &T { - &self.data[self.data.len() - 2] - } - - pub fn pop_last(&mut self) { - self.data.pop_back().unwrap(); - } -} - -impl Index for RingBuffer { - type Output = T; - fn index(&self, index: usize) -> &Self::Output { - &self.data[index.checked_sub(self.offset).unwrap()] - } -} - -impl IndexMut for RingBuffer { - fn index_mut(&mut self, index: usize) -> &mut Self::Output { - &mut self.data[index.checked_sub(self.offset).unwrap()] - } -} diff --git a/packages/autofmt/src/prettyplease/stmt.rs b/packages/autofmt/src/prettyplease/stmt.rs deleted file mode 100644 index 68295fa70..000000000 --- a/packages/autofmt/src/prettyplease/stmt.rs +++ /dev/null @@ -1,85 +0,0 @@ -use super::algorithm::Printer; -use syn::{Expr, Stmt}; - -impl Printer { - pub fn stmt(&mut self, stmt: &Stmt) { - match stmt { - Stmt::Local(local) => { - self.outer_attrs(&local.attrs); - self.ibox(0); - self.word("let "); - self.pat(&local.pat); - if let Some((_eq, init)) = &local.init { - self.word(" = "); - self.neverbreak(); - self.expr(init); - } - self.word(";"); - self.end(); - self.hardbreak(); - } - Stmt::Item(item) => self.item(item), - Stmt::Expr(expr) => { - if break_after(expr) { - self.ibox(0); - self.expr_beginning_of_line(expr, true); - if add_semi(expr) { - self.word(";"); - } - self.end(); - self.hardbreak(); - } else { - self.expr_beginning_of_line(expr, true); - } - } - Stmt::Semi(expr, _semi) => { - if let Expr::Verbatim(tokens) = expr { - if tokens.is_empty() { - return; - } - } - self.ibox(0); - self.expr_beginning_of_line(expr, true); - if !remove_semi(expr) { - self.word(";"); - } - self.end(); - self.hardbreak(); - } - } - } -} - -pub fn add_semi(expr: &Expr) -> bool { - match expr { - Expr::Assign(_) - | Expr::AssignOp(_) - | Expr::Break(_) - | Expr::Continue(_) - | Expr::Return(_) - | Expr::Yield(_) => true, - Expr::Group(group) => add_semi(&group.expr), - _ => false, - } -} - -pub fn break_after(expr: &Expr) -> bool { - if let Expr::Group(group) = expr { - if let Expr::Verbatim(verbatim) = group.expr.as_ref() { - return !verbatim.is_empty(); - } - } - true -} - -fn remove_semi(expr: &Expr) -> bool { - match expr { - Expr::ForLoop(_) | Expr::While(_) => true, - Expr::Group(group) => remove_semi(&group.expr), - Expr::If(expr) => match &expr.else_branch { - Some((_else_token, else_branch)) => remove_semi(else_branch), - None => true, - }, - _ => false, - } -} diff --git a/packages/autofmt/src/prettyplease/token.rs b/packages/autofmt/src/prettyplease/token.rs deleted file mode 100644 index f25f0d3c8..000000000 --- a/packages/autofmt/src/prettyplease/token.rs +++ /dev/null @@ -1,80 +0,0 @@ -use super::algorithm::Printer; -use proc_macro2::{Delimiter, Ident, Literal, Spacing, TokenStream, TokenTree}; - -impl Printer { - pub fn single_token(&mut self, token: Token, group_contents: fn(&mut Self, TokenStream)) { - match token { - Token::Group(delimiter, stream) => self.token_group(delimiter, stream, group_contents), - Token::Ident(ident) => self.ident(&ident), - Token::Punct(ch, _spacing) => self.token_punct(ch), - Token::Literal(literal) => self.token_literal(&literal), - } - } - - fn token_group( - &mut self, - delimiter: Delimiter, - stream: TokenStream, - group_contents: fn(&mut Self, TokenStream), - ) { - self.delimiter_open(delimiter); - if !stream.is_empty() { - if delimiter == Delimiter::Brace { - self.space(); - } - group_contents(self, stream); - if delimiter == Delimiter::Brace { - self.space(); - } - } - self.delimiter_close(delimiter); - } - - pub fn ident(&mut self, ident: &Ident) { - self.word(ident.to_string()); - } - - pub fn token_punct(&mut self, ch: char) { - self.word(ch.to_string()); - } - - pub fn token_literal(&mut self, literal: &Literal) { - self.word(literal.to_string()); - } - - pub fn delimiter_open(&mut self, delimiter: Delimiter) { - self.word(match delimiter { - Delimiter::Parenthesis => "(", - Delimiter::Brace => "{", - Delimiter::Bracket => "[", - Delimiter::None => return, - }); - } - - pub fn delimiter_close(&mut self, delimiter: Delimiter) { - self.word(match delimiter { - Delimiter::Parenthesis => ")", - Delimiter::Brace => "}", - Delimiter::Bracket => "]", - Delimiter::None => return, - }); - } -} - -pub enum Token { - Group(Delimiter, TokenStream), - Ident(Ident), - Punct(char, Spacing), - Literal(Literal), -} - -impl From for Token { - fn from(tt: TokenTree) -> Self { - match tt { - TokenTree::Group(group) => Token::Group(group.delimiter(), group.stream()), - TokenTree::Ident(ident) => Token::Ident(ident), - TokenTree::Punct(punct) => Token::Punct(punct.as_char(), punct.spacing()), - TokenTree::Literal(literal) => Token::Literal(literal), - } - } -} diff --git a/packages/autofmt/src/prettyplease/ty.rs b/packages/autofmt/src/prettyplease/ty.rs deleted file mode 100644 index 93cb48c77..000000000 --- a/packages/autofmt/src/prettyplease/ty.rs +++ /dev/null @@ -1,202 +0,0 @@ -use super::algorithm::Printer; -use super::iter::IterDelimited; -use super::INDENT; -use proc_macro2::TokenStream; -use syn::{ - Abi, BareFnArg, ReturnType, Type, TypeArray, TypeBareFn, TypeGroup, TypeImplTrait, TypeInfer, - TypeMacro, TypeNever, TypeParen, TypePath, TypePtr, TypeReference, TypeSlice, TypeTraitObject, - TypeTuple, Variadic, -}; - -impl Printer { - pub fn ty(&mut self, ty: &Type) { - match ty { - Type::Array(ty) => self.type_array(ty), - Type::BareFn(ty) => self.type_bare_fn(ty), - Type::Group(ty) => self.type_group(ty), - Type::ImplTrait(ty) => self.type_impl_trait(ty), - Type::Infer(ty) => self.type_infer(ty), - Type::Macro(ty) => self.type_macro(ty), - Type::Never(ty) => self.type_never(ty), - Type::Paren(ty) => self.type_paren(ty), - Type::Path(ty) => self.type_path(ty), - Type::Ptr(ty) => self.type_ptr(ty), - Type::Reference(ty) => self.type_reference(ty), - Type::Slice(ty) => self.type_slice(ty), - Type::TraitObject(ty) => self.type_trait_object(ty), - Type::Tuple(ty) => self.type_tuple(ty), - Type::Verbatim(ty) => self.type_verbatim(ty), - #[cfg_attr(all(test, exhaustive), deny(non_exhaustive_omitted_patterns))] - _ => unimplemented!("unknown Type"), - } - } - - fn type_array(&mut self, ty: &TypeArray) { - self.word("["); - self.ty(&ty.elem); - self.word("; "); - self.expr(&ty.len); - self.word("]"); - } - - fn type_bare_fn(&mut self, ty: &TypeBareFn) { - if let Some(bound_lifetimes) = &ty.lifetimes { - self.bound_lifetimes(bound_lifetimes); - } - if ty.unsafety.is_some() { - self.word("unsafe "); - } - if let Some(abi) = &ty.abi { - self.abi(abi); - } - self.word("fn("); - self.cbox(INDENT); - self.zerobreak(); - for bare_fn_arg in ty.inputs.iter().delimited() { - self.bare_fn_arg(&bare_fn_arg); - self.trailing_comma(bare_fn_arg.is_last && ty.variadic.is_none()); - } - if let Some(variadic) = &ty.variadic { - self.variadic(variadic); - self.zerobreak(); - } - self.offset(-INDENT); - self.end(); - self.word(")"); - self.return_type(&ty.output); - } - - fn type_group(&mut self, ty: &TypeGroup) { - self.ty(&ty.elem); - } - - fn type_impl_trait(&mut self, ty: &TypeImplTrait) { - self.word("impl "); - for type_param_bound in ty.bounds.iter().delimited() { - if !type_param_bound.is_first { - self.word(" + "); - } - self.type_param_bound(&type_param_bound); - } - } - - fn type_infer(&mut self, ty: &TypeInfer) { - let _ = ty; - self.word("_"); - } - - fn type_macro(&mut self, ty: &TypeMacro) { - self.mac(&ty.mac, None); - } - - fn type_never(&mut self, ty: &TypeNever) { - let _ = ty; - self.word("!"); - } - - fn type_paren(&mut self, ty: &TypeParen) { - self.word("("); - self.ty(&ty.elem); - self.word(")"); - } - - fn type_path(&mut self, ty: &TypePath) { - self.qpath(&ty.qself, &ty.path); - } - - fn type_ptr(&mut self, ty: &TypePtr) { - self.word("*"); - if ty.mutability.is_some() { - self.word("mut "); - } else { - self.word("const "); - } - self.ty(&ty.elem); - } - - fn type_reference(&mut self, ty: &TypeReference) { - self.word("&"); - if let Some(lifetime) = &ty.lifetime { - self.lifetime(lifetime); - self.nbsp(); - } - if ty.mutability.is_some() { - self.word("mut "); - } - self.ty(&ty.elem); - } - - fn type_slice(&mut self, ty: &TypeSlice) { - self.word("["); - self.ty(&ty.elem); - self.word("]"); - } - - fn type_trait_object(&mut self, ty: &TypeTraitObject) { - self.word("dyn "); - for type_param_bound in ty.bounds.iter().delimited() { - if !type_param_bound.is_first { - self.word(" + "); - } - self.type_param_bound(&type_param_bound); - } - } - - fn type_tuple(&mut self, ty: &TypeTuple) { - self.word("("); - self.cbox(INDENT); - self.zerobreak(); - for elem in ty.elems.iter().delimited() { - self.ty(&elem); - if ty.elems.len() == 1 { - self.word(","); - self.zerobreak(); - } else { - self.trailing_comma(elem.is_last); - } - } - self.offset(-INDENT); - self.end(); - self.word(")"); - } - - fn type_verbatim(&mut self, ty: &TokenStream) { - if ty.to_string() == "..." { - self.word("..."); - } else { - unimplemented!("Type::Verbatim `{}`", ty); - } - } - - pub fn return_type(&mut self, ty: &ReturnType) { - match ty { - ReturnType::Default => {} - ReturnType::Type(_arrow, ty) => { - self.word(" -> "); - self.ty(ty); - } - } - } - - fn bare_fn_arg(&mut self, bare_fn_arg: &BareFnArg) { - self.outer_attrs(&bare_fn_arg.attrs); - if let Some((name, _colon)) = &bare_fn_arg.name { - self.ident(name); - self.word(": "); - } - self.ty(&bare_fn_arg.ty); - } - - fn variadic(&mut self, variadic: &Variadic) { - self.outer_attrs(&variadic.attrs); - self.word("..."); - } - - pub fn abi(&mut self, abi: &Abi) { - self.word("extern "); - if let Some(name) = &abi.name { - self.lit_str(name); - self.nbsp(); - } - } -} diff --git a/packages/autofmt/src/util.rs b/packages/autofmt/src/util.rs new file mode 100644 index 000000000..2cf362db7 --- /dev/null +++ b/packages/autofmt/src/util.rs @@ -0,0 +1,43 @@ +use dioxus_rsx::*; +use std::fmt::Write; + +pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize { + attributes + .iter() + .map(|attr| match &attr.attr { + ElementAttr::AttrText { name, value } => value.value().len(), + ElementAttr::AttrExpression { name, value } => 10, + ElementAttr::CustomAttrText { name, value } => value.value().len(), + ElementAttr::CustomAttrExpression { name, value } => 10, + ElementAttr::EventTokens { name, tokens } => 1000000, + }) + .sum::() +} + +pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result { + for _ in 0..num { + write!(f, " ")? + } + Ok(()) +} + +pub fn find_bracket_end(contents: &str) -> Option { + let mut depth = 0; + let mut i = 0; + + for c in contents.chars() { + if c == '{' { + depth += 1; + } else if c == '}' { + depth -= 1; + } + + if depth == 0 { + return Some(i); + } + + i += 1; + } + + None +} diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index 736abc7dc..3f4df0a4e 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -36,6 +36,10 @@ fn formats_block() { class: "asd", } } + + Component {} + + Component {} } } "#; diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index 56d82876f..0cdae3bcb 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -66,6 +66,7 @@ pub fn rsx(s: TokenStream) -> TokenStream { Err(err) => err.into_compile_error().into(), } } + #[cfg(not(feature = "hot_reload"))] body.to_token_stream().into() } From 79b475798d68ab6e51600f00997dc28635ba2cac Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 25 Jun 2022 02:22:26 -0400 Subject: [PATCH 03/13] wip: make element printer more comprehensive --- packages/autofmt/src/block.rs | 41 +++++ packages/autofmt/src/element.rs | 300 ++++++++++++++++++++------------ packages/autofmt/src/util.rs | 2 +- 3 files changed, 228 insertions(+), 115 deletions(-) diff --git a/packages/autofmt/src/block.rs b/packages/autofmt/src/block.rs index d3ef135d9..7930f1e15 100644 --- a/packages/autofmt/src/block.rs +++ b/packages/autofmt/src/block.rs @@ -84,10 +84,51 @@ fn format_block_basic() { h1 { "thing" } h1 { "thing" } h1 { "thing" } + h1 { "thing" "is whack" "but you're wacker" } + h1 { "thing" div {"special cases?" } } + div { + a: 123, + b: 456, + c: 789, + d: "hello", + } + div { + a: 123, + b: 456, + c: 789, + d: "hello", + p {} + c {} + } + + h3 { class: "asdasd", "asdasd" } + h3 { class: "mx-large bg-gray-900 tall-md-400", div { + "classy" + }} + + + // Some comments explaining my genius + div { + "comment compression" + } + + // Some comments explaining my genius + div { + // Some comments explaining my genius + a: 123, + + // comment compression + b: 456, + + // comments on attributes + c: 789, + } } } "#; + // div { class: "asdasd", p { "hello!" } } + let edits = get_format_blocks(block); println!("{}", edits[0].formatted); diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index bde1c33f8..4af1d2460 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -2,6 +2,14 @@ use crate::{util::*, write_ident}; use dioxus_rsx::*; use std::{fmt, fmt::Write}; +enum ShortOptimization { + // Special because we want to print the closing bracket immediately + Empty, + Oneliner, + PropsOnTop, + NoOpt, +} + pub fn write_element( Element { name, @@ -16,145 +24,209 @@ pub fn write_element( indent: usize, ) -> fmt::Result { /* - Write the tag - Write the key - Write the attributes - Write the children + 1. Write the tag + 2. Write the key + 3. Write the attributes + 4. Write the children */ write!(buf, "{name} {{")?; - let total_attr_len = extract_attr_len(attributes); - let is_long_attr_list = total_attr_len > 80; + // decide if we have any special optimizations + // Default with none, opt the cases in one-by-one + let mut opt_level = ShortOptimization::NoOpt; - if let Some(key) = key.as_ref().map(|f| f.value()) { - if is_long_attr_list { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - } else { + // check if we have a lot of attributes + let is_short_attr_list = is_short_attrs(attributes); + let is_small_children = is_short_children(children); + + // if we have few attributes and a lot of children, place the attrs on top + if is_short_attr_list && !is_small_children { + opt_level = ShortOptimization::PropsOnTop; + } + + // if we have few children and few attributes, make it a one-liner + if is_short_attr_list && is_small_children { + opt_level = ShortOptimization::Oneliner; + } + + // If there's nothing at all, empty optimization + if attributes.is_empty() && children.is_empty() && key.is_none() { + opt_level = ShortOptimization::Empty; + } + + match opt_level { + ShortOptimization::Empty => write!(buf, "}}")?, + ShortOptimization::Oneliner => { write!(buf, " ")?; - } - write!(buf, "key: \"{key}\"")?; + write_attributes(buf, attributes, true, indent)?; - if !attributes.is_empty() { - write!(buf, ",")?; + if !children.is_empty() && !attributes.is_empty() { + write!(buf, ", ")?; + } + + // write the children + for child in children { + write_ident(buf, lines, child, indent + 1)?; + } + + write!(buf, " }}")?; + } + + ShortOptimization::PropsOnTop => { + write!(buf, " ")?; + write_attributes(buf, attributes, true, indent)?; + + if !children.is_empty() && !attributes.is_empty() { + write!(buf, ",")?; + } + + // write the children + for child in children { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write_ident(buf, lines, child, indent + 1)?; + } + + writeln!(buf)?; + write_tabs(buf, indent)?; + write!(buf, "}}")?; + } + + ShortOptimization::NoOpt => { + // write the key + + // write the attributes + write_attributes(buf, attributes, false, indent)?; + + // write the children + for child in children { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + write_ident(buf, lines, child, indent + 1)?; + } + + writeln!(buf)?; + write_tabs(buf, indent)?; + write!(buf, "}}")?; } } + Ok(()) +} + +fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { + let total_attr_len = extract_attr_len(attrs); + total_attr_len < 80 +} + +// check if the children are short enough to be on the same line +// We don't have the notion of current line depth - each line tries to be < 80 total +fn is_short_children(children: &[BodyNode]) -> bool { + if children.is_empty() { + return true; + } + + if children.len() == 1 { + if let BodyNode::Text(ref text) = &children[0] { + return text.value().len() < 80; + } + } + + false +} + +fn write_key() { + // if let Some(key) = key.as_ref().map(|f| f.value()) { + // if is_long_attr_list { + // writeln!(buf)?; + // write_tabs(buf, indent + 1)?; + // } else { + // write!(buf, " ")?; + // } + // write!(buf, "key: \"{key}\"")?; + + // if !attributes.is_empty() { + // write!(buf, ",")?; + // } + // } +} + +fn write_attributes( + buf: &mut String, + attributes: &[ElementAttrNamed], + sameline: bool, + indent: usize, +) -> fmt::Result { let mut attr_iter = attributes.iter().peekable(); while let Some(attr) = attr_iter.next() { - if is_long_attr_list { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - } else { - write!(buf, " ")?; + write_attribute(buf, attr, indent)?; + + if attr_iter.peek().is_some() { + write!(buf, ",")?; + + if sameline { + write!(buf, " ")?; + } else { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + } + } + } + + Ok(()) +} + +fn write_attribute(buf: &mut String, attr: &ElementAttrNamed, indent: usize) -> fmt::Result { + match &attr.attr { + ElementAttr::AttrText { name, value } => { + write!(buf, "{name}: \"{value}\"", value = value.value())?; + } + ElementAttr::AttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "{}: {}", name, out)?; } - match &attr.attr { - ElementAttr::AttrText { name, value } => { - write!(buf, "{name}: \"{value}\"", value = value.value())?; - } - ElementAttr::AttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "{}: {}", name, out)?; - } + ElementAttr::CustomAttrText { name, value } => { + write!( + buf, + "\"{name}\": \"{value}\"", + name = name.value(), + value = value.value() + )?; + } - ElementAttr::CustomAttrText { name, value } => { - write!( - buf, - "\"{name}\": \"{value}\"", - name = name.value(), - value = value.value() - )?; - } + ElementAttr::CustomAttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "\"{}\": {}", name.value(), out)?; + } - ElementAttr::CustomAttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "\"{}\": {}", name.value(), out)?; - } + ElementAttr::EventTokens { name, tokens } => { + let out = prettyplease::unparse_expr(tokens); - ElementAttr::EventTokens { name, tokens } => { - let out = prettyplease::unparse_expr(tokens); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(buf, "{}: {}", name, first)?; + } else { + writeln!(buf, "{}: {}", name, first)?; - // a one-liner for whatever reason - // Does not need a new line - if lines.peek().is_none() { - write!(buf, "{}: {}", name, first)?; - } else { - writeln!(buf, "{}: {}", name, first)?; - - while let Some(line) = lines.next() { - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; - if lines.peek().is_none() { - write!(buf, "")?; - } else { - writeln!(buf)?; - } + while let Some(line) = lines.next() { + write_tabs(buf, indent + 1)?; + write!(buf, "{}", line)?; + if lines.peek().is_none() { + write!(buf, "")?; + } else { + writeln!(buf)?; } } } } - - if attr_iter.peek().is_some() || !children.is_empty() { - write!(buf, ",")?; - } } - // If the attr list is short, then we want to optimize for some cases - let is_child_optimized = match children.as_slice() { - // No children, just close the tag - [] => true, - - // Only a text node, just write it out - [BodyNode::Text(_)] => true, - - // If these have zero children and attributes, then we can just write out the tag - [BodyNode::Component(ref comp)] => comp.body.is_empty() && comp.children.is_empty(), - [BodyNode::Element(ref el)] => el.attributes.is_empty() && el.children.is_empty(), - - // Nothing else is optimized - _ => false, - }; - - if !is_long_attr_list && is_child_optimized { - write_ident(buf, lines, &children[0], indent)?; - } else { - for child in children { - writeln!(buf)?; - write_ident(buf, lines, child, indent + 1)?; - } - } - - // let text_val = text.value(); - // if total_attr_len + text_val.len() > 80 { - // writeln!(buf)?; - // write_tabs(buf, indent + 1)?; - // writeln!(buf, "\"{}\"", text.value())?; - // write_tabs(buf, indent)?; - // } else { - // write!(buf, " \"{}\" ", text.value())?; - // } - - // if is_long_attr_list { - // writeln!(buf)?; - // } - - // for child in children { - // write_ident(buf, lines, child, indent + 1)?; - // } - - // if is_long_attr_list { - // write_tabs(buf, indent)?; - // } else { - // write!(buf, " ")?; - // } - - writeln!(buf, "}}")?; - Ok(()) } diff --git a/packages/autofmt/src/util.rs b/packages/autofmt/src/util.rs index 2cf362db7..9a358e959 100644 --- a/packages/autofmt/src/util.rs +++ b/packages/autofmt/src/util.rs @@ -11,7 +11,7 @@ pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize { ElementAttr::CustomAttrExpression { name, value } => 10, ElementAttr::EventTokens { name, tokens } => 1000000, }) - .sum::() + .sum() } pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result { From a2a194ca40739e7304d8ba07620466df45c5789a Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 27 Jun 2022 13:36:41 -0400 Subject: [PATCH 04/13] wip: use stateful buffer --- packages/autofmt/src/buffer.rs | 17 +++++++++++++++++ packages/autofmt/src/element.rs | 15 ++++++++++----- packages/autofmt/src/ident.rs | 2 +- packages/autofmt/src/lib.rs | 1 + 4 files changed, 29 insertions(+), 6 deletions(-) create mode 100644 packages/autofmt/src/buffer.rs diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs new file mode 100644 index 000000000..dfd7166ba --- /dev/null +++ b/packages/autofmt/src/buffer.rs @@ -0,0 +1,17 @@ +use std::fmt::Result; + +struct Buffer { + buf: String, + cur_line: usize, + cur_indent: usize, +} + +impl Buffer { + fn write_tabs(&mut self, num: usize) -> Result { + todo!() + } + + fn new_line(&mut self) -> Result { + todo!() + } +} diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 4af1d2460..4c4a00785 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -1,12 +1,18 @@ use crate::{util::*, write_ident}; use dioxus_rsx::*; -use std::{fmt, fmt::Write}; +use std::{fmt, fmt::Result, fmt::Write}; enum ShortOptimization { // Special because we want to print the closing bracket immediately Empty, + + // Special optimization to put everything on the same line Oneliner, + + // Optimization where children flow but props remain fixed on top PropsOnTop, + + // The noisiest optimization where everything flows NoOpt, } @@ -20,9 +26,8 @@ pub fn write_element( }: &Element, buf: &mut String, lines: &[&str], - node: &BodyNode, indent: usize, -) -> fmt::Result { +) -> Result { /* 1. Write the tag 2. Write the key @@ -157,7 +162,7 @@ fn write_attributes( attributes: &[ElementAttrNamed], sameline: bool, indent: usize, -) -> fmt::Result { +) -> Result { let mut attr_iter = attributes.iter().peekable(); while let Some(attr) = attr_iter.next() { @@ -178,7 +183,7 @@ fn write_attributes( Ok(()) } -fn write_attribute(buf: &mut String, attr: &ElementAttrNamed, indent: usize) -> fmt::Result { +fn write_attribute(buf: &mut String, attr: &ElementAttrNamed, indent: usize) -> Result { match &attr.attr { ElementAttr::AttrText { name, value } => { write!(buf, "{name}: \"{value}\"", value = value.value())?; diff --git a/packages/autofmt/src/ident.rs b/packages/autofmt/src/ident.rs index 19fd3cb8d..e4dee56c9 100644 --- a/packages/autofmt/src/ident.rs +++ b/packages/autofmt/src/ident.rs @@ -11,7 +11,7 @@ pub fn write_ident( indent: usize, ) -> fmt::Result { match node { - BodyNode::Element(el) => write_element(el, buf, lines, node, indent), + BodyNode::Element(el) => write_element(el, buf, lines, indent), BodyNode::Component(component) => write_component(component, buf, indent, lines), BodyNode::Text(text) => write_text(text, buf, indent), BodyNode::RawExpr(exp) => write_raw_expr(exp, indent, lines, buf), diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index c6fd2eeec..6d6c88819 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -5,6 +5,7 @@ use proc_macro2::TokenStream as TokenStream2; use quote::ToTokens; mod block; +mod buffer; mod children; mod component; mod element; From d70b4361571e957b20f2155bf7db224fc6670a38 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 27 Jun 2022 16:24:36 -0400 Subject: [PATCH 05/13] wip: more cleanup, more tests --- packages/autofmt/src/block.rs | 77 +------ packages/autofmt/src/buffer.rs | 84 ++++++- packages/autofmt/src/component.rs | 145 ++++++------ packages/autofmt/src/element.rs | 352 +++++++++++++++--------------- packages/autofmt/src/expr.rs | 74 ++++--- packages/autofmt/src/ident.rs | 23 -- packages/autofmt/src/lib.rs | 78 ++++++- packages/autofmt/src/util.rs | 7 - packages/autofmt/tests/sink.rs | 133 ++++++++--- packages/rsx/src/element.rs | 10 + 10 files changed, 560 insertions(+), 423 deletions(-) delete mode 100644 packages/autofmt/src/ident.rs diff --git a/packages/autofmt/src/block.rs b/packages/autofmt/src/block.rs index 7930f1e15..2dad1b780 100644 --- a/packages/autofmt/src/block.rs +++ b/packages/autofmt/src/block.rs @@ -1,79 +1,8 @@ -use crate::{util::*, write_ident}; +use crate::{util::*, FormattedBlock}; use dioxus_rsx::*; use std::fmt::Write; use triple_accel::{levenshtein_search, Match}; -#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)] -pub struct FormattedBlock { - pub formatted: String, - pub start: usize, - pub end: usize, -} - -pub fn get_format_blocks(contents: &str) -> Vec { - let matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable(); - - let mut formatted_blocks = Vec::new(); - let mut last_bracket_end = 0; - - // find the rsx! marker - for Match { start, end, k } in matches { - // ensure the marker is not nested - if start < last_bracket_end { - continue; - } - - let remaining = &contents[end - 1..]; - let bracket_end = find_bracket_end(remaining).unwrap(); - let sub_string = &contents[end..bracket_end + end - 1]; - last_bracket_end = bracket_end + end - 1; - - let new = fmt_block(sub_string).unwrap(); - - let stripped = &contents[end + 1..bracket_end + end - 1]; - - if stripped == new { - continue; - } - - // if we have code to push, we want the code to end up on the right lines with the right indentation - let mut output = String::new(); - writeln!(output).unwrap(); - - for line in new.lines() { - writeln!(output, "{}", line).ok(); - } - - formatted_blocks.push(FormattedBlock { - formatted: output, - start: end, - end: end + bracket_end - 1, - }); - } - - formatted_blocks -} - -struct Isolate<'a> { - contents: &'a str, - start: usize, - end: usize, -} -fn isolate_body_of_rsx(contents: &str, Match { start, end, k }: triple_accel::Match) -> Isolate { - todo!() -} - -pub fn fmt_block(block: &str) -> Option { - let mut buf = String::new(); - let lines = block.split('\n').collect::>(); - - for node in &syn::parse_str::(block).ok()?.roots { - write_ident(&mut buf, &lines, node, 0).ok()?; - } - - Some(buf) -} - #[test] fn format_block_basic() { let block = r#" @@ -129,7 +58,7 @@ fn format_block_basic() { // div { class: "asdasd", p { "hello!" } } - let edits = get_format_blocks(block); + // let edits = get_format_blocks(block); - println!("{}", edits[0].formatted); + // println!("{}", edits[0].formatted); } diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index dfd7166ba..e6f021beb 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -1,17 +1,83 @@ -use std::fmt::Result; +use std::fmt::{Result, Write}; -struct Buffer { - buf: String, - cur_line: usize, - cur_indent: usize, +use dioxus_rsx::BodyNode; + +pub struct Buffer { + pub buf: String, + pub line: usize, + pub indent: usize, } impl Buffer { - fn write_tabs(&mut self, num: usize) -> Result { - todo!() + pub fn new() -> Self { + Self { + buf: String::new(), + line: 0, + indent: 0, + } } - fn new_line(&mut self) -> Result { - todo!() + // Create a new line and tab it to the current tab level + pub fn tabbed_line(&mut self) -> Result { + self.new_line()?; + self.tab() + } + + // Create a new line and tab it to the current tab level + pub fn indented_tabbed_line(&mut self) -> Result { + self.new_line()?; + self.indented_tab() + } + + pub fn tab(&mut self) -> Result { + self.write_tabs(self.indent) + } + + pub fn indented_tab(&mut self) -> Result { + self.write_tabs(self.indent + 1) + } + + pub fn write_tabs(&mut self, num: usize) -> std::fmt::Result { + for _ in 0..num { + write!(self.buf, " ")? + } + Ok(()) + } + + pub fn new_line(&mut self) -> Result { + writeln!(self.buf) + } + + pub fn write_indented_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result { + self.write_ident(lines, node)?; + Ok(()) + } + + pub fn write_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result { + match node { + BodyNode::Element(el) => self.write_element(el, lines), + BodyNode::Component(component) => self.write_component(component, lines), + BodyNode::Text(text) => self.write_text(text), + BodyNode::RawExpr(exp) => self.write_raw_expr(exp, lines), + } + } + + pub fn write_text(&mut self, text: &syn::LitStr) -> Result { + write!(self.buf, "\"{}\"", text.value()) + } + + // Push out the indent level and write each component, line by line + pub fn write_body_indented(&mut self, children: &[BodyNode], lines: &[&str]) -> Result { + self.indent += 1; + for child in children { + // Exprs handle their own indenting/line breaks + if !matches!(child, BodyNode::RawExpr(_)) { + self.tabbed_line()?; + } + + self.write_ident(lines, child)?; + } + self.indent -= 1; + Ok(()) } } diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index df4e14006..ec17b4c22 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -1,78 +1,85 @@ -use crate::{util::*, write_ident}; +use crate::Buffer; use dioxus_rsx::*; use quote::ToTokens; -use std::fmt::{self, Write}; +use std::fmt::{self, Result, Write}; -pub fn write_component( - component: &Component, - buf: &mut String, - indent: usize, - lines: &[&str], -) -> Result<(), fmt::Error> { - let Component { - name, - body, - children, - manual_props, - prop_gen_args, - } = component; - let mut name = name.to_token_stream().to_string(); - name.retain(|c| !c.is_whitespace()); - write_tabs(buf, indent)?; - write!(buf, "{name}")?; - if let Some(generics) = prop_gen_args { - let mut written = generics.to_token_stream().to_string(); - written.retain(|c| !c.is_whitespace()); - write!(buf, "{}", written)?; - } - write!(buf, " {{")?; - if !body.is_empty() || !children.is_empty() { - writeln!(buf)?; - } - for field in body { - write_tabs(buf, indent + 1)?; - let name = &field.name; - match &field.content { - ContentField::ManExpr(exp) => { - let out = prettyplease::unparse_expr(exp); - writeln!(buf, "{}: {},", name, out)?; - } - ContentField::Formatted(s) => { - writeln!(buf, "{}: \"{}\",", name, s.value())?; - } - ContentField::OnHandlerRaw(exp) => { - let out = prettyplease::unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(buf, "{}: {}", name, first)?; - for line in lines { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; +impl Buffer { + pub fn write_component( + &mut self, + Component { + name, + body, + children, + manual_props, + prop_gen_args, + }: &Component, + lines: &[&str], + ) -> Result { + let mut name = name.to_token_stream().to_string(); + name.retain(|c| !c.is_whitespace()); + self.tab()?; + write!(self.buf, "{name}")?; + + if let Some(generics) = prop_gen_args { + let mut written = generics.to_token_stream().to_string(); + written.retain(|c| !c.is_whitespace()); + write!(self.buf, "{}", written)?; + } + + write!(self.buf, " {{")?; + + if !body.is_empty() || !children.is_empty() { + self.new_line()?; + } + + for field in body { + self.indented_tab()?; + let name = &field.name; + match &field.content { + ContentField::ManExpr(exp) => { + let out = prettyplease::unparse_expr(exp); + writeln!(self.buf, "{}: {},", name, out)?; + } + ContentField::Formatted(s) => { + writeln!(self.buf, "{}: \"{}\",", name, s.value())?; + } + ContentField::OnHandlerRaw(exp) => { + let out = prettyplease::unparse_expr(exp); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(self.buf, "{}: {}", name, first)?; + for line in lines { + self.new_line()?; + self.indented_tab()?; + write!(self.buf, "{}", line)?; + } + writeln!(self.buf, ",")?; } - writeln!(buf, ",")?; } } - } - if let Some(exp) = manual_props { - write_tabs(buf, indent + 1)?; - let out = prettyplease::unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(buf, "..{}", first)?; - for line in lines { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; + + if let Some(exp) = manual_props { + self.indented_tab()?; + let out = prettyplease::unparse_expr(exp); + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + write!(self.buf, "..{}", first)?; + for line in lines { + self.new_line()?; + self.indented_tab()?; + write!(self.buf, "{}", line)?; + } + self.new_line()?; } - writeln!(buf)?; + + for child in children { + self.write_indented_ident(lines, child)?; + } + + if !body.is_empty() || !children.is_empty() { + self.tab()?; + } + writeln!(self.buf, "}}")?; + Ok(()) } - for child in children { - write_ident(buf, lines, child, indent + 1)?; - } - if !body.is_empty() || !children.is_empty() { - write_tabs(buf, indent)?; - } - writeln!(buf, "}}")?; - Ok(()) } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 4c4a00785..0180f15a4 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -1,6 +1,6 @@ -use crate::{util::*, write_ident}; +use crate::{util::*, Buffer}; use dioxus_rsx::*; -use std::{fmt, fmt::Result, fmt::Write}; +use std::{fmt::Result, fmt::Write}; enum ShortOptimization { // Special because we want to print the closing bracket immediately @@ -16,108 +16,181 @@ enum ShortOptimization { NoOpt, } -pub fn write_element( - Element { - name, - key, - attributes, - children, - _is_static, - }: &Element, - buf: &mut String, - lines: &[&str], - indent: usize, -) -> Result { - /* - 1. Write the tag - 2. Write the key - 3. Write the attributes - 4. Write the children - */ +impl Buffer { + pub fn write_element( + &mut self, + Element { + name, + key, + attributes, + children, + _is_static, + }: &Element, + lines: &[&str], + ) -> Result { + /* + 1. Write the tag + 2. Write the key + 3. Write the attributes + 4. Write the children + */ - write!(buf, "{name} {{")?; + write!(self.buf, "{name} {{")?; - // decide if we have any special optimizations - // Default with none, opt the cases in one-by-one - let mut opt_level = ShortOptimization::NoOpt; + // decide if we have any special optimizations + // Default with none, opt the cases in one-by-one + let mut opt_level = ShortOptimization::NoOpt; - // check if we have a lot of attributes - let is_short_attr_list = is_short_attrs(attributes); - let is_small_children = is_short_children(children); + // check if we have a lot of attributes + let is_short_attr_list = is_short_attrs(attributes); + let is_small_children = is_short_children(children); - // if we have few attributes and a lot of children, place the attrs on top - if is_short_attr_list && !is_small_children { - opt_level = ShortOptimization::PropsOnTop; - } - - // if we have few children and few attributes, make it a one-liner - if is_short_attr_list && is_small_children { - opt_level = ShortOptimization::Oneliner; - } - - // If there's nothing at all, empty optimization - if attributes.is_empty() && children.is_empty() && key.is_none() { - opt_level = ShortOptimization::Empty; - } - - match opt_level { - ShortOptimization::Empty => write!(buf, "}}")?, - ShortOptimization::Oneliner => { - write!(buf, " ")?; - write_attributes(buf, attributes, true, indent)?; - - if !children.is_empty() && !attributes.is_empty() { - write!(buf, ", ")?; - } - - // write the children - for child in children { - write_ident(buf, lines, child, indent + 1)?; - } - - write!(buf, " }}")?; + // if we have few attributes and a lot of children, place the attrs on top + if is_short_attr_list && !is_small_children { + opt_level = ShortOptimization::PropsOnTop; } - ShortOptimization::PropsOnTop => { - write!(buf, " ")?; - write_attributes(buf, attributes, true, indent)?; - - if !children.is_empty() && !attributes.is_empty() { - write!(buf, ",")?; + // even if the attr is long, it should be put on one line + if !is_short_attr_list && attributes.len() <= 1 { + if children.is_empty() { + opt_level = ShortOptimization::Oneliner; + } else { + opt_level = ShortOptimization::PropsOnTop; } - - // write the children - for child in children { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - write_ident(buf, lines, child, indent + 1)?; - } - - writeln!(buf)?; - write_tabs(buf, indent)?; - write!(buf, "}}")?; } - ShortOptimization::NoOpt => { - // write the key + // if we have few children and few attributes, make it a one-liner + if is_short_attr_list && is_small_children { + opt_level = ShortOptimization::Oneliner; + } - // write the attributes - write_attributes(buf, attributes, false, indent)?; + // If there's nothing at all, empty optimization + if attributes.is_empty() && children.is_empty() && key.is_none() { + opt_level = ShortOptimization::Empty; + } - // write the children - for child in children { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - write_ident(buf, lines, child, indent + 1)?; + match opt_level { + ShortOptimization::Empty => write!(self.buf, "}}")?, + ShortOptimization::Oneliner => { + write!(self.buf, " ")?; + self.write_attributes(attributes, true)?; + + if !children.is_empty() && !attributes.is_empty() { + write!(self.buf, ", ")?; + } + + // write the children + for child in children { + self.write_ident(lines, child)?; + } + + write!(self.buf, " }}")?; } - writeln!(buf)?; - write_tabs(buf, indent)?; - write!(buf, "}}")?; + ShortOptimization::PropsOnTop => { + write!(self.buf, " ")?; + self.write_attributes(attributes, true)?; + + if !children.is_empty() && !attributes.is_empty() { + write!(self.buf, ",")?; + } + + // write the children + self.write_body_indented(children, lines)?; + + self.tabbed_line()?; + write!(self.buf, "}}")?; + } + + ShortOptimization::NoOpt => { + // write the key + + // write the attributes + self.write_attributes(attributes, false)?; + + self.write_body_indented(children, lines)?; + + self.tabbed_line()?; + write!(self.buf, "}}")?; + } } + + Ok(()) } - Ok(()) + fn write_attributes(&mut self, attributes: &[ElementAttrNamed], sameline: bool) -> Result { + let mut attr_iter = attributes.iter().peekable(); + + while let Some(attr) = attr_iter.next() { + if !sameline { + self.indented_tabbed_line()?; + } + self.write_attribute(attr)?; + + if attr_iter.peek().is_some() { + write!(self.buf, ",")?; + + if sameline { + write!(self.buf, " ")?; + } + } + } + + Ok(()) + } + + fn write_attribute(&mut self, attr: &ElementAttrNamed) -> Result { + match &attr.attr { + ElementAttr::AttrText { name, value } => { + write!(self.buf, "{name}: \"{value}\"", value = value.value())?; + } + ElementAttr::AttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(self.buf, "{}: {}", name, out)?; + } + + ElementAttr::CustomAttrText { name, value } => { + write!( + self.buf, + "\"{name}\": \"{value}\"", + name = name.value(), + value = value.value() + )?; + } + + ElementAttr::CustomAttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(self.buf, "\"{}\": {}", name.value(), out)?; + } + + ElementAttr::EventTokens { name, tokens } => { + let out = prettyplease::unparse_expr(tokens); + + let mut lines = out.split('\n').peekable(); + let first = lines.next().unwrap(); + + // a one-liner for whatever reason + // Does not need a new line + if lines.peek().is_none() { + write!(self.buf, "{}: {}", name, first)?; + } else { + writeln!(self.buf, "{}: {}", name, first)?; + + while let Some(line) = lines.next() { + self.indented_tab()?; + write!(self.buf, "{}", line)?; + if lines.peek().is_none() { + write!(self.buf, "")?; + } else { + writeln!(self.buf)?; + } + } + } + } + } + + Ok(()) + } } fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { @@ -132,106 +205,29 @@ fn is_short_children(children: &[BodyNode]) -> bool { return true; } - if children.len() == 1 { - if let BodyNode::Text(ref text) = &children[0] { - return text.value().len() < 80; - } - } + match children { + [BodyNode::Text(ref text)] => text.value().len() < 80, + [BodyNode::Element(ref el)] => { + // && !el.attributes.iter().any(|f| f.attr.is_expr()) - false + extract_attr_len(&el.attributes) < 80 && is_short_children(&el.children) + } + _ => false, + } } fn write_key() { // if let Some(key) = key.as_ref().map(|f| f.value()) { // if is_long_attr_list { - // writeln!(buf)?; - // write_tabs(buf, indent + 1)?; + // self.new_line()?; + // self.write_tabs( indent + 1)?; // } else { - // write!(buf, " ")?; + // write!(self.buf, " ")?; // } - // write!(buf, "key: \"{key}\"")?; + // write!(self.buf, "key: \"{key}\"")?; // if !attributes.is_empty() { - // write!(buf, ",")?; + // write!(self.buf, ",")?; // } // } } - -fn write_attributes( - buf: &mut String, - attributes: &[ElementAttrNamed], - sameline: bool, - indent: usize, -) -> Result { - let mut attr_iter = attributes.iter().peekable(); - - while let Some(attr) = attr_iter.next() { - write_attribute(buf, attr, indent)?; - - if attr_iter.peek().is_some() { - write!(buf, ",")?; - - if sameline { - write!(buf, " ")?; - } else { - writeln!(buf)?; - write_tabs(buf, indent + 1)?; - } - } - } - - Ok(()) -} - -fn write_attribute(buf: &mut String, attr: &ElementAttrNamed, indent: usize) -> Result { - match &attr.attr { - ElementAttr::AttrText { name, value } => { - write!(buf, "{name}: \"{value}\"", value = value.value())?; - } - ElementAttr::AttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "{}: {}", name, out)?; - } - - ElementAttr::CustomAttrText { name, value } => { - write!( - buf, - "\"{name}\": \"{value}\"", - name = name.value(), - value = value.value() - )?; - } - - ElementAttr::CustomAttrExpression { name, value } => { - let out = prettyplease::unparse_expr(value); - write!(buf, "\"{}\": {}", name.value(), out)?; - } - - ElementAttr::EventTokens { name, tokens } => { - let out = prettyplease::unparse_expr(tokens); - - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - - // a one-liner for whatever reason - // Does not need a new line - if lines.peek().is_none() { - write!(buf, "{}: {}", name, first)?; - } else { - writeln!(buf, "{}: {}", name, first)?; - - while let Some(line) = lines.next() { - write_tabs(buf, indent + 1)?; - write!(buf, "{}", line)?; - if lines.peek().is_none() { - write!(buf, "")?; - } else { - writeln!(buf)?; - } - } - } - } - } - - Ok(()) -} diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index 7cc0ed17a..cf75e7d87 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -1,39 +1,53 @@ //! pretty printer for rsx! -use std::fmt::{self, Write}; +use std::fmt::{self, Result, Write}; -pub fn write_raw_expr( - exp: &syn::Expr, - indent: usize, - lines: &[&str], - buf: &mut String, -) -> Result<(), fmt::Error> { - use syn::spanned::Spanned; - let placement = exp.span(); - let start = placement.start(); - let end = placement.end(); - let num_spaces_desired = (indent * 4) as isize; - let first = lines[start.line - 1]; - let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; - let offset = num_spaces_real - num_spaces_desired; +use crate::Buffer; - for line_id in start.line - 1..end.line { - let line = lines[line_id]; +impl Buffer { + pub fn write_raw_expr(&mut self, exp: &syn::Expr, lines: &[&str]) -> Result { + /* + We want to normalize the expr to the appropriate indent level. + */ - // trim the leading whitespace + // in a perfect world, just fire up the rust pretty printer + // pretty_print_rust_code_as_if_it_were_rustfmt() - if offset < 0 { - for _ in 0..-offset { - write!(buf, " ")?; + use syn::spanned::Spanned; + let placement = exp.span(); + let start = placement.start(); + let end = placement.end(); + let num_spaces_desired = (self.indent * 4) as isize; + + let first = lines[start.line - 1]; + let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; + + let offset = num_spaces_real - num_spaces_desired; + + for line in &lines[start.line - 1..end.line] { + writeln!(self.buf)?; + // trim the leading whitespace + if offset < 0 { + for _ in 0..-offset { + write!(self.buf, " ")?; + } + + write!(self.buf, "{}", line)?; + } else { + let offset = offset as usize; + let right = &line[offset..]; + write!(self.buf, "{}", right)?; } - - writeln!(buf, "{}", line)?; - } else { - let offset = offset as usize; - - let right = &line[offset..]; - writeln!(buf, "{}", right)?; } - } - Ok(()) + Ok(()) + } } + +// :( +// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String { +// let formatted = prettyplease::unparse_expr(exp); +// for line in formatted.lines() { +// write!(self.buf, "{}", line)?; +// self.new_line()?; +// } +// } diff --git a/packages/autofmt/src/ident.rs b/packages/autofmt/src/ident.rs deleted file mode 100644 index e4dee56c9..000000000 --- a/packages/autofmt/src/ident.rs +++ /dev/null @@ -1,23 +0,0 @@ -use crate::{ - component::write_component, element::write_element, expr::write_raw_expr, util::write_tabs, -}; -use dioxus_rsx::*; -use std::fmt::{self, Write}; - -pub fn write_ident( - buf: &mut String, - lines: &[&str], - node: &BodyNode, - indent: usize, -) -> fmt::Result { - match node { - BodyNode::Element(el) => write_element(el, buf, lines, indent), - BodyNode::Component(component) => write_component(component, buf, indent, lines), - BodyNode::Text(text) => write_text(text, buf, indent), - BodyNode::RawExpr(exp) => write_raw_expr(exp, indent, lines, buf), - } -} - -fn write_text(text: &syn::LitStr, buf: &mut String, indent: usize) -> fmt::Result { - write!(buf, "\"{}\"", text.value()) -} diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 6d6c88819..6a9e5c594 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -1,8 +1,7 @@ //! pretty printer for rsx code -use dioxus_rsx::*; -use proc_macro2::TokenStream as TokenStream2; -use quote::ToTokens; +pub use crate::buffer::*; +use crate::util::*; mod block; mod buffer; @@ -10,8 +9,75 @@ mod children; mod component; mod element; mod expr; -mod ident; mod util; -pub use block::{fmt_block, get_format_blocks}; -pub use ident::write_ident; +// pub use block::{fmt_block, get_format_blocks}; + +/// A modification to the original file to be applied by an IDE +/// +/// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes. +/// +/// In a "perfect" world we would have tiny edits to preserve things like cursor states and selections. The API here makes +/// it possible to migrate to a more precise modification approach in the future without breaking existing code. +/// +/// Note that this is tailored to VSCode's TextEdit API and not a general Diff API. Line numbers are not accurate if +/// multiple edits are applied in a single file without tracking text shifts. +#[derive(serde::Deserialize, serde::Serialize, Clone, Debug, PartialEq, Hash)] +pub struct FormattedBlock { + /// The new contents of the block + pub formatted: String, + + /// The line number of the first line of the block. + pub start: usize, + + /// The end of the block, exclusive. + pub end: usize, +} + +/// Format a file into a list of `FormattedBlock`s to be applied by an IDE for autoformatting. +/// +/// This function expects a complete file, not just a block of code. To format individual rsx! blocks, use fmt_block instead. +pub fn fmt_file(contents: &str) -> Vec { + let mut formatted_blocks = Vec::new(); + let mut last_bracket_end = 0; + + use triple_accel::{levenshtein_search, Match}; + + for Match { end, k, start } in levenshtein_search(b"rsx! {", contents.as_bytes()) { + // ensure the marker is not nested + if start < last_bracket_end { + continue; + } + + let remaining = &contents[end - 1..]; + let bracket_end = find_bracket_end(remaining).unwrap(); + let sub_string = &contents[end..bracket_end + end - 1]; + last_bracket_end = bracket_end + end - 1; + + let new = fmt_block(sub_string).unwrap(); + let stripped = &contents[end + 1..bracket_end + end - 1]; + + if stripped == new { + continue; + } + + formatted_blocks.push(FormattedBlock { + formatted: new, + start: end, + end: end + bracket_end - 1, + }); + } + + formatted_blocks +} + +pub fn fmt_block(block: &str) -> Option { + let mut buf = Buffer::new(); + let lines = block.split('\n').collect::>(); + + for node in &syn::parse_str::(block).ok()?.roots { + buf.write_ident(&lines, node).ok()?; + } + + Some(buf.buf) +} diff --git a/packages/autofmt/src/util.rs b/packages/autofmt/src/util.rs index 9a358e959..96db23697 100644 --- a/packages/autofmt/src/util.rs +++ b/packages/autofmt/src/util.rs @@ -14,13 +14,6 @@ pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize { .sum() } -pub fn write_tabs(f: &mut dyn Write, num: usize) -> std::fmt::Result { - for _ in 0..num { - write!(f, " ")? - } - Ok(()) -} - pub fn find_bracket_end(contents: &str) -> Option { let mut depth = 0; let mut i = 0; diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index 3f4df0a4e..c42c5de9d 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -2,6 +2,16 @@ use dioxus_autofmt::*; use proc_macro2::TokenStream as TokenStream2; use syn::{Attribute, Meta}; +fn test_block(wrong: &str, right: &str) { + let formatted = fmt_block(wrong).unwrap(); + assert_eq!(formatted, right); +} + +fn print_block(wrong: &str) { + let formatted = fmt_block(wrong).unwrap(); + println!("{}", formatted); +} + #[test] fn formats_block() { let block = r#" @@ -46,7 +56,7 @@ fn formats_block() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -62,6 +72,54 @@ fn parse_comment() { dbg!(parsed); } +#[test] +fn print_cases() { + print_block( + r#" + div { + adsasd: "asd", + h1 {"asd"} + div { + div { + "hello" + } + div { + "goodbye" + } + div { class: "broccoli", + div { + "hi" + } + } + div { class: "broccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccoli", + div { + "hi" + } + } + div { class: "alksdjasd", onclick: move |_| { + + liberty!(); + }, + div { + "hi" + } + } + + commented{ + // is unparalled + class: "asdasd", + + // My genius + div { + "hi" + } + } + } + } + "#, + ); +} + #[test] fn formats_component() { let block = r#" @@ -76,7 +134,7 @@ fn formats_component() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -97,7 +155,7 @@ fn formats_element() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -118,7 +176,7 @@ fn formats_element_short() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -132,7 +190,25 @@ fn formats_element_nested() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + assert_eq!( + formatted, + r#"h3 { class: "mb-2 text-xl font-bold", "Invite Member" }"# + ); +} + +#[test] +fn formats_element_props_on_top() { + let block = r#" + h3 { + class: "mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold", + + "Invite Member" + } +"#; + + let formatted = fmt_block(block).unwrap(); + + println!("{formatted}"); } #[test] @@ -143,7 +219,10 @@ fn formats_element_nested_no_trailing_tabs() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + assert_eq!( + formatted, + r#"img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" }"# + ); } #[test] @@ -160,20 +239,20 @@ fn formats_element_with_correct_indent() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] fn small_elements_and_text_are_small() { let block = r###" - a { class: " text-white", + a { class: "text-white", "Send invitation" } "###; let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + assert_eq!(formatted, r#"a { class: "text-white", "Send invitation" }"#); } #[test] @@ -192,7 +271,7 @@ fn formats_component_man_props() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -204,24 +283,24 @@ fn formats_component_tiny() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] fn formats_exprs() { let block = r#" ul { - (0..10).map(|f| rsx!{ - li { - "hi" - } + div {} + (0..10).map(|f| rsx! { + li { "hi" } }) + div {} } "#; let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -238,7 +317,7 @@ fn formats_exprs_neg_indent() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -261,7 +340,7 @@ fn formats_exprs_handlers() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -296,7 +375,7 @@ fn formats_complex() { let formatted = fmt_block(block).unwrap(); - print!("{formatted}"); + println!("{formatted}"); } #[test] @@ -314,9 +393,9 @@ rsx!{ "#; - let formatted = get_format_blocks(block); + let formatted = fmt_file(block); - print!("{formatted:?}"); + println!("{formatted:?}"); } #[test] @@ -332,9 +411,9 @@ rsx!{ } "#; - let formatted = get_format_blocks(block); + let formatted = fmt_file(block); - print!("{formatted:?}"); + println!("{formatted:?}"); } #[test] @@ -350,7 +429,7 @@ rsx! { } "#; - let formatted = get_format_blocks(src); + let formatted = fmt_file(src); println!("{formatted:?}"); } @@ -378,7 +457,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) "# .to_string(); - let formatted = get_format_blocks(&src); + let formatted = fmt_file(&src); let block = formatted.into_iter().next().unwrap(); @@ -446,7 +525,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) "# .to_string(); - let formatted = get_format_blocks(&src); + let formatted = fmt_file(&src); dbg!(&formatted); @@ -469,7 +548,7 @@ pub fn Alert(cx: Scope) -> Element { "### .to_string(); - let formatted = get_format_blocks(&src); + let formatted = fmt_file(&src); dbg!(&formatted); } diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 38ab75575..456f45b54 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -209,6 +209,16 @@ pub enum ElementAttr { /// onclick: {} EventTokens { name: Ident, tokens: Expr }, } +impl ElementAttr { + pub fn is_expr(&self) -> bool { + matches!( + self, + ElementAttr::AttrExpression { .. } + | ElementAttr::CustomAttrExpression { .. } + | ElementAttr::EventTokens { .. } + ) + } +} #[derive(PartialEq, Eq)] pub struct ElementAttrNamed { From a825cdcf58cdb9177102e72c2d5ba28c49546d37 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 15:01:51 -0400 Subject: [PATCH 06/13] feat: simple support for comments --- packages/autofmt/src/buffer.rs | 144 ++++++++++++++++++++++++++---- packages/autofmt/src/component.rs | 3 +- packages/autofmt/src/element.rs | 57 +++++++----- packages/autofmt/src/expr.rs | 7 +- packages/autofmt/src/lib.rs | 6 +- packages/autofmt/tests/sink.rs | 39 +++++++- packages/rsx/src/node.rs | 13 ++- 7 files changed, 218 insertions(+), 51 deletions(-) diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index e6f021beb..46f3668be 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -2,21 +2,14 @@ use std::fmt::{Result, Write}; use dioxus_rsx::BodyNode; +#[derive(Default, Debug)] pub struct Buffer { + pub src: Vec, pub buf: String, - pub line: usize, pub indent: usize, } impl Buffer { - pub fn new() -> Self { - Self { - buf: String::new(), - line: 0, - indent: 0, - } - } - // Create a new line and tab it to the current tab level pub fn tabbed_line(&mut self) -> Result { self.new_line()?; @@ -48,17 +41,17 @@ impl Buffer { writeln!(self.buf) } - pub fn write_indented_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result { - self.write_ident(lines, node)?; + pub fn write_indented_ident(&mut self, node: &BodyNode) -> Result { + self.write_ident(node)?; Ok(()) } - pub fn write_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result { + pub fn write_ident(&mut self, node: &BodyNode) -> Result { match node { - BodyNode::Element(el) => self.write_element(el, lines), - BodyNode::Component(component) => self.write_component(component, lines), + BodyNode::Element(el) => self.write_element(el), + BodyNode::Component(component) => self.write_component(component), BodyNode::Text(text) => self.write_text(text), - BodyNode::RawExpr(exp) => self.write_raw_expr(exp, lines), + BodyNode::RawExpr(exp) => self.write_raw_expr(exp), } } @@ -67,17 +60,134 @@ impl Buffer { } // Push out the indent level and write each component, line by line - pub fn write_body_indented(&mut self, children: &[BodyNode], lines: &[&str]) -> Result { + pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { self.indent += 1; + + let mut comments = Vec::new(); + for child in children { // Exprs handle their own indenting/line breaks if !matches!(child, BodyNode::RawExpr(_)) { + // collect all comments upwards + let start = child.span().start().line; + + for (id, line) in self.src[..start - 1].iter().enumerate().rev() { + if line.trim().starts_with("//") || line.is_empty() { + comments.push(id); + } else { + break; + } + } + + let mut last_was_empty = false; + for comment_line in comments.drain(..).rev() { + let line = &self.src[comment_line]; + if line.is_empty() { + if !last_was_empty { + self.new_line()?; + } + last_was_empty = true; + } else { + last_was_empty = false; + self.tabbed_line()?; + write!(self.buf, "{}", self.src[comment_line].trim())?; + } + } + self.tabbed_line()?; } - self.write_ident(lines, child)?; + self.write_ident(child)?; } + + // let mut children_iter = children.iter().enumerate().peekable(); + + // while let Some((id, child)) = children_iter.next() { + // let mut written_empty = false; + + // for line in lines[self.line..child.span().start().line - 1].iter() { + // if id == 0 && line.trim().is_empty() { + // continue; + // } + + // if !written_empty && line.is_empty() { + // writeln!(self.buf)?; + // written_empty = true; + // continue; + // } + + // if written_empty && line.is_empty() { + // continue; + // } + + // writeln!(self.buf)?; + // // self.write_tabs(indent + 1)?; + // write!(self.buf, "{}", line.trim())?; + // } + + // writeln!(self.buf)?; + // self.indented_tab()?; + // self.write_ident(lines, child)?; + + // self.line = child.span().end().line; + // } + + // for child in children { + // // Exprs handle their own indenting/line breaks + // if !matches!(child, BodyNode::RawExpr(_)) { + // self.tabbed_line()?; + // } + + // self.write_ident(lines, child)?; + // } self.indent -= 1; Ok(()) } } + +// ShortOptimization::PropsOnTop => { +// write!(self.buf, " ")?; +// self.write_attributes(attributes, true, indent)?; + +// if !children.is_empty() && !attributes.is_empty() { +// write!(self.buf, ",")?; +// } + +// if let Some(last_attr) = attributes.last() { +// self.cur_line = last_attr.name_span().end().line + 1; +// } + +// // write the children +// for (id, child) in children.iter().enumerate() { +// let mut written_empty = false; +// for line in self.lines[self.cur_line..child.span().start().line - 1].iter() { +// if id == 0 && line.trim().is_empty() { +// continue; +// } + +// if !written_empty && line.is_empty() { +// writeln!(self.buf)?; +// written_empty = true; +// continue; +// } + +// if written_empty && line.is_empty() { +// continue; +// } + +// writeln!(self.buf)?; +// // self.write_tabs(indent + 1)?; +// write!(self.buf, "{}", line.trim())?; +// } + +// writeln!(self.buf)?; +// self.write_tabs(indent + 1)?; +// self.write_ident(child, indent + 1)?; + +// self.cur_line = child.span().end().line; +// } + +// writeln!(self.buf)?; +// self.write_tabs(indent)?; +// write!(self.buf, "}}")?; +// } diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index ec17b4c22..81c75c540 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -13,7 +13,6 @@ impl Buffer { manual_props, prop_gen_args, }: &Component, - lines: &[&str], ) -> Result { let mut name = name.to_token_stream().to_string(); name.retain(|c| !c.is_whitespace()); @@ -73,7 +72,7 @@ impl Buffer { } for child in children { - self.write_indented_ident(lines, child)?; + self.write_indented_ident(child)?; } if !body.is_empty() || !children.is_empty() { diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 0180f15a4..247c7dae1 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -26,7 +26,6 @@ impl Buffer { children, _is_static, }: &Element, - lines: &[&str], ) -> Result { /* 1. Write the tag @@ -43,7 +42,7 @@ impl Buffer { // check if we have a lot of attributes let is_short_attr_list = is_short_attrs(attributes); - let is_small_children = is_short_children(children); + let is_small_children = self.is_short_children(children); // if we have few attributes and a lot of children, place the attrs on top if is_short_attr_list && !is_small_children { @@ -81,7 +80,7 @@ impl Buffer { // write the children for child in children { - self.write_ident(lines, child)?; + self.write_ident(child)?; } write!(self.buf, " }}")?; @@ -96,7 +95,7 @@ impl Buffer { } // write the children - self.write_body_indented(children, lines)?; + self.write_body_indented(children)?; self.tabbed_line()?; write!(self.buf, "}}")?; @@ -108,7 +107,7 @@ impl Buffer { // write the attributes self.write_attributes(attributes, false)?; - self.write_body_indented(children, lines)?; + self.write_body_indented(children)?; self.tabbed_line()?; write!(self.buf, "}}")?; @@ -191,6 +190,36 @@ impl Buffer { Ok(()) } + + // check if the children are short enough to be on the same line + // We don't have the notion of current line depth - each line tries to be < 80 total + fn is_short_children(&self, children: &[BodyNode]) -> bool { + if children.is_empty() { + // todo: allow elements with comments but no children + // like div { /* comment */ } + return true; + } + + for child in children { + 'line: for line in self.src[..child.span().start().line - 1].iter().rev() { + if line.trim().starts_with("//") { + return false; + } else if line.is_empty() { + continue; + } else { + break 'line; + } + } + } + + match children { + [BodyNode::Text(ref text)] => text.value().len() < 80, + [BodyNode::Element(ref el)] => { + extract_attr_len(&el.attributes) < 80 && self.is_short_children(&el.children) + } + _ => false, + } + } } fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { @@ -198,24 +227,6 @@ fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { total_attr_len < 80 } -// check if the children are short enough to be on the same line -// We don't have the notion of current line depth - each line tries to be < 80 total -fn is_short_children(children: &[BodyNode]) -> bool { - if children.is_empty() { - return true; - } - - match children { - [BodyNode::Text(ref text)] => text.value().len() < 80, - [BodyNode::Element(ref el)] => { - // && !el.attributes.iter().any(|f| f.attr.is_expr()) - - extract_attr_len(&el.attributes) < 80 && is_short_children(&el.children) - } - _ => false, - } -} - fn write_key() { // if let Some(key) = key.as_ref().map(|f| f.value()) { // if is_long_attr_list { diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index cf75e7d87..5415f8dd4 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -4,7 +4,7 @@ use std::fmt::{self, Result, Write}; use crate::Buffer; impl Buffer { - pub fn write_raw_expr(&mut self, exp: &syn::Expr, lines: &[&str]) -> Result { + pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result { /* We want to normalize the expr to the appropriate indent level. */ @@ -18,12 +18,13 @@ impl Buffer { let end = placement.end(); let num_spaces_desired = (self.indent * 4) as isize; - let first = lines[start.line - 1]; + let first = &self.src[start.line - 1]; + // let first = lines[start.line - 1]; let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; let offset = num_spaces_real - num_spaces_desired; - for line in &lines[start.line - 1..end.line] { + for line in &self.src[start.line - 1..end.line] { writeln!(self.buf)?; // trim the leading whitespace if offset < 0 { diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 6a9e5c594..f59f74bbd 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -72,11 +72,13 @@ pub fn fmt_file(contents: &str) -> Vec { } pub fn fmt_block(block: &str) -> Option { - let mut buf = Buffer::new(); + let mut buf = Buffer::default(); + buf.src = block.lines().map(|f| f.to_string()).collect(); // unnecessary clone, but eh, most files are small + let lines = block.split('\n').collect::>(); for node in &syn::parse_str::(block).ok()?.roots { - buf.write_ident(&lines, node).ok()?; + buf.write_ident(&node).ok()?; } Some(buf.buf) diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index c42c5de9d..93d929912 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -97,7 +97,7 @@ fn print_cases() { } } div { class: "alksdjasd", onclick: move |_| { - + // hi! liberty!(); }, div { @@ -105,7 +105,7 @@ fn print_cases() { } } - commented{ + commented { // is unparalled class: "asdasd", @@ -113,6 +113,10 @@ fn print_cases() { div { "hi" } + + div { + + } } } } @@ -120,6 +124,37 @@ fn print_cases() { ); } +#[test] +fn format_comments() { + let block = r#" + div { + adsasd: "asd", block: "asd", + + + // this is a comment + "hello" + + // this is a comment 1 + + // this is a comment 2 + "hello" + + div { + // this is a comment + "hello" + } + + div { + // empty space + } + } + "#; + + let formatted = fmt_block(block).unwrap(); + + println!("{formatted}"); +} + #[test] fn formats_component() { let block = r#" diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index e8b754f13..73911c15b 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -1,10 +1,10 @@ use super::*; -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream}, - token, Expr, LitStr, Result, + token, Expr, LitStr, Result, spanned::Spanned, }; /* @@ -27,6 +27,15 @@ impl BodyNode { pub fn is_litstr(&self) -> bool { matches!(self, BodyNode::Text(_)) } + + pub fn span(&self) -> Span { + match self { + BodyNode::Element(el) => el.name.span(), + BodyNode::Component(component) => component.name.span(), + BodyNode::Text(text) => text.span(), + BodyNode::RawExpr(exp) => exp.span(), + } + } } impl Parse for BodyNode { From 1e4af430a865d98537079e719ff39b95dd598567 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 15:15:09 -0400 Subject: [PATCH 07/13] wip: eliminate warnings --- packages/autofmt/src/block.rs | 64 ---------------------- packages/autofmt/src/buffer.rs | 91 ++----------------------------- packages/autofmt/src/children.rs | 4 -- packages/autofmt/src/component.rs | 2 +- packages/autofmt/src/element.rs | 32 ++--------- packages/autofmt/src/expr.rs | 2 +- packages/autofmt/src/lib.rs | 22 +++----- packages/autofmt/src/util.rs | 23 ++++---- 8 files changed, 28 insertions(+), 212 deletions(-) delete mode 100644 packages/autofmt/src/block.rs delete mode 100644 packages/autofmt/src/children.rs diff --git a/packages/autofmt/src/block.rs b/packages/autofmt/src/block.rs deleted file mode 100644 index 2dad1b780..000000000 --- a/packages/autofmt/src/block.rs +++ /dev/null @@ -1,64 +0,0 @@ -use crate::{util::*, FormattedBlock}; -use dioxus_rsx::*; -use std::fmt::Write; -use triple_accel::{levenshtein_search, Match}; - -#[test] -fn format_block_basic() { - let block = r#" - let a = 120; - - rsx! { - div { - h1 { "thing" } - h1 { "thing" } - h1 { "thing" } - h1 { "thing" "is whack" "but you're wacker" } - h1 { "thing" div {"special cases?" } } - div { - a: 123, - b: 456, - c: 789, - d: "hello", - } - div { - a: 123, - b: 456, - c: 789, - d: "hello", - p {} - c {} - } - - h3 { class: "asdasd", "asdasd" } - h3 { class: "mx-large bg-gray-900 tall-md-400", div { - "classy" - }} - - - // Some comments explaining my genius - div { - "comment compression" - } - - // Some comments explaining my genius - div { - // Some comments explaining my genius - a: 123, - - // comment compression - b: 456, - - // comments on attributes - c: 789, - } - } - } - "#; - - // div { class: "asdasd", p { "hello!" } } - - // let edits = get_format_blocks(block); - - // println!("{}", edits[0].formatted); -} diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index 46f3668be..b6769d8e2 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -59,6 +59,10 @@ impl Buffer { write!(self.buf, "\"{}\"", text.value()) } + pub fn consume(self) -> Option { + Some(self.buf) + } + // Push out the indent level and write each component, line by line pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result { self.indent += 1; @@ -100,94 +104,7 @@ impl Buffer { self.write_ident(child)?; } - // let mut children_iter = children.iter().enumerate().peekable(); - - // while let Some((id, child)) = children_iter.next() { - // let mut written_empty = false; - - // for line in lines[self.line..child.span().start().line - 1].iter() { - // if id == 0 && line.trim().is_empty() { - // continue; - // } - - // if !written_empty && line.is_empty() { - // writeln!(self.buf)?; - // written_empty = true; - // continue; - // } - - // if written_empty && line.is_empty() { - // continue; - // } - - // writeln!(self.buf)?; - // // self.write_tabs(indent + 1)?; - // write!(self.buf, "{}", line.trim())?; - // } - - // writeln!(self.buf)?; - // self.indented_tab()?; - // self.write_ident(lines, child)?; - - // self.line = child.span().end().line; - // } - - // for child in children { - // // Exprs handle their own indenting/line breaks - // if !matches!(child, BodyNode::RawExpr(_)) { - // self.tabbed_line()?; - // } - - // self.write_ident(lines, child)?; - // } self.indent -= 1; Ok(()) } } - -// ShortOptimization::PropsOnTop => { -// write!(self.buf, " ")?; -// self.write_attributes(attributes, true, indent)?; - -// if !children.is_empty() && !attributes.is_empty() { -// write!(self.buf, ",")?; -// } - -// if let Some(last_attr) = attributes.last() { -// self.cur_line = last_attr.name_span().end().line + 1; -// } - -// // write the children -// for (id, child) in children.iter().enumerate() { -// let mut written_empty = false; -// for line in self.lines[self.cur_line..child.span().start().line - 1].iter() { -// if id == 0 && line.trim().is_empty() { -// continue; -// } - -// if !written_empty && line.is_empty() { -// writeln!(self.buf)?; -// written_empty = true; -// continue; -// } - -// if written_empty && line.is_empty() { -// continue; -// } - -// writeln!(self.buf)?; -// // self.write_tabs(indent + 1)?; -// write!(self.buf, "{}", line.trim())?; -// } - -// writeln!(self.buf)?; -// self.write_tabs(indent + 1)?; -// self.write_ident(child, indent + 1)?; - -// self.cur_line = child.span().end().line; -// } - -// writeln!(self.buf)?; -// self.write_tabs(indent)?; -// write!(self.buf, "}}")?; -// } diff --git a/packages/autofmt/src/children.rs b/packages/autofmt/src/children.rs deleted file mode 100644 index 3b56590bc..000000000 --- a/packages/autofmt/src/children.rs +++ /dev/null @@ -1,4 +0,0 @@ -// write the body of a component or element -pub fn write_children() { - -} diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 81c75c540..5b312b739 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -1,7 +1,7 @@ use crate::Buffer; use dioxus_rsx::*; use quote::ToTokens; -use std::fmt::{self, Result, Write}; +use std::fmt::{Result, Write}; impl Buffer { pub fn write_component( diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 247c7dae1..c7cdad03a 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -69,7 +69,7 @@ impl Buffer { } match opt_level { - ShortOptimization::Empty => write!(self.buf, "}}")?, + ShortOptimization::Empty => {} ShortOptimization::Oneliner => { write!(self.buf, " ")?; self.write_attributes(attributes, true)?; @@ -78,12 +78,11 @@ impl Buffer { write!(self.buf, ", ")?; } - // write the children for child in children { self.write_ident(child)?; } - write!(self.buf, " }}")?; + write!(self.buf, " ")?; } ShortOptimization::PropsOnTop => { @@ -94,26 +93,19 @@ impl Buffer { write!(self.buf, ",")?; } - // write the children self.write_body_indented(children)?; - self.tabbed_line()?; - write!(self.buf, "}}")?; } ShortOptimization::NoOpt => { - // write the key - - // write the attributes self.write_attributes(attributes, false)?; - self.write_body_indented(children)?; - self.tabbed_line()?; - write!(self.buf, "}}")?; } } + write!(self.buf, "}}")?; + Ok(()) } @@ -226,19 +218,3 @@ fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { let total_attr_len = extract_attr_len(attrs); total_attr_len < 80 } - -fn write_key() { - // if let Some(key) = key.as_ref().map(|f| f.value()) { - // if is_long_attr_list { - // self.new_line()?; - // self.write_tabs( indent + 1)?; - // } else { - // write!(self.buf, " ")?; - // } - // write!(self.buf, "key: \"{key}\"")?; - - // if !attributes.is_empty() { - // write!(self.buf, ",")?; - // } - // } -} diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index 5415f8dd4..41a866ad9 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -1,5 +1,5 @@ //! pretty printer for rsx! -use std::fmt::{self, Result, Write}; +use std::fmt::{Result, Write}; use crate::Buffer; diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index f59f74bbd..bae0b0bd1 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -1,18 +1,12 @@ -//! pretty printer for rsx code - -pub use crate::buffer::*; +use crate::buffer::*; use crate::util::*; -mod block; mod buffer; -mod children; mod component; mod element; mod expr; mod util; -// pub use block::{fmt_block, get_format_blocks}; - /// A modification to the original file to be applied by an IDE /// /// Right now this re-writes entire rsx! blocks at a time, instead of precise line-by-line changes. @@ -43,7 +37,7 @@ pub fn fmt_file(contents: &str) -> Vec { use triple_accel::{levenshtein_search, Match}; - for Match { end, k, start } in levenshtein_search(b"rsx! {", contents.as_bytes()) { + for Match { end, start, .. } in levenshtein_search(b"rsx! {", contents.as_bytes()) { // ensure the marker is not nested if start < last_bracket_end { continue; @@ -72,14 +66,14 @@ pub fn fmt_file(contents: &str) -> Vec { } pub fn fmt_block(block: &str) -> Option { - let mut buf = Buffer::default(); - buf.src = block.lines().map(|f| f.to_string()).collect(); // unnecessary clone, but eh, most files are small + let mut buf = Buffer { + src: block.lines().map(|f| f.to_string()).collect(), + ..Buffer::default() + }; - let lines = block.split('\n').collect::>(); - - for node in &syn::parse_str::(block).ok()?.roots { + for node in syn::parse_str::(block).ok()?.roots { buf.write_ident(&node).ok()?; } - Some(buf.buf) + buf.consume() } diff --git a/packages/autofmt/src/util.rs b/packages/autofmt/src/util.rs index 96db23697..9c84f98e1 100644 --- a/packages/autofmt/src/util.rs +++ b/packages/autofmt/src/util.rs @@ -1,35 +1,32 @@ use dioxus_rsx::*; -use std::fmt::Write; +// todo: use recursive or complete sizeing pub fn extract_attr_len(attributes: &[ElementAttrNamed]) -> usize { attributes .iter() .map(|attr| match &attr.attr { - ElementAttr::AttrText { name, value } => value.value().len(), - ElementAttr::AttrExpression { name, value } => 10, - ElementAttr::CustomAttrText { name, value } => value.value().len(), - ElementAttr::CustomAttrExpression { name, value } => 10, - ElementAttr::EventTokens { name, tokens } => 1000000, + ElementAttr::AttrText { value, .. } => value.value().len(), + ElementAttr::AttrExpression { .. } => 10, + ElementAttr::CustomAttrText { value, .. } => value.value().len(), + ElementAttr::CustomAttrExpression { .. } => 10, + ElementAttr::EventTokens { .. } => 1000000, }) .sum() } pub fn find_bracket_end(contents: &str) -> Option { let mut depth = 0; - let mut i = 0; - for c in contents.chars() { + for (i, c) in contents.chars().enumerate() { if c == '{' { depth += 1; } else if c == '}' { depth -= 1; - } - if depth == 0 { - return Some(i); + if depth == 0 { + return Some(i); + } } - - i += 1; } None From 5bab835843a778ea05c6b3831fbaf1dc2ebb9875 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 15:35:46 -0400 Subject: [PATCH 08/13] fix: allow keys --- packages/autofmt/src/element.rs | 24 ++++++++++++++++++++---- packages/autofmt/tests/sink.rs | 6 ++++++ 2 files changed, 26 insertions(+), 4 deletions(-) diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index c7cdad03a..a4af77249 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -72,7 +72,8 @@ impl Buffer { ShortOptimization::Empty => {} ShortOptimization::Oneliner => { write!(self.buf, " ")?; - self.write_attributes(attributes, true)?; + + self.write_attributes(attributes, key, true)?; if !children.is_empty() && !attributes.is_empty() { write!(self.buf, ", ")?; @@ -87,7 +88,7 @@ impl Buffer { ShortOptimization::PropsOnTop => { write!(self.buf, " ")?; - self.write_attributes(attributes, true)?; + self.write_attributes(attributes, key, true)?; if !children.is_empty() && !attributes.is_empty() { write!(self.buf, ",")?; @@ -98,7 +99,7 @@ impl Buffer { } ShortOptimization::NoOpt => { - self.write_attributes(attributes, false)?; + self.write_attributes(attributes, key, false)?; self.write_body_indented(children)?; self.tabbed_line()?; } @@ -109,9 +110,24 @@ impl Buffer { Ok(()) } - fn write_attributes(&mut self, attributes: &[ElementAttrNamed], sameline: bool) -> Result { + fn write_attributes( + &mut self, + attributes: &[ElementAttrNamed], + key: &Option, + sameline: bool, + ) -> Result { let mut attr_iter = attributes.iter().peekable(); + if let Some(key) = key { + if !sameline { + self.indented_tabbed_line()?; + } + write!(self.buf, "key: \"{}\"", key.value())?; + if !attributes.is_empty() { + write!(self.buf, ", ")?; + } + } + while let Some(attr) = attr_iter.next() { if !sameline { self.indented_tabbed_line()?; diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index 93d929912..1b10c9799 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -147,6 +147,12 @@ fn format_comments() { div { // empty space } + + div { + key: "asd", class: "asdsda", + div {} + h3 {} + } } "#; From fa756ba245449caf83705cc4a474ff7f420e5ee7 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 17:16:26 -0400 Subject: [PATCH 09/13] feat: manual props --- packages/autofmt/src/buffer.rs | 4 + packages/autofmt/src/component.rs | 198 ++++++++++++++++++++++++++---- packages/autofmt/src/element.rs | 33 ++--- packages/autofmt/src/expr.rs | 1 - packages/autofmt/tests/sink.rs | 58 +++++++-- packages/rsx/src/component.rs | 8 +- 6 files changed, 245 insertions(+), 57 deletions(-) diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index b6769d8e2..0884657ef 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -83,6 +83,10 @@ impl Buffer { } } + if comments.len() == 1 && self.src[comments[0]].is_empty() { + comments.pop(); + } + let mut last_was_empty = false; for comment_line in comments.drain(..).rev() { let line = &self.src[comment_line]; diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index 5b312b739..de6e146bf 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -2,45 +2,151 @@ use crate::Buffer; use dioxus_rsx::*; use quote::ToTokens; use std::fmt::{Result, Write}; +use syn::AngleBracketedGenericArguments; + +enum ShortOptimization { + // Special because we want to print the closing bracket immediately + Empty, + + // Special optimization to put everything on the same line + Oneliner, + + // Optimization where children flow but props remain fixed on top + PropsOnTop, + + // The noisiest optimization where everything flows + NoOpt, +} impl Buffer { pub fn write_component( &mut self, Component { name, - body, + fields, children, manual_props, prop_gen_args, }: &Component, + ) -> Result { + self.write_component_name(name, prop_gen_args)?; + + // decide if we have any special optimizations + // Default with none, opt the cases in one-by-one + let mut opt_level = ShortOptimization::NoOpt; + + // check if we have a lot of attributes + let is_short_attr_list = self.is_short_fields(fields, manual_props); + let is_small_children = self.is_short_children(children).is_some(); + + // if we have few attributes and a lot of children, place the attrs on top + if is_short_attr_list && !is_small_children { + opt_level = ShortOptimization::PropsOnTop; + } + + // even if the attr is long, it should be put on one line + if !is_short_attr_list && (fields.len() <= 1 && manual_props.is_none()) { + if children.is_empty() { + opt_level = ShortOptimization::Oneliner; + } else { + opt_level = ShortOptimization::PropsOnTop; + } + } + + // if we have few children and few attributes, make it a one-liner + if is_short_attr_list && is_small_children { + opt_level = ShortOptimization::Oneliner; + } + + // If there's nothing at all, empty optimization + if fields.is_empty() && children.is_empty() { + opt_level = ShortOptimization::Empty; + } + + match opt_level { + ShortOptimization::Empty => {} + ShortOptimization::Oneliner => { + write!(self.buf, " ")?; + + self.write_component_fields(fields, manual_props, true)?; + + if !children.is_empty() && !fields.is_empty() { + write!(self.buf, ", ")?; + } + + for child in children { + self.write_ident(child)?; + } + + write!(self.buf, " ")?; + } + + ShortOptimization::PropsOnTop => { + write!(self.buf, " ")?; + self.write_component_fields(fields, manual_props, true)?; + + if !children.is_empty() && !fields.is_empty() { + write!(self.buf, ",")?; + } + + self.write_body_indented(children)?; + self.tabbed_line()?; + } + + ShortOptimization::NoOpt => { + self.write_component_fields(fields, manual_props, false)?; + self.write_body_indented(children)?; + self.tabbed_line()?; + } + } + + write!(self.buf, "}}")?; + Ok(()) + } + + fn write_component_name( + &mut self, + name: &syn::Path, + generics: &Option, ) -> Result { let mut name = name.to_token_stream().to_string(); name.retain(|c| !c.is_whitespace()); - self.tab()?; + write!(self.buf, "{name}")?; - if let Some(generics) = prop_gen_args { + if let Some(generics) = generics { let mut written = generics.to_token_stream().to_string(); written.retain(|c| !c.is_whitespace()); + write!(self.buf, "{}", written)?; } write!(self.buf, " {{")?; - if !body.is_empty() || !children.is_empty() { - self.new_line()?; - } + Ok(()) + } + + fn write_component_fields( + &mut self, + fields: &[ComponentField], + manual_props: &Option, + sameline: bool, + ) -> Result { + let mut field_iter = fields.iter().peekable(); + + while let Some(field) = field_iter.next() { + if !sameline { + self.indented_tabbed_line()?; + } - for field in body { - self.indented_tab()?; let name = &field.name; match &field.content { ContentField::ManExpr(exp) => { let out = prettyplease::unparse_expr(exp); - writeln!(self.buf, "{}: {},", name, out)?; + write!(self.buf, "{}: {}", name, out)?; } ContentField::Formatted(s) => { - writeln!(self.buf, "{}: \"{}\",", name, s.value())?; + write!(self.buf, "{}: \"{}\"", name, s.value())?; } ContentField::OnHandlerRaw(exp) => { let out = prettyplease::unparse_expr(exp); @@ -52,33 +158,71 @@ impl Buffer { self.indented_tab()?; write!(self.buf, "{}", line)?; } - writeln!(self.buf, ",")?; + } + } + + if field_iter.peek().is_some() || manual_props.is_some() { + write!(self.buf, ",")?; + + if sameline { + write!(self.buf, " ")?; } } } if let Some(exp) = manual_props { - self.indented_tab()?; - let out = prettyplease::unparse_expr(exp); - let mut lines = out.split('\n').peekable(); - let first = lines.next().unwrap(); - write!(self.buf, "..{}", first)?; - for line in lines { - self.new_line()?; - self.indented_tab()?; - write!(self.buf, "{}", line)?; + if !sameline { + self.indented_tabbed_line()?; } - self.new_line()?; + self.write_manual_props(exp)?; } - for child in children { - self.write_indented_ident(child)?; + Ok(()) + } + fn is_short_fields(&self, fields: &[ComponentField], manual_props: &Option) -> bool { + let attr_len = fields + .iter() + .map(|field| match &field.content { + ContentField::ManExpr(exp) => exp.to_token_stream().to_string().len(), + ContentField::Formatted(s) => s.value().len() , + ContentField::OnHandlerRaw(_) => 100000, + } + 10) + .sum::() + self.indent * 4; + + match manual_props { + Some(p) => { + let content = prettyplease::unparse_expr(p); + if content.len() + attr_len > 80 { + return false; + } + let mut lines = content.lines(); + lines.next().unwrap(); + + lines.next().is_none() + } + None => attr_len < 80, + } + } + + fn write_manual_props(&mut self, exp: &syn::Expr) -> Result { + /* + We want to normalize the expr to the appropriate indent level. + */ + + use syn::spanned::Spanned; + + let formatted = prettyplease::unparse_expr(exp); + + let mut lines = formatted.lines(); + + let first_line = lines.next().unwrap(); + + write!(self.buf, "..{first_line}")?; + for line in lines { + self.indented_tabbed_line()?; + write!(self.buf, "{line}")?; } - if !body.is_empty() || !children.is_empty() { - self.tab()?; - } - writeln!(self.buf, "}}")?; Ok(()) } } diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index a4af77249..ba8a17b5d 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -42,7 +42,7 @@ impl Buffer { // check if we have a lot of attributes let is_short_attr_list = is_short_attrs(attributes); - let is_small_children = self.is_short_children(children); + let is_small_children = self.is_short_children(children).is_some(); // if we have few attributes and a lot of children, place the attrs on top if is_short_attr_list && !is_small_children { @@ -201,36 +201,37 @@ impl Buffer { // check if the children are short enough to be on the same line // We don't have the notion of current line depth - each line tries to be < 80 total - fn is_short_children(&self, children: &[BodyNode]) -> bool { + // returns the total line length if it's short + // returns none if the length exceeds the limit + // I think this eventually becomes quadratic :( + pub fn is_short_children(&self, children: &[BodyNode]) -> Option { if children.is_empty() { // todo: allow elements with comments but no children // like div { /* comment */ } - return true; + return Some(0); } for child in children { 'line: for line in self.src[..child.span().start().line - 1].iter().rev() { - if line.trim().starts_with("//") { - return false; - } else if line.is_empty() { - continue; - } else { - break 'line; + match (line.trim().starts_with("//"), line.is_empty()) { + (true, _) => return None, + (_, true) => continue 'line, + _ => break 'line, } } } match children { - [BodyNode::Text(ref text)] => text.value().len() < 80, - [BodyNode::Element(ref el)] => { - extract_attr_len(&el.attributes) < 80 && self.is_short_children(&el.children) - } - _ => false, + [BodyNode::Text(ref text)] => Some(text.value().len()), + [BodyNode::Element(ref el)] => self + .is_short_children(&el.children) + .map(|f| f + extract_attr_len(&el.attributes)) + .and_then(|new_len| if new_len > 80 { None } else { Some(new_len) }), + _ => None, } } } fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { - let total_attr_len = extract_attr_len(attrs); - total_attr_len < 80 + extract_attr_len(attrs) < 80 } diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index 41a866ad9..d89a5cb39 100644 --- a/packages/autofmt/src/expr.rs +++ b/packages/autofmt/src/expr.rs @@ -19,7 +19,6 @@ impl Buffer { let num_spaces_desired = (self.indent * 4) as isize; let first = &self.src[start.line - 1]; - // let first = lines[start.line - 1]; let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize; let offset = num_spaces_real - num_spaces_desired; diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index 1b10c9799..025b3f59e 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -1,11 +1,5 @@ use dioxus_autofmt::*; use proc_macro2::TokenStream as TokenStream2; -use syn::{Attribute, Meta}; - -fn test_block(wrong: &str, right: &str) { - let formatted = fmt_block(wrong).unwrap(); - assert_eq!(formatted, right); -} fn print_block(wrong: &str) { let formatted = fmt_block(wrong).unwrap(); @@ -165,10 +159,12 @@ fn format_comments() { fn formats_component() { let block = r#" Component { - adsasd: "asd", // this is a comment + adsasd: "asd", + + // this is a comment onclick: move |_| { let blah = 120; - let blah = 120; + let blah = 122; }, } "#; @@ -178,6 +174,49 @@ fn formats_component() { println!("{formatted}"); } +#[test] +fn formats_component_complex() { + let block = r#" + div { + Component { + adsasd: "asd", + onclick: move |_| { + let a = a; + } + div { + "thing" + } + } + Component { + asdasd: "asdasd", + asdasd: "asdasdasdasdasdasdasdasdasdasd", + ..Props { + a: 10, + b: 20 + } + } + Component { + // + asdasd: "asdasd", + ..Props { + a: 10, + b: 20, + c: { + fn main() { + // + } + } + } + "content" + } + } +"#; + + let formatted = fmt_block(block).unwrap(); + + println!("{formatted}"); +} + #[test] fn formats_element() { let block = r#" @@ -239,6 +278,7 @@ fn formats_element_nested() { #[test] fn formats_element_props_on_top() { + // Gets compressed because there's no real comments let block = r#" h3 { class: "mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold", @@ -580,7 +620,7 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) #[test] fn empty_blocks() { - let mut src = r###" + let src = r###" pub fn Alert(cx: Scope) -> Element { cx.render(rsx! { div { } diff --git a/packages/rsx/src/component.rs b/packages/rsx/src/component.rs index 90248ad1f..f4f1a9bc6 100644 --- a/packages/rsx/src/component.rs +++ b/packages/rsx/src/component.rs @@ -27,7 +27,7 @@ use syn::{ pub struct Component { pub name: syn::Path, pub prop_gen_args: Option, - pub body: Vec, + pub fields: Vec, pub children: Vec, pub manual_props: Option, } @@ -105,7 +105,7 @@ impl Parse for Component { Ok(Self { name, prop_gen_args, - body, + fields: body, children, manual_props, }) @@ -124,7 +124,7 @@ impl ToTokens for Component { let mut toks = quote! { let mut __manual_props = #manual_props; }; - for field in &self.body { + for field in &self.fields { if field.name == "key" { has_key = Some(field); } else { @@ -147,7 +147,7 @@ impl ToTokens for Component { Some(gen_args) => quote! { fc_to_builder #gen_args(#name) }, None => quote! { fc_to_builder(#name) }, }; - for field in &self.body { + for field in &self.fields { match field.name.to_string().as_str() { "key" => { // From bd936d3af21f8f4f04df47db1c3ec48b625c61ad Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 17:29:10 -0400 Subject: [PATCH 10/13] feat: more cleanup --- packages/autofmt/src/component.rs | 20 +++++++++++++------- packages/autofmt/src/element.rs | 22 ++++++++++++++++++++++ packages/autofmt/tests/sink.rs | 2 +- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index de6e146bf..32c028247 100644 --- a/packages/autofmt/src/component.rs +++ b/packages/autofmt/src/component.rs @@ -36,7 +36,7 @@ impl Buffer { let mut opt_level = ShortOptimization::NoOpt; // check if we have a lot of attributes - let is_short_attr_list = self.is_short_fields(fields, manual_props); + let is_short_attr_list = self.is_short_fields(fields, manual_props).is_some(); let is_small_children = self.is_short_children(children).is_some(); // if we have few attributes and a lot of children, place the attrs on top @@ -179,7 +179,11 @@ impl Buffer { Ok(()) } - fn is_short_fields(&self, fields: &[ComponentField], manual_props: &Option) -> bool { + pub fn is_short_fields( + &self, + fields: &[ComponentField], + manual_props: &Option, + ) -> Option { let attr_len = fields .iter() .map(|field| match &field.content { @@ -193,14 +197,18 @@ impl Buffer { Some(p) => { let content = prettyplease::unparse_expr(p); if content.len() + attr_len > 80 { - return false; + return None; } let mut lines = content.lines(); lines.next().unwrap(); - lines.next().is_none() + if lines.next().is_none() { + Some(attr_len + content.len()) + } else { + None + } } - None => attr_len < 80, + None => Some(attr_len), } } @@ -209,8 +217,6 @@ impl Buffer { We want to normalize the expr to the appropriate indent level. */ - use syn::spanned::Spanned; - let formatted = prettyplease::unparse_expr(exp); let mut lines = formatted.lines(); diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index ba8a17b5d..8fe64c454 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -223,6 +223,28 @@ impl Buffer { match children { [BodyNode::Text(ref text)] => Some(text.value().len()), + [BodyNode::Component(ref comp)] => { + let is_short_child = self.is_short_children(&comp.children); + let is_short_attrs = self.is_short_fields(&comp.fields, &comp.manual_props); + + match (is_short_child, is_short_attrs) { + (Some(child_len), Some(attrs_len)) => Some(child_len + attrs_len), + (Some(child_len), None) => Some(child_len), + (None, Some(attrs_len)) => Some(attrs_len), + (None, None) => None, + } + } + [BodyNode::RawExpr(ref text)] => { + // TODO: let rawexprs to be inlined + // let span = syn::spanned::Spanned::span(&text); + // let (start, end) = (span.start(), span.end()); + // if start.line == end.line { + // Some(end.column - start.column) + // } else { + // None + // } + None + } [BodyNode::Element(ref el)] => self .is_short_children(&el.children) .map(|f| f + extract_attr_len(&el.attributes)) diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index 025b3f59e..ca1cb5c88 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -435,7 +435,7 @@ fn formats_complex() { icons::icon_0 {} } span { "{name}" } - children.is_some().then(|| rsx!{ + children.is_some().then(|| rsx! { span { class: "inline-block ml-auto hover:bg-gray-500", onclick: move |evt| { From 0aec3753718ca3d81167585aa7055b209b3d021d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 17:33:11 -0400 Subject: [PATCH 11/13] wip: clean up formatting package --- packages/autofmt/src/buffer.rs | 5 ----- packages/autofmt/src/element.rs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index 0884657ef..f962751f5 100644 --- a/packages/autofmt/src/buffer.rs +++ b/packages/autofmt/src/buffer.rs @@ -41,11 +41,6 @@ impl Buffer { writeln!(self.buf) } - pub fn write_indented_ident(&mut self, node: &BodyNode) -> Result { - self.write_ident(node)?; - Ok(()) - } - pub fn write_ident(&mut self, node: &BodyNode) -> Result { match node { BodyNode::Element(el) => self.write_element(el), diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 8fe64c454..41abf658a 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -234,7 +234,7 @@ impl Buffer { (None, None) => None, } } - [BodyNode::RawExpr(ref text)] => { + [BodyNode::RawExpr(ref _expr)] => { // TODO: let rawexprs to be inlined // let span = syn::spanned::Spanned::span(&text); // let (start, end) = (span.start(), span.end()); From 28a0c67275adda4c215b842736588b7393b787c8 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Thu, 30 Jun 2022 15:19:58 -0400 Subject: [PATCH 12/13] polish: clean up the examples and move autofmt sample into dedicated file --- examples/readme.rs | 8 - packages/autofmt/src/lib.rs | 6 +- packages/autofmt/tests/sample.rs | 23 ++ packages/autofmt/tests/samples/all.rs | 225 ++++++++++++ packages/autofmt/tests/sink.rs | 497 -------------------------- 5 files changed, 251 insertions(+), 508 deletions(-) create mode 100644 packages/autofmt/tests/sample.rs create mode 100644 packages/autofmt/tests/samples/all.rs diff --git a/examples/readme.rs b/examples/readme.rs index d7a2363c9..4f4250343 100644 --- a/examples/readme.rs +++ b/examples/readme.rs @@ -11,14 +11,6 @@ fn main() { fn app(cx: Scope) -> Element { let mut count = use_state(&cx, || 0); - let a = rsx! { - div { - - - key: "a" - } - }; - cx.render(rsx! { h1 { "High-Five counter: {count}" } button { onclick: move |_| count += 1, "Up high!" } diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index bae0b0bd1..5b95e989a 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -71,9 +71,9 @@ pub fn fmt_block(block: &str) -> Option { ..Buffer::default() }; - for node in syn::parse_str::(block).ok()?.roots { - buf.write_ident(&node).ok()?; - } + let body = syn::parse_str::(block).unwrap(); + + buf.write_body_indented(&body.roots).unwrap(); buf.consume() } diff --git a/packages/autofmt/tests/sample.rs b/packages/autofmt/tests/sample.rs new file mode 100644 index 000000000..6b6b7d244 --- /dev/null +++ b/packages/autofmt/tests/sample.rs @@ -0,0 +1,23 @@ +const SRC: &str = include_str!("./samples/all.rs"); + +fn body() -> &'static str { + &SRC[6..SRC.len() - 3] +} + +fn unindented_body() -> String { + body() + .lines() + .map(|line| match line.strip_prefix(" ") { + Some(line) => line, + None => line, + }) + .collect::>() + .join("\n") +} + +#[test] +fn way_and_back() { + let blocks = dioxus_autofmt::fmt_file(SRC).into_iter().next().unwrap(); + + println!("{}", blocks.formatted); +} diff --git a/packages/autofmt/tests/samples/all.rs b/packages/autofmt/tests/samples/all.rs new file mode 100644 index 000000000..b0899709c --- /dev/null +++ b/packages/autofmt/tests/samples/all.rs @@ -0,0 +1,225 @@ +rsx! { + // Simple case + div { key: "a", class: "ban", style: "color: red" } + + // Many attributes + div { + div { + key: "ddd", + class: "asd", + class: "asd", + class: "asd", + class: "asd", + class: "asd", + class: "asd", + blah: 123, + onclick: move |_| { + let blah = 120; + true + }, + onclick: move |_| { + let blah = 120; + true + }, + onclick: move |_| { + let blah = 120; + true + }, + onclick: move |_| { + let blah = 120; + true + }, + div { + div { "hi" } + h2 { class: "asd" } + } + Component {} + + // Generics + Component {} + } + } + + // Intertwined comments + div { adsasd: "asd", + h1 { "asd" } + div { + div { "hello" } + div { "goodbye" } + div { class: "broccoli", div { "hi" } } + div { class: "broccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccoli", + div { "hi" } + } + div { + class: "alksdjasd", + onclick: move |_| { + liberty!(); + }, + div { "hi" } + } + commented { + // is unparalled + class: "asdasd", + + // My genius + div { "hi" } + + div {} + } + } + } + + // Regular comments + div { adsasd: "asd", block: "asd", + // this is a comment + "hello" + + // this is a comment 1 + + // this is a comment 2 + "hello" + div { + // this is a comment + "hello" + } + div { key: "asd", class: "asdsda", + div {} + h3 {} + } + } + + // Components + Component { + adsasd: "asd", + + // this is a comment + onclick: move |_| { + let blah = 120; + let blah = 122; + } + } + + // Manual props + div { + Component { + adsasd: "asd", + onclick: move |_| { + let a = a; + }, + div { "thing" } + } + Component { + asdasd: "asdasd", + asdasd: "asdasdasdasdasdasdasdasdasdasd", + ..Props { + a: 10, + b: 20 + } + } + Component { + asdasd: "asdasd", + ..Props { + a: 10, + b: 20, + c: { + fn main() {} + }, + } + "content" + } + } + + // Long attributes + div { + a: "1234567891012345678910123456789101234567891012345678910123456789101234567891012345678910123456789101234567891012345678910", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123" + } + + // Short attributes + div { a: "123", a: "123", a: "123", a: "123", a: "123", a: "123", a: "123", a: "123", a: "123" } + + // Compression + h3 { class: "mb-2 text-xl font-bold", "Invite Member" } + a { class: "text-white", "Send invitation" } + + // Props on tops + h3 { class: "mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold", + "Invite Member" + } + + // No children, minimal props + img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" } + + // One level compression + div { + a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", "Send invitation" } + } + + // Tiny component + Component { a: 123 } + + // Expressions + ul { + div {} + (0..10).map(|f| rsx! { + li { "hi" } + }) + div {} + } + + // Complex nesting with components + button { + class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded", + onclick: move |evt| { + show_user_menu.set(!show_user_menu.get()); + evt.cancel_bubble(); + }, + onclick: move |evt| show_user_menu.set(!show_user_menu.get()), + span { class: "inline-block mr-4", icons::icon_14 {} } + span { "Settings" } + } + + // Complex nesting with handlers + li { + Link { class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", to: "{to}", + span { class: "inline-block mr-3", icons::icon_0 {} } + span { "{name}" } + children.is_some().then(|| rsx! { + span { + class: "inline-block ml-auto hover:bg-gray-500", + onclick: move |evt| { + // open.set(!open.get()); + evt.cancel_bubble(); + }, + icons::icon_8 {} + } + }) + } + div { class: "px-4", + is_current.then(|| rsx!{ children }) + } + } + + // No nesting + Component { + adsasd: "asd", + onclick: move |_| { + let blah = 120; + } + } + + // Component path + my::thing::Component { + adsasd: "asd", + onclick: move |_| { + let blah = 120; + } + } +} diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index ca1cb5c88..3654b794a 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -1,501 +1,4 @@ use dioxus_autofmt::*; -use proc_macro2::TokenStream as TokenStream2; - -fn print_block(wrong: &str) { - let formatted = fmt_block(wrong).unwrap(); - println!("{}", formatted); -} - -#[test] -fn formats_block() { - let block = r#" - div { - div { - class: "asd", - class: "asd",class: "asd",class: "asd",class: "asd",class: "asd", - key: "ddd", - onclick: move |_| { - let blah = 120; - true - }, - blah: 123, - onclick: move |_| { - let blah = 120; - true - }, - onclick: move |_| { - let blah = 120; - true - }, - onclick: move |_| { - let blah = 120; - true - }, - - div { - div { - "hi" - } - h2 { - class: "asd", - } - } - - Component {} - - Component {} - } - } - "#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn parse_comment() { - let block = r#" - div { - adsasd: "asd", // this is a comment - } - "#; - - let parsed: TokenStream2 = syn::parse_str(block).unwrap(); - - dbg!(parsed); -} - -#[test] -fn print_cases() { - print_block( - r#" - div { - adsasd: "asd", - h1 {"asd"} - div { - div { - "hello" - } - div { - "goodbye" - } - div { class: "broccoli", - div { - "hi" - } - } - div { class: "broccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccolibroccoli", - div { - "hi" - } - } - div { class: "alksdjasd", onclick: move |_| { - // hi! - liberty!(); - }, - div { - "hi" - } - } - - commented { - // is unparalled - class: "asdasd", - - // My genius - div { - "hi" - } - - div { - - } - } - } - } - "#, - ); -} - -#[test] -fn format_comments() { - let block = r#" - div { - adsasd: "asd", block: "asd", - - - // this is a comment - "hello" - - // this is a comment 1 - - // this is a comment 2 - "hello" - - div { - // this is a comment - "hello" - } - - div { - // empty space - } - - div { - key: "asd", class: "asdsda", - div {} - h3 {} - } - } - "#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_component() { - let block = r#" - Component { - adsasd: "asd", - - // this is a comment - onclick: move |_| { - let blah = 120; - let blah = 122; - }, - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_component_complex() { - let block = r#" - div { - Component { - adsasd: "asd", - onclick: move |_| { - let a = a; - } - div { - "thing" - } - } - Component { - asdasd: "asdasd", - asdasd: "asdasdasdasdasdasdasdasdasdasd", - ..Props { - a: 10, - b: 20 - } - } - Component { - // - asdasd: "asdasd", - ..Props { - a: 10, - b: 20, - c: { - fn main() { - // - } - } - } - "content" - } - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_element() { - let block = r#" - div { - a: "1234567891012345678910123456789101234567891012345678910123456789101234567891012345678910123456789101234567891012345678910", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_element_short() { - let block = r#" - div { - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - a: "123", - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_element_nested() { - let block = r#" - h3 { - class: "mb-2 text-xl font-bold", - "Invite Member" - } -"#; - - let formatted = fmt_block(block).unwrap(); - - assert_eq!( - formatted, - r#"h3 { class: "mb-2 text-xl font-bold", "Invite Member" }"# - ); -} - -#[test] -fn formats_element_props_on_top() { - // Gets compressed because there's no real comments - let block = r#" - h3 { - class: "mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold mb-2 text-xl font-bold", - - "Invite Member" - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_element_nested_no_trailing_tabs() { - let block = r#" - img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "", } -"#; - - let formatted = fmt_block(block).unwrap(); - - assert_eq!( - formatted, - r#"img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "" }"# - ); -} - -#[test] -fn formats_element_with_correct_indent() { - let block = r###" - div { - - a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", - "Send invitation" - } - } - -"###; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn small_elements_and_text_are_small() { - let block = r###" - a { class: "text-white", - "Send invitation" - } -"###; - - let formatted = fmt_block(block).unwrap(); - - assert_eq!(formatted, r#"a { class: "text-white", "Send invitation" }"#); -} - -#[test] -fn formats_component_man_props() { - let block = r#" - Component { - ..MyProps { - val: 123 - }, - adsasd: "asd", // this is a comment - onclick: move |_| { - let blah = 120; - }, - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_component_tiny() { - let block = r#" - Component { a: 123 - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_exprs() { - let block = r#" - ul { - div {} - (0..10).map(|f| rsx! { - li { "hi" } - }) - div {} - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_exprs_neg_indent() { - let block = r#" - ul { - (0..10).map(|f| rsx!{ - li { - "hi" - } - }) - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_exprs_handlers() { - let block = r#" - button { - class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded", - onclick: move |evt| { - show_user_menu.set(!show_user_menu.get()); evt.cancel_bubble(); }, - - onclick: move |evt| - - show_user_menu.set(!show_user_menu.get()), - span { class: "inline-block mr-4", - icons::icon_14 {} - } - span { "Settings" } - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_complex() { - let block = r#" - li { - Link { - class: "flex items-center pl-3 py-3 pr-4 {active_class} rounded", - to: "{to}", - span { class: "inline-block mr-3", - icons::icon_0 {} - } - span { "{name}" } - children.is_some().then(|| rsx! { - span { - class: "inline-block ml-auto hover:bg-gray-500", - onclick: move |evt| { - // open.set(!open.get()); - evt.cancel_bubble(); - }, - icons::icon_8 {} - } - }) - } - div { - class: "px-4", - is_current.then(|| rsx!{ children }) - // open.then(|| rsx!{ children }) - } - } -"#; - - let formatted = fmt_block(block).unwrap(); - - println!("{formatted}"); -} - -#[test] -fn formats_document() { - let block = r#" -rsx!{ - Component { - adsasd: "asd", // this is a comment - onclick: move |_| { - let blah = 120; - }, - } -} - - -"#; - - let formatted = fmt_file(block); - - println!("{formatted:?}"); -} - -#[test] -fn component_path_mod_style() { - let block = r#" -rsx!{ - my::thing::Component { - adsasd: "asd", // this is a comment - onclick: move |_| { - let blah = 120; - }, - } -} -"#; - - let formatted = fmt_file(block); - - println!("{formatted:?}"); -} #[test] fn formats_valid_rust_src() { From 30c1e702b37b14ed790139533100be3600d31687 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 2 Jul 2022 22:49:19 -0400 Subject: [PATCH 13/13] fix: fmt --- packages/rsx/src/node.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 73911c15b..f0b7c4222 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -4,7 +4,8 @@ use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens, TokenStreamExt}; use syn::{ parse::{Parse, ParseStream}, - token, Expr, LitStr, Result, spanned::Spanned, + spanned::Spanned, + token, Expr, LitStr, Result, }; /*