mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
feat: add fork of prettyplease for autoformatting
This commit is contained in:
parent
541d67dcfa
commit
5b9e34aadd
24 changed files with 5308 additions and 0 deletions
|
@ -67,6 +67,7 @@ members = [
|
|||
"packages/fermi",
|
||||
"packages/tui",
|
||||
"packages/liveview",
|
||||
"packages/autofmt",
|
||||
"packages/rsx",
|
||||
"packages/rsx_interpreter",
|
||||
"packages/native-core",
|
||||
|
|
14
packages/autofmt/Cargo.toml
Normal file
14
packages/autofmt/Cargo.toml
Normal file
|
@ -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"] }
|
5
packages/autofmt/README.md
Normal file
5
packages/autofmt/README.md
Normal file
|
@ -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.
|
413
packages/autofmt/src/lib.rs
Normal file
413
packages/autofmt/src/lib.rs
Normal file
|
@ -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<FormattedBlock> {
|
||||
let mut matches = levenshtein_search(b"rsx! {", contents.as_bytes()).peekable();
|
||||
|
||||
let mut cur_match: Option<Match> = 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<String> {
|
||||
let mut raw_lines = block.split('\n').collect::<Vec<_>>();
|
||||
|
||||
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::<usize>();
|
||||
|
||||
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<usize> {
|
||||
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
|
||||
}
|
376
packages/autofmt/src/prettyplease/algorithm.rs
Normal file
376
packages/autofmt/src/prettyplease/algorithm.rs
Normal file
|
@ -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<char>,
|
||||
pub post_break: Option<char>,
|
||||
pub no_break: Option<char>,
|
||||
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<BufEntry>,
|
||||
// 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<usize>,
|
||||
// Stack of blocks-in-progress being flushed by print
|
||||
print_stack: Vec<PrintFrame>,
|
||||
// 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;
|
||||
}
|
||||
}
|
208
packages/autofmt/src/prettyplease/attr.rs
Normal file
208
packages/autofmt/src/prettyplease/attr.rs
Normal file
|
@ -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<String> {
|
||||
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
|
||||
}
|
98
packages/autofmt/src/prettyplease/convenience.rs
Normal file
98
packages/autofmt/src/prettyplease/convenience.rs
Normal file
|
@ -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<S: Into<Cow<'static, str>>>(&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()
|
||||
});
|
||||
}
|
||||
}
|
95
packages/autofmt/src/prettyplease/data.rs
Normal file
95
packages/autofmt/src/prettyplease/data.rs
Normal file
|
@ -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(") ");
|
||||
}
|
||||
}
|
1028
packages/autofmt/src/prettyplease/expr.rs
Normal file
1028
packages/autofmt/src/prettyplease/expr.rs
Normal file
File diff suppressed because it is too large
Load diff
17
packages/autofmt/src/prettyplease/file.rs
Normal file
17
packages/autofmt/src/prettyplease/file.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
280
packages/autofmt/src/prettyplease/generics.rs
Normal file
280
packages/autofmt/src/prettyplease/generics.rs
Normal file
|
@ -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<WhereClause>) {
|
||||
let hardbreaks = true;
|
||||
let semi = false;
|
||||
self.where_clause_impl(where_clause, hardbreaks, semi);
|
||||
}
|
||||
|
||||
pub fn where_clause_semi(&mut self, where_clause: &Option<WhereClause>) {
|
||||
let hardbreaks = true;
|
||||
let semi = true;
|
||||
self.where_clause_impl(where_clause, hardbreaks, semi);
|
||||
}
|
||||
|
||||
pub fn where_clause_oneline(&mut self, where_clause: &Option<WhereClause>) {
|
||||
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<WhereClause>) {
|
||||
let hardbreaks = false;
|
||||
let semi = true;
|
||||
self.where_clause_impl(where_clause, hardbreaks, semi);
|
||||
}
|
||||
|
||||
fn where_clause_impl(
|
||||
&mut self,
|
||||
where_clause: &Option<WhereClause>,
|
||||
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);
|
||||
}
|
||||
}
|
757
packages/autofmt/src/prettyplease/item.rs
Normal file
757
packages/autofmt/src/prettyplease/item.rs
Normal file
|
@ -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");
|
||||
}
|
||||
}
|
46
packages/autofmt/src/prettyplease/iter.rs
Normal file
46
packages/autofmt/src/prettyplease/iter.rs
Normal file
|
@ -0,0 +1,46 @@
|
|||
use std::iter::Peekable;
|
||||
use std::ops::Deref;
|
||||
|
||||
pub struct Delimited<I: Iterator> {
|
||||
is_first: bool,
|
||||
iter: Peekable<I>,
|
||||
}
|
||||
|
||||
pub trait IterDelimited: Iterator + Sized {
|
||||
fn delimited(self) -> Delimited<Self> {
|
||||
Delimited {
|
||||
is_first: true,
|
||||
iter: self.peekable(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: Iterator> IterDelimited for I {}
|
||||
|
||||
pub struct IteratorItem<T> {
|
||||
value: T,
|
||||
pub is_first: bool,
|
||||
pub is_last: bool,
|
||||
}
|
||||
|
||||
impl<I: Iterator> Iterator for Delimited<I> {
|
||||
type Item = IteratorItem<I::Item>;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
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<T> Deref for IteratorItem<T> {
|
||||
type Target = T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.value
|
||||
}
|
||||
}
|
9
packages/autofmt/src/prettyplease/lifetime.rs
Normal file
9
packages/autofmt/src/prettyplease/lifetime.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
50
packages/autofmt/src/prettyplease/lit.rs
Normal file
50
packages/autofmt/src/prettyplease/lit.rs
Normal file
|
@ -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());
|
||||
}
|
||||
}
|
220
packages/autofmt/src/prettyplease/mac.rs
Normal file
220
packages/autofmt/src/prettyplease/mac.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
381
packages/autofmt/src/prettyplease/mod.rs
Normal file
381
packages/autofmt/src/prettyplease/mod.rs
Normal file
|
@ -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
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! **prettyplease::unparse** — a minimal `syn` syntax tree pretty-printer
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # 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.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # 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 | ❌ | ❌ | ✅ |
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # 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.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # 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.
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # 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<T, U> Into<U> for T where U: From<T> {
|
||||
//! 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);
|
||||
//! }
|
||||
//! ```
|
||||
//!
|
||||
//! <br>
|
||||
//!
|
||||
//! # 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()
|
||||
}
|
210
packages/autofmt/src/prettyplease/pat.rs
Normal file
210
packages/autofmt/src/prettyplease/pat.rs
Normal file
|
@ -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);
|
||||
}
|
||||
}
|
181
packages/autofmt/src/prettyplease/path.rs
Normal file
181
packages/autofmt/src/prettyplease/path.rs
Normal file
|
@ -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<QSelf>, 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);
|
||||
}
|
||||
}
|
||||
}
|
81
packages/autofmt/src/prettyplease/ring.rs
Normal file
81
packages/autofmt/src/prettyplease/ring.rs
Normal file
|
@ -0,0 +1,81 @@
|
|||
use std::collections::VecDeque;
|
||||
use std::ops::{Index, IndexMut};
|
||||
|
||||
pub struct RingBuffer<T> {
|
||||
data: VecDeque<T>,
|
||||
// Abstract index of data[0] in infinitely sized queue
|
||||
offset: usize,
|
||||
}
|
||||
|
||||
impl<T> RingBuffer<T> {
|
||||
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<T> Index<usize> for RingBuffer<T> {
|
||||
type Output = T;
|
||||
fn index(&self, index: usize) -> &Self::Output {
|
||||
&self.data[index.checked_sub(self.offset).unwrap()]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<usize> for RingBuffer<T> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut Self::Output {
|
||||
&mut self.data[index.checked_sub(self.offset).unwrap()]
|
||||
}
|
||||
}
|
85
packages/autofmt/src/prettyplease/stmt.rs
Normal file
85
packages/autofmt/src/prettyplease/stmt.rs
Normal file
|
@ -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,
|
||||
}
|
||||
}
|
80
packages/autofmt/src/prettyplease/token.rs
Normal file
80
packages/autofmt/src/prettyplease/token.rs
Normal file
|
@ -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<TokenTree> 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),
|
||||
}
|
||||
}
|
||||
}
|
202
packages/autofmt/src/prettyplease/ty.rs
Normal file
202
packages/autofmt/src/prettyplease/ty.rs
Normal file
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
471
packages/autofmt/tests/sink.rs
Normal file
471
packages/autofmt/tests/sink.rs
Normal file
|
@ -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);
|
||||
}
|
Loading…
Reference in a new issue