diff --git a/Cargo.lock b/Cargo.lock index 27029aa0830..1939e6dc7a4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2388,6 +2388,12 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "paste" +version = "1.0.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9423e2b32f7a043629287a536f21951e8c6a82482d0acb1eeebfc90bc2225b22" + [[package]] name = "peg" version = "0.7.0" @@ -3667,6 +3673,8 @@ version = "0.19.2" dependencies = [ "anyhow", "forc-util", + "paste", + "prettydiff 0.6.1", "ropey", "serde", "serde_ignored", diff --git a/sway-fmt-v2/Cargo.toml b/sway-fmt-v2/Cargo.toml index ebd3569d91f..bca4c4fd97d 100644 --- a/sway-fmt-v2/Cargo.toml +++ b/sway-fmt-v2/Cargo.toml @@ -21,3 +21,8 @@ sway-parse = { version = "0.19.2", path = "../sway-parse" } sway-types = { version = "0.19.2", path = "../sway-types" } thiserror = "1.0.30" toml = "0.5" + +[dev-dependencies] +forc-util = { path = "../forc-util" } +paste = "1.0" +prettydiff = "0.6" diff --git a/sway-fmt-v2/src/config/comments.rs b/sway-fmt-v2/src/config/comments.rs index b92b5aadcb0..cb8132afa87 100644 --- a/sway-fmt-v2/src/config/comments.rs +++ b/sway-fmt-v2/src/config/comments.rs @@ -3,7 +3,7 @@ use crate::constants::DEFAULT_MAX_COMMENT_WIDTH; use super::user_opts::CommentsOptions; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Comments { /// Break comments to fit on the line. pub wrap_comments: bool, diff --git a/sway-fmt-v2/src/config/heuristics.rs b/sway-fmt-v2/src/config/heuristics.rs index fa8673587ab..438a5903147 100644 --- a/sway-fmt-v2/src/config/heuristics.rs +++ b/sway-fmt-v2/src/config/heuristics.rs @@ -1,12 +1,12 @@ //! Configuration options related to heuristics. use crate::constants::{ - DEFAULT_ARRAY_WIDTH, DEFAULT_ATTR_FN_LIKE_WIDTH, DEFAULT_CHAIN_WIDTH, DEFAULT_FN_CALL_WIDTH, - DEFAULT_MAX_LINE_WIDTH, DEFAULT_SINGLE_LINE_IF_ELSE_WIDTH, DEFAULT_STRUCTURE_LIT_WIDTH, - DEFAULT_STRUCTURE_VAR_WIDTH, + DEFAULT_ATTR_FN_LIKE_WIDTH, DEFAULT_CHAIN_WIDTH, DEFAULT_COLLECTION_WIDTH, + DEFAULT_FN_CALL_WIDTH, DEFAULT_MAX_LINE_WIDTH, DEFAULT_SINGLE_LINE_IF_ELSE_WIDTH, + DEFAULT_STRUCTURE_LIT_WIDTH, DEFAULT_STRUCTURE_VAR_WIDTH, }; use serde::{Deserialize, Serialize}; -use super::{user_opts::HeuristicsOptions, whitespace::Whitespace}; +use super::user_opts::HeuristicsOptions; #[derive(Debug, Copy, Clone)] pub struct Heuristics { @@ -51,17 +51,17 @@ pub enum HeuristicsPreferences { } impl HeuristicsPreferences { - pub fn to_width_heuristics(self, ws_opts: &Whitespace) -> WidthHeuristics { + pub fn to_width_heuristics(self, max_width: usize) -> WidthHeuristics { match self { HeuristicsPreferences::Off => WidthHeuristics::off(), - HeuristicsPreferences::Max => WidthHeuristics::max(ws_opts.max_width), - HeuristicsPreferences::Scaled => WidthHeuristics::scaled(ws_opts.max_width), + HeuristicsPreferences::Max => WidthHeuristics::max(max_width), + HeuristicsPreferences::Scaled => WidthHeuristics::scaled(max_width), } } } /// 'small' heuristic values -#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Copy)] +#[derive(Deserialize, Serialize, Clone, Debug, PartialEq, Eq, Copy)] pub struct WidthHeuristics { // Maximum width of the args of a function call before falling back // to vertical formatting. @@ -72,12 +72,12 @@ pub struct WidthHeuristics { // Maximum width in the body of a user-defined structure literal before falling back to // vertical formatting. pub(crate) structure_lit_width: usize, - // Maximum width in the body of a user-defined structure field before falling back + // Maximum width of a user-defined structure field before falling back // to vertical formatting. pub(crate) structure_field_width: usize, - // Maximum width of an array literal before falling back to vertical + // Maximum width of a collection literal before falling back to vertical // formatting. - pub(crate) array_width: usize, + pub(crate) collection_width: usize, // Maximum length of a chain to fit on a single line. pub(crate) chain_width: usize, // Maximum line length for single line if-else expressions. A value @@ -85,12 +85,6 @@ pub struct WidthHeuristics { pub(crate) single_line_if_else_max_width: usize, } -impl std::fmt::Display for WidthHeuristics { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{:?}", self) - } -} - impl WidthHeuristics { /// Using this WidthHeuristics means we ignore heuristics. pub fn off() -> WidthHeuristics { @@ -99,7 +93,7 @@ impl WidthHeuristics { attr_fn_like_width: usize::max_value(), structure_lit_width: 0, structure_field_width: 0, - array_width: usize::max_value(), + collection_width: usize::max_value(), chain_width: usize::max_value(), single_line_if_else_max_width: 0, } @@ -111,7 +105,7 @@ impl WidthHeuristics { attr_fn_like_width: max_width, structure_lit_width: max_width, structure_field_width: max_width, - array_width: max_width, + collection_width: max_width, chain_width: max_width, single_line_if_else_max_width: max_width, } @@ -135,7 +129,7 @@ impl WidthHeuristics { as usize, structure_field_width: (DEFAULT_STRUCTURE_VAR_WIDTH as f32 * max_width_ratio).round() as usize, - array_width: (DEFAULT_ARRAY_WIDTH as f32 * max_width_ratio).round() as usize, + collection_width: (DEFAULT_COLLECTION_WIDTH as f32 * max_width_ratio).round() as usize, chain_width: (DEFAULT_CHAIN_WIDTH as f32 * max_width_ratio).round() as usize, single_line_if_else_max_width: (DEFAULT_SINGLE_LINE_IF_ELSE_WIDTH as f32 * max_width_ratio) @@ -144,10 +138,8 @@ impl WidthHeuristics { } } -impl ::std::str::FromStr for WidthHeuristics { - type Err = &'static str; - - fn from_str(_: &str) -> Result { - Err("WidthHeuristics is not parsable") +impl Default for WidthHeuristics { + fn default() -> Self { + Self::scaled(100) } } diff --git a/sway-fmt-v2/src/config/manifest.rs b/sway-fmt-v2/src/config/manifest.rs index e7b8b5b3182..19b3fc0949c 100644 --- a/sway-fmt-v2/src/config/manifest.rs +++ b/sway-fmt-v2/src/config/manifest.rs @@ -14,7 +14,7 @@ use crate::{ }; /// A finalized `swayfmt` config. -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Config { pub whitespace: Whitespace, pub imports: Imports, diff --git a/sway-fmt-v2/src/config/ordering.rs b/sway-fmt-v2/src/config/ordering.rs index 29e60ca86a3..210f6d89929 100644 --- a/sway-fmt-v2/src/config/ordering.rs +++ b/sway-fmt-v2/src/config/ordering.rs @@ -2,7 +2,7 @@ use super::user_opts::OrderingOptions; -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Ordering { /// Reorder import and extern crate statements alphabetically. pub reorder_imports: bool, diff --git a/sway-fmt-v2/src/config/user_def.rs b/sway-fmt-v2/src/config/user_def.rs index 66e698b2120..34318f38d70 100644 --- a/sway-fmt-v2/src/config/user_def.rs +++ b/sway-fmt-v2/src/config/user_def.rs @@ -5,7 +5,7 @@ use serde::{Deserialize, Serialize}; use super::user_opts::StructuresOptions; /// Styling preferences for user-defined structures like `struct`s or `enum`s. -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Structures { /// Align fields of user-defined structures if their diffs fit within threshold. pub field_alignment: FieldAlignment, diff --git a/sway-fmt-v2/src/constants.rs b/sway-fmt-v2/src/constants.rs index cf2deb6b049..90378495078 100644 --- a/sway-fmt-v2/src/constants.rs +++ b/sway-fmt-v2/src/constants.rs @@ -16,10 +16,10 @@ pub const DEFAULT_FN_CALL_WIDTH: usize = 60; pub const DEFAULT_ATTR_FN_LIKE_WIDTH: usize = 70; /// Default max width in the body of a user-defined structure literal before falling back to vertical formatting. pub const DEFAULT_STRUCTURE_LIT_WIDTH: usize = 18; -/// Default max width in the body of a user-defined structure field before falling back to vertical formatting. +/// Default max width of a user-defined structure field before falling back to vertical formatting. pub const DEFAULT_STRUCTURE_VAR_WIDTH: usize = 35; /// Default Maximum width of an array literal before falling back to vertical formatting. -pub const DEFAULT_ARRAY_WIDTH: usize = 60; +pub const DEFAULT_COLLECTION_WIDTH: usize = 60; /// Defalt width threshold for an array element to be considered short. pub const DEFAULT_SHORT_ARRAY_ELEM_WIDTH_THRESHOLD: usize = 10; /// Default max length of a chain to fit on a single line. diff --git a/sway-fmt-v2/src/fmt.rs b/sway-fmt-v2/src/fmt.rs index 590e67d98a3..a06b797a1eb 100644 --- a/sway-fmt-v2/src/fmt.rs +++ b/sway-fmt-v2/src/fmt.rs @@ -1,6 +1,6 @@ use crate::utils::{ - comments::handle_comments, indent_style::Shape, newline_style::apply_newline_style, - program_type::insert_program_type, + comments::handle_comments, newline_style::apply_newline_style, + program_type::insert_program_type, shape::Shape, }; use std::{path::Path, sync::Arc}; use sway_core::BuildConfig; @@ -10,7 +10,7 @@ pub use crate::{ error::{ConfigError, FormatterError}, }; -#[derive(Debug, Default)] +#[derive(Debug, Default, Clone)] pub struct Formatter { pub(crate) shape: Shape, pub config: Config, @@ -34,6 +34,7 @@ impl Formatter { Err(e) => return Err(e), }; let shape = Shape::default(); + Ok(Self { config, shape }) } pub fn format( @@ -42,23 +43,28 @@ impl Formatter { build_config: Option<&BuildConfig>, ) -> Result { let path = build_config.map(|build_config| build_config.canonical_root_module()); - let src_len = src.len(); + // update shape of the formatter with the width heuristics settings from the `Config` + self.shape.apply_width_heuristics( + self.config + .heuristics + .heuristics_pref + .to_width_heuristics(self.config.whitespace.max_width), + ); let module = sway_parse::parse_file(src.clone(), path.clone())?; - // Get parsed items - let items = &module.items; - // Get the program type (script, predicate, contract or library) - let program_type = &module.kind; // Formatted code will be pushed here with raw newline stlye. // Which means newlines are not converted into system-specific versions until `apply_newline_style()`. // Use the length of src as a hint of the memory size needed for `raw_formatted_code`, // which will reduce the number of reallocations - let mut raw_formatted_code = String::with_capacity(src_len); + let mut raw_formatted_code = String::with_capacity(src.len()); - // Insert program type to the formatted code. + // Get the program type (script, predicate, contract or library) + // and insert program type to the formatted code. + let program_type = &module.kind; insert_program_type(&mut raw_formatted_code, program_type)?; // Insert parsed & formatted items into the formatted code. + let items = &module.items; let mut iter = items.iter().peekable(); while let Some(item) = iter.next() { // format Annotated @@ -111,7 +117,7 @@ pub const TEST: u16 = 10;"#; } #[test] - fn test_struct_multiline_line_alignment() { + fn test_struct_alignment() { let sway_code_to_format = r#"contract; pub struct Foo { barbazfoo: u64, @@ -132,43 +138,7 @@ pub struct Foo { assert_eq!(correct_sway_code, formatted_sway_code) } #[test] - fn test_struct_single_line() { - let sway_code_to_format = r#"contract; -pub struct Foo { - bar: u64, - baz: bool, -} -"#; - let correct_sway_code = r#"contract; - -pub struct Foo { bar: u64, baz: bool }"#; - let mut formatter = Formatter::default(); - formatter.config.structures.small_structures_single_line = true; - formatter.config.whitespace.max_width = 300; - let formatted_sway_code = - Formatter::format(&mut formatter, Arc::from(sway_code_to_format), None).unwrap(); - assert_eq!(correct_sway_code, formatted_sway_code) - } - #[test] - fn test_enum_single_line() { - let sway_code_to_format = r#"contract; -pub enum Foo { - bar: u64, - baz: bool, -} -"#; - let correct_sway_code = r#"contract; - -pub enum Foo { bar: u64, baz: bool }"#; - let mut formatter = Formatter::default(); - formatter.config.structures.small_structures_single_line = true; - formatter.config.whitespace.max_width = 300; - let formatted_sway_code = - Formatter::format(&mut formatter, Arc::from(sway_code_to_format), None).unwrap(); - assert_eq!(correct_sway_code, formatted_sway_code) - } - #[test] - fn test_struct_multi_line() { + fn test_struct() { let sway_code_to_format = r#"contract; pub struct Foo { bar: u64, @@ -313,20 +283,33 @@ enum TestTy { Formatter::format(&mut formatter, Arc::from(sway_code_to_format), None).unwrap(); assert_eq!(correct_sway_code, formatted_sway_code); } + #[test] fn test_storage_without_alignment() { let sway_code_to_format = r#"contract; - -storage{foo:Test=Test{},bar -: - Test=Test{} -, baz: u64 } -"#; + struct Type1 { + foo: u64, + } + + struct Type2 { + bar: u64, + } + + storage { + var1: Type1=Type1{ foo: 8 }, + var2: Type2=Type2{ bar: 9 }, + } + "#; let correct_sway_code = r#"contract; +struct Type1 { + foo: u64, +} +struct Type2 { + bar: u64, +} storage { - foo: Test, - bar: Test, - baz: u64, + var1: Type1 = Type1 { foo: 8 }, + var2: Type2 = Type2 { bar: 9 }, }"#; let mut formatter = Formatter::default(); @@ -337,19 +320,30 @@ storage { #[test] fn test_storage_with_alignment() { let sway_code_to_format = r#"contract; +struct Type1 { + foo: u64, +} + +struct Type2 { + bar: u64, +} storage { - long_var_name: Type1=Type1{}, - var2: Type2=Type2{}, + long_var_name: Type1=Type1{ foo: 8 }, + var2: Type2=Type2{ bar: 9 }, } "#; let correct_sway_code = r#"contract; +struct Type1 { + foo : u64, +} +struct Type2 { + bar : u64, +} storage { - long_var_name : Type1 = Type1 { - }, - var2 : Type2 = Type2 { - }, + long_var_name : Type1 = Type1 { foo: 8 }, + var2 : Type2 = Type2 { bar: 9 }, }"#; let mut formatter = Formatter::default(); @@ -373,7 +367,15 @@ struct Type2 { } storage { - var1: Type1 = Type1 {x: 0,y: 0, }, + var1: Type1 = Type1 { + + + + x: 0, + + y: + 0, + }, var2: Type2 = Type2 { w: 0x0000000000000000000000000000000000000000000000000000000000000000,z: false, }, }"#; @@ -388,10 +390,7 @@ struct Type2 { z: bool, } storage { - var1: Type1 = Type1 { - x: 0, - y: 0, - }, + var1: Type1 = Type1 { x: 0, y: 0 }, var2: Type2 = Type2 { w: 0x0000000000000000000000000000000000000000000000000000000000000000, z: false, @@ -475,6 +474,88 @@ trait CompSciStudent: Programmer + Student { Formatter::format(&mut formatter, Arc::from(sway_code_to_format), None).unwrap(); assert_eq!(correct_sway_code, formatted_sway_code) } + #[test] + fn test_method_calls() { + let sway_code_to_format = r#"script; + +struct Opts { + gas: u64, + coins: u64, + id: ContractId, +} + +fn main( ) -> bool{ + let default_gas = 1_000_000_000_000 ;let fuelcoin_id = ~ContractId::from(0x018f59fe434b323a5054e7bb41de983f4926a3c5d3e4e1f9f33b5f0f0e611889); + + let balance_test_id = ~ContractId :: from( 0x597e5ddb1a6bec92a96a73e4f0bc6f6e3e7b21f5e03e1c812cd63cffac480463 ) ; + + let fuel_coin = abi( TestFuelCoin, fuelcoin_id.into( ) ) ; + + assert(fuelcoin_balance == 0); + + fuel_coin.mint { + gas: default_gas + } + + (11); + + fuelcoin_balance = balance_of(fuelcoin_id, fuelcoin_id); + assert( fuelcoin_balance == 11 ) ; + + fuel_coin.burn { + gas: default_gas + } + (7); + + fuelcoin_balance = balance_of(fuelcoin_id, fuelcoin_id); + assert(fuelcoin_balance == 4); + + fuel_coin.force_transfer { + gas: default_gas + } + (3, fuelcoin_id, balance_test_id); + + fuelcoin_balance = balance_of(fuelcoin_id, fuelcoin_id); + let balance_test_contract_balance = balance_of(fuelcoin_id, balance_test_id); + assert(fuelcoin_balance == 1); + assert(balance_test_contract_balance == 3); + + true +}"#; + + let correct_sway_code = r#"script; + +struct Opts { + gas: u64, + coins: u64, + id: ContractId, +} +fn main() -> bool { + let default_gas = 1_000_000_000_000; + let fuelcoin_id = ~ContractId::from(0x018f59fe434b323a5054e7bb41de983f4926a3c5d3e4e1f9f33b5f0f0e611889); + let balance_test_id = ~ContractId::from(0x597e5ddb1a6bec92a96a73e4f0bc6f6e3e7b21f5e03e1c812cd63cffac480463); + let fuel_coin = abi(TestFuelCoin, fuelcoin_id.into()); + assert(fuelcoin_balance == 0); + fuel_coin.mint { gas: default_gas }(11); + fuelcoin_balance = balance_of(fuelcoin_id, fuelcoin_id); + assert(fuelcoin_balance == 11); + fuel_coin.burn { gas: default_gas }(7); + fuelcoin_balance = balance_of(fuelcoin_id, fuelcoin_id); + assert(fuelcoin_balance == 4); + fuel_coin.force_transfer { gas: default_gas }(3, fuelcoin_id, balance_test_id); + fuelcoin_balance = balance_of(fuelcoin_id, fuelcoin_id); + let balance_test_contract_balance = balance_of(fuelcoin_id, balance_test_id); + assert(fuelcoin_balance == 1); + assert(balance_test_contract_balance == 3); + true +}"#; + let mut formatter = Formatter::default(); + formatter.config.structures.small_structures_single_line = true; + formatter.config.whitespace.max_width = 220; + let formatted_sway_code = + Formatter::format(&mut formatter, Arc::from(sway_code_to_format), None).unwrap(); + assert_eq!(correct_sway_code, formatted_sway_code) + } #[test] fn test_struct_comments() { @@ -618,21 +699,32 @@ pub const /* TEST: blah blah tests */ TEST: u16 = 10;"#; // Comment next to cons #[test] fn test_storage_comments() { let sway_code_to_format = r#"contract; + +struct Type1 { + foo: u64, +} +struct Type2 { + bar: u64, +} storage { // Testing a comment inside storage - long_var_name: Type1=Type1{}, + long_var_name: Type1=Type1{ foo: 8}, // Testing another comment - var2: Type2 = Type2{} // This is the last comment + var2: Type2 = Type2{bar:9} // This is the last comment }"#; let correct_sway_code = r#"contract; +struct Type1 { + foo: u64, +} +struct Type2 { + bar: u64, +} storage { // Testing a comment inside storage - long_var_name: Type1 = Type1 { - }, + long_var_name: Type1 = Type1 { foo: 8 }, // Testing another comment - var2: Type2 = Type2 { - }, // This is the last comment + var2: Type2 = Type2 { bar: 9 }, // This is the last comment }"#; let mut formatter = Formatter::default(); let formatted_sway_code = diff --git a/sway-fmt-v2/src/items/item_enum.rs b/sway-fmt-v2/src/items/item_enum.rs index b96e7830157..77b65136d49 100644 --- a/sway-fmt-v2/src/items/item_enum.rs +++ b/sway-fmt-v2/src/items/item_enum.rs @@ -4,17 +4,12 @@ use crate::{ utils::{ bracket::CurlyBrace, comments::{ByteSpan, LeafSpans}, - indent_style::LineStyle, - item::ItemLenChars, + shape::LineStyle, }, FormatterError, }; use std::fmt::Write; -use sway_ast::keywords::Token; -use sway_ast::{ - token::{Delimiter, PunctKind}, - ItemEnum, -}; +use sway_ast::{token::Delimiter, ItemEnum}; use sway_types::Spanned; impl Format for ItemEnum { @@ -23,182 +18,93 @@ impl Format for ItemEnum { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - formatter.shape.update_width(self.len_chars()?); - formatter.shape.get_line_style(&formatter.config); - - format_enum(self, formatted_code, formatter)?; - if formatter.shape.line_style == LineStyle::Inline { - formatter.shape.reset_line_style(); + formatter + .shape + .code_line + .update_line_style(LineStyle::Multiline); + // If there is a visibility token add it to the formatted_code with a ` ` after it. + if let Some(visibility) = &self.visibility { + write!(formatted_code, "{} ", visibility.span().as_str())?; + } + // Add enum token and name + write!( + formatted_code, + "{} {}", + self.enum_token.span().as_str(), + self.name.as_str() + )?; + // Format `GenericParams`, if any + if let Some(generics) = &self.generics { + generics.format(formatted_code, formatter)?; } - Ok(()) - } -} - -/// Format the enum if the multiline is passed as false enum will be formatted into a single line. -/// -/// ##examples -/// -/// (multiline : false): -/// -/// ```rust,ignore -/// enum Foo { bar: u64, baz: bool } -/// ``` -/// -/// (multiline : true): -/// ```rust,ignore -/// enum Foo { -/// bar: u64, -/// baz: bool, -/// } -/// ``` -fn format_enum( - item_enum: &ItemEnum, - formatted_code: &mut FormattedCode, - formatter: &mut Formatter, -) -> Result<(), FormatterError> { - // If there is a visibility token add it to the formatted_code with a ` ` after it. - if let Some(visibility) = &item_enum.visibility { - write!(formatted_code, "{} ", visibility.span().as_str())?; - } - // Add enum token and name - write!( - formatted_code, - "{} {}", - item_enum.enum_token.span().as_str(), - item_enum.name.as_str() - )?; - - // Format `GenericParams`, if any - if let Some(generics) = &item_enum.generics { - generics.format(formatted_code, formatter)?; - } - - let fields = item_enum.fields.clone().into_inner(); - - // Handle openning brace - ItemEnum::open_curly_brace(formatted_code, formatter)?; - match formatter.shape.line_style { - LineStyle::Multiline => { - writeln!(formatted_code)?; - // Determine alignment tactic - match formatter.config.structures.field_alignment { - FieldAlignment::AlignFields(enum_variant_align_threshold) => { - let value_pairs = fields.value_separator_pairs; - // In first iteration we are going to be collecting the lengths of the enum variants. - let variant_length: Vec = value_pairs - .iter() - .map(|(type_field, _)| type_field.name.as_str().len()) - .collect(); - - // Find the maximum length in the variant_length vector that is still smaller than enum_field_align_threshold. - let mut max_valid_variant_length = 0; - variant_length.iter().for_each(|length| { - if *length > max_valid_variant_length - && *length < enum_variant_align_threshold - { - max_valid_variant_length = *length; - } - }); - - let mut value_pairs_iter = value_pairs.iter().enumerate().peekable(); - for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - - // Add name - write!(formatted_code, "{}", type_field.name.as_str())?; - let current_variant_length = variant_length[var_index]; - if current_variant_length < max_valid_variant_length { - // We need to add alignment between : and ty - // max_valid_variant_length: the length of the variant that we are taking as a reference to align - // current_variant_length: the length of the current variant that we are trying to format - let mut required_alignment = - max_valid_variant_length - current_variant_length; - while required_alignment != 0 { - write!(formatted_code, " ")?; - required_alignment -= 1; - } - } - // Add `:`, ty & `CommaToken` - write!( - formatted_code, - " {} ", - type_field.colon_token.ident().as_str(), - )?; - type_field.ty.format(formatted_code, formatter)?; - if value_pairs_iter.peek().is_some() { - writeln!(formatted_code, "{}", comma_token.span().as_str())?; - } else if let Some(final_value) = &fields.final_value_opt { - write!(formatted_code, "{}", final_value.span().as_str())?; - } + let fields = self.fields.clone().into_inner(); + + // Handle openning brace + ItemEnum::open_curly_brace(formatted_code, formatter)?; + // Determine alignment tactic + match formatter.config.structures.field_alignment { + FieldAlignment::AlignFields(enum_variant_align_threshold) => { + writeln!(formatted_code)?; + let value_pairs = fields.value_separator_pairs; + // In first iteration we are going to be collecting the lengths of the enum variants. + let variant_length: Vec = value_pairs + .iter() + .map(|(type_field, _)| type_field.name.as_str().len()) + .collect(); + + // Find the maximum length in the variant_length vector that is still smaller than enum_field_align_threshold. + let mut max_valid_variant_length = 0; + variant_length.iter().for_each(|length| { + if *length > max_valid_variant_length && *length < enum_variant_align_threshold + { + max_valid_variant_length = *length; } - } - FieldAlignment::Off => { - let mut value_pairs_iter = fields.value_separator_pairs.iter().peekable(); - for (type_field, comma_token) in value_pairs_iter.clone() { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - // TypeField - type_field.format(formatted_code, formatter)?; - - if value_pairs_iter.peek().is_some() { - writeln!(formatted_code, "{}", comma_token.span().as_str())?; + }); + + let mut value_pairs_iter = value_pairs.iter().enumerate().peekable(); + for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() { + write!( + formatted_code, + "{}", + &formatter.shape.indent.to_string(&formatter.config)? + )?; + + // Add name + write!(formatted_code, "{}", type_field.name.as_str())?; + let current_variant_length = variant_length[var_index]; + if current_variant_length < max_valid_variant_length { + // We need to add alignment between : and ty + // max_valid_variant_length: the length of the variant that we are taking as a reference to align + // current_variant_length: the length of the current variant that we are trying to format + let mut required_alignment = + max_valid_variant_length - current_variant_length; + while required_alignment != 0 { + write!(formatted_code, " ")?; + required_alignment -= 1; } } - if let Some(final_value) = &fields.final_value_opt { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - final_value.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", PunctKind::Comma.as_char())?; + // Add `:`, ty & `CommaToken` + write!( + formatted_code, + " {} ", + type_field.colon_token.span().as_str(), + )?; + type_field.ty.format(formatted_code, formatter)?; + if value_pairs_iter.peek().is_some() { + writeln!(formatted_code, "{}", comma_token.span().as_str())?; + } else if let Some(final_value) = &fields.final_value_opt { + write!(formatted_code, "{}", final_value.span().as_str())?; } } } + FieldAlignment::Off => fields.format(formatted_code, formatter)?, } - LineStyle::Inline => { - // non-multiline formatting - write!(formatted_code, " ")?; - let mut value_pairs_iter = fields.value_separator_pairs.iter().peekable(); - for (type_field, comma_token) in value_pairs_iter.clone() { - type_field.format(formatted_code, formatter)?; - - if value_pairs_iter.peek().is_some() { - write!(formatted_code, "{} ", comma_token.span().as_str())?; - } - } - if let Some(final_value) = &fields.final_value_opt { - final_value.format(formatted_code, formatter)?; - write!(formatted_code, " ")?; - } else { - formatted_code.pop(); - formatted_code.pop(); - write!(formatted_code, " ")?; - } - } - } - // Handle closing brace - ItemEnum::close_curly_brace(formatted_code, formatter)?; + // Handle closing brace + ItemEnum::close_curly_brace(formatted_code, formatter)?; + formatter.shape.reset_line_settings(); - Ok(()) -} - -impl ItemLenChars for ItemEnum { - fn len_chars(&self) -> Result { - // Format to single line and return the length - let mut str_item = String::new(); - let mut formatter = Formatter::default(); - formatter.shape.line_style = LineStyle::Inline; - format_enum(self, &mut str_item, &mut formatter)?; - Ok(str_item.chars().count() as usize) + Ok(()) } } @@ -231,6 +137,7 @@ impl CurlyBrace for ItemEnum { write!(line, "{}", Delimiter::Brace.as_close_char())?; // If shape is becoming left-most aligned or - indent just have the defualt shape formatter.shape.block_unindent(&formatter.config); + Ok(()) } } diff --git a/sway-fmt-v2/src/items/item_fn.rs b/sway-fmt-v2/src/items/item_fn.rs index d8d0a3d4f2f..0c118a31ee5 100644 --- a/sway-fmt-v2/src/items/item_fn.rs +++ b/sway-fmt-v2/src/items/item_fn.rs @@ -8,7 +8,7 @@ use crate::{ }; use std::fmt::Write; use sway_ast::keywords::Token; -use sway_ast::{token::Delimiter, CodeBlockContents, FnArg, FnArgs, FnSignature, ItemFn}; +use sway_ast::{token::Delimiter, FnArg, FnArgs, FnSignature, ItemFn}; use sway_types::Spanned; impl Format for ItemFn { @@ -166,29 +166,6 @@ impl Parenthesis for FnSignature { } } -impl Format for CodeBlockContents { - fn format( - &self, - formatted_code: &mut FormattedCode, - formatter: &mut Formatter, - ) -> Result<(), FormatterError> { - for statement in self.statements.iter() { - statement.format(formatted_code, formatter)?; - } - if let Some(final_expr) = &self.final_expr_opt { - write!( - formatted_code, - "{}", - formatter.shape.indent.to_string(&formatter.config)? - )?; - final_expr.format(formatted_code, formatter)?; - writeln!(formatted_code)?; - } - - Ok(()) - } -} - // TODO: Use this in `Punctuated::format()` impl Format for FnArg { fn format( @@ -215,19 +192,6 @@ impl LeafSpans for ItemFn { } } -impl LeafSpans for CodeBlockContents { - fn leaf_spans(&self) -> Vec { - let mut collected_span = Vec::new(); - for statement in self.statements.iter() { - collected_span.append(&mut statement.leaf_spans()); - } - if let Some(expr) = &self.final_expr_opt { - collected_span.append(&mut expr.leaf_spans()); - } - collected_span - } -} - impl LeafSpans for FnSignature { fn leaf_spans(&self) -> Vec { let mut collected_spans = Vec::new(); diff --git a/sway-fmt-v2/src/items/item_storage.rs b/sway-fmt-v2/src/items/item_storage.rs index e514c02a8bf..0853d5e2e38 100644 --- a/sway-fmt-v2/src/items/item_storage.rs +++ b/sway-fmt-v2/src/items/item_storage.rs @@ -4,15 +4,13 @@ use crate::{ utils::{ bracket::CurlyBrace, comments::{ByteSpan, LeafSpans}, + shape::LineStyle, }, FormatterError, }; use std::fmt::Write; use sway_ast::keywords::Token; -use sway_ast::{ - token::{Delimiter, PunctKind}, - ItemStorage, StorageField, -}; +use sway_ast::{token::Delimiter, ItemStorage, StorageField}; use sway_types::Spanned; impl Format for ItemStorage { @@ -21,16 +19,21 @@ impl Format for ItemStorage { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { + formatter + .shape + .code_line + .update_line_style(LineStyle::Multiline); // Add storage token write!(formatted_code, "{}", self.storage_token.span().as_str())?; let fields = self.fields.clone().into_inner(); // Handle openning brace Self::open_curly_brace(formatted_code, formatter)?; - writeln!(formatted_code)?; + // Determine alignment tactic match formatter.config.structures.field_alignment { FieldAlignment::AlignFields(storage_field_align_threshold) => { + writeln!(formatted_code)?; let value_pairs = fields.value_separator_pairs; // In first iteration we are going to be collecting the lengths of the struct fields. let field_length: Vec = value_pairs @@ -90,34 +93,12 @@ impl Format for ItemStorage { } } } - FieldAlignment::Off => { - let mut value_pairs_iter = fields.value_separator_pairs.iter().peekable(); - for (storage_field, comma_token) in value_pairs_iter.clone() { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - // storage_field - storage_field.format(formatted_code, formatter)?; - - if value_pairs_iter.peek().is_some() { - writeln!(formatted_code, "{}", comma_token.ident().as_str())?; - } - } - if let Some(final_value) = &fields.final_value_opt { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - final_value.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", PunctKind::Comma.as_char())?; - } - } + FieldAlignment::Off => fields.format(formatted_code, formatter)?, } // Handle closing brace Self::close_curly_brace(formatted_code, formatter)?; + formatter.shape.reset_line_settings(); + Ok(()) } } @@ -152,6 +133,7 @@ impl CurlyBrace for ItemStorage { // shrink_left would return error if the current indentation level is becoming < 0, in that // case we should use the Shape::default() which has 0 indentation level. formatter.shape.block_unindent(&formatter.config); + Ok(()) } } diff --git a/sway-fmt-v2/src/items/item_struct.rs b/sway-fmt-v2/src/items/item_struct.rs index 9ccd3aefa2d..e35a67ad3af 100644 --- a/sway-fmt-v2/src/items/item_struct.rs +++ b/sway-fmt-v2/src/items/item_struct.rs @@ -4,17 +4,12 @@ use crate::{ utils::{ bracket::CurlyBrace, comments::{ByteSpan, LeafSpans}, - indent_style::LineStyle, - item::ItemLenChars, + shape::LineStyle, }, FormatterError, }; use std::fmt::Write; -use sway_ast::{ - keywords::Token, - token::{Delimiter, PunctKind}, - ItemStruct, -}; +use sway_ast::{token::Delimiter, ItemStruct}; use sway_types::Spanned; impl Format for ItemStruct { @@ -23,183 +18,95 @@ impl Format for ItemStruct { formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - formatter.shape.update_width(self.len_chars()?); - formatter.shape.get_line_style(&formatter.config); - - format_struct(self, formatted_code, formatter)?; - if formatter.shape.line_style == LineStyle::Inline { - formatter.shape.reset_line_style(); + formatter + .shape + .code_line + .update_line_style(LineStyle::Multiline); + // If there is a visibility token add it to the formatted_code with a ` ` after it. + if let Some(visibility) = &self.visibility { + write!(formatted_code, "{} ", visibility.span().as_str())?; + } + // Add struct token and name + write!( + formatted_code, + "{} {}", + self.struct_token.span().as_str(), + self.name.as_str(), + )?; + // Format `GenericParams`, if any + if let Some(generics) = &self.generics { + generics.format(formatted_code, formatter)?; } - Ok(()) - } -} - -/// Format the struct if the multiline is passed as false struct will be formatted into a single line. -/// -/// ## Examples -/// -/// (multiline : false): -/// -/// ```rust,ignore -/// struct Foo { bar: u64, baz: bool } -/// ``` -/// -/// (multiline : true): -/// -/// ```rust,ignore -/// struct Foo { -/// bar: u64, -/// baz: bool, -/// } -/// ``` -fn format_struct( - item_struct: &ItemStruct, - formatted_code: &mut FormattedCode, - formatter: &mut Formatter, -) -> Result<(), FormatterError> { - // If there is a visibility token add it to the formatted_code with a ` ` after it. - if let Some(visibility) = &item_struct.visibility { - write!(formatted_code, "{} ", visibility.span().as_str())?; - } - // Add struct token and name - write!( - formatted_code, - "{} {}", - item_struct.struct_token.span().as_str(), - item_struct.name.as_str(), - )?; - - // Format `GenericParams`, if any - if let Some(generics) = &item_struct.generics { - generics.format(formatted_code, formatter)?; - } - - let fields = item_struct.fields.clone().into_inner(); - - // Handle openning brace - ItemStruct::open_curly_brace(formatted_code, formatter)?; - match formatter.shape.line_style { - LineStyle::Multiline => { - writeln!(formatted_code)?; - // Determine alignment tactic - match formatter.config.structures.field_alignment { - FieldAlignment::AlignFields(enum_variant_align_threshold) => { - let value_pairs = fields.value_separator_pairs; - // In first iteration we are going to be collecting the lengths of the struct variants. - let variant_length: Vec = value_pairs - .iter() - .map(|(type_field, _)| type_field.name.as_str().len()) - .collect(); - - // Find the maximum length in the variant_length vector that is still smaller than struct_field_align_threshold. - let mut max_valid_variant_length = 0; - variant_length.iter().for_each(|length| { - if *length > max_valid_variant_length - && *length < enum_variant_align_threshold - { - max_valid_variant_length = *length; - } - }); - - let mut value_pairs_iter = value_pairs.iter().enumerate().peekable(); - for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - - // Add name - write!(formatted_code, "{}", type_field.name.as_str())?; - let current_variant_length = variant_length[var_index]; - if current_variant_length < max_valid_variant_length { - // We need to add alignment between : and ty - // max_valid_variant_length: the length of the variant that we are taking as a reference to align - // current_variant_length: the length of the current variant that we are trying to format - let mut required_alignment = - max_valid_variant_length - current_variant_length; - while required_alignment != 0 { - write!(formatted_code, " ")?; - required_alignment -= 1; - } - } - // Add `:`, ty & `CommaToken` - write!( - formatted_code, - " {} ", - type_field.colon_token.ident().as_str(), - )?; - type_field.ty.format(formatted_code, formatter)?; - if value_pairs_iter.peek().is_some() { - writeln!(formatted_code, "{}", comma_token.span().as_str())?; - } else if let Some(final_value) = &fields.final_value_opt { - write!(formatted_code, "{}", final_value.span().as_str())?; - } + let fields = self.fields.clone().into_inner(); + + // Handle openning brace + Self::open_curly_brace(formatted_code, formatter)?; + // Determine alignment tactic + match formatter.config.structures.field_alignment { + FieldAlignment::AlignFields(enum_variant_align_threshold) => { + writeln!(formatted_code)?; + let value_pairs = fields.value_separator_pairs; + // In first iteration we are going to be collecting the lengths of the struct variants. + let variant_length: Vec = value_pairs + .iter() + .map(|(type_field, _)| type_field.name.as_str().len()) + .collect(); + + // Find the maximum length in the variant_length vector that is still smaller than struct_field_align_threshold. + let mut max_valid_variant_length = 0; + variant_length.iter().for_each(|length| { + if *length > max_valid_variant_length && *length < enum_variant_align_threshold + { + max_valid_variant_length = *length; } - } - FieldAlignment::Off => { - let mut value_pairs_iter = fields.value_separator_pairs.iter().peekable(); - for (type_field, comma_token) in value_pairs_iter.clone() { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - // TypeField - type_field.format(formatted_code, formatter)?; - - if value_pairs_iter.peek().is_some() { - writeln!(formatted_code, "{}", comma_token.span().as_str())?; + }); + + let mut value_pairs_iter = value_pairs.iter().enumerate().peekable(); + for (var_index, (type_field, comma_token)) in value_pairs_iter.clone() { + write!( + formatted_code, + "{}", + &formatter.shape.indent.to_string(&formatter.config)? + )?; + + // Add name + write!(formatted_code, "{}", type_field.name.as_str())?; + let current_variant_length = variant_length[var_index]; + if current_variant_length < max_valid_variant_length { + // We need to add alignment between : and ty + // max_valid_variant_length: the length of the variant that we are taking as a reference to align + // current_variant_length: the length of the current variant that we are trying to format + let mut required_alignment = + max_valid_variant_length - current_variant_length; + while required_alignment != 0 { + write!(formatted_code, " ")?; + required_alignment -= 1; } } - if let Some(final_value) = &fields.final_value_opt { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - final_value.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", PunctKind::Comma.as_char())?; + // Add `:`, ty & `CommaToken` + write!( + formatted_code, + " {} ", + type_field.colon_token.span().as_str(), + )?; + type_field.ty.format(formatted_code, formatter)?; + if value_pairs_iter.peek().is_some() { + writeln!(formatted_code, "{}", comma_token.span().as_str())?; + } else if let Some(final_value) = &fields.final_value_opt { + write!(formatted_code, "{}", final_value.span().as_str())?; } } } - } - LineStyle::Inline => { - // non-multiline formatting - write!(formatted_code, " ")?; - let mut value_pairs_iter = fields.value_separator_pairs.iter().peekable(); - for (type_field, comma_token) in value_pairs_iter.clone() { - type_field.format(formatted_code, formatter)?; - - if value_pairs_iter.peek().is_some() { - write!(formatted_code, "{} ", comma_token.span().as_str())?; - } - } - if let Some(final_value) = &fields.final_value_opt { - final_value.format(formatted_code, formatter)?; - write!(formatted_code, " ")?; - } else { - formatted_code.pop(); - formatted_code.pop(); - write!(formatted_code, " ")?; + FieldAlignment::Off => { + fields.format(formatted_code, formatter)?; } } - } + // Handle closing brace + Self::close_curly_brace(formatted_code, formatter)?; + formatter.shape.reset_line_settings(); - // Handle closing brace - ItemStruct::close_curly_brace(formatted_code, formatter)?; - Ok(()) -} - -impl ItemLenChars for ItemStruct { - fn len_chars(&self) -> Result { - // Format to single line and return the length - let mut str_item = String::new(); - let mut formatter = Formatter::default(); - formatter.shape.line_style = LineStyle::Inline; - format_struct(self, &mut str_item, &mut formatter)?; - Ok(str_item.chars().count() as usize) + Ok(()) } } @@ -232,6 +139,7 @@ impl CurlyBrace for ItemStruct { write!(line, "{}", Delimiter::Brace.as_close_char())?; // If shape is becoming left-most alligned or - indent just have the defualt shape formatter.shape.block_unindent(&formatter.config); + Ok(()) } } diff --git a/sway-fmt-v2/src/lib.rs b/sway-fmt-v2/src/lib.rs index 14d0f4406f1..5807ef6f8e1 100644 --- a/sway-fmt-v2/src/lib.rs +++ b/sway-fmt-v2/src/lib.rs @@ -11,5 +11,5 @@ mod fmt; mod items; mod utils; -pub use crate::fmt::Formatter; +pub use crate::fmt::{Format, Formatter}; pub use error::FormatterError; diff --git a/sway-fmt-v2/src/utils.rs b/sway-fmt-v2/src/utils.rs index 510dd259629..1e8ba8a554e 100644 --- a/sway-fmt-v2/src/utils.rs +++ b/sway-fmt-v2/src/utils.rs @@ -3,7 +3,6 @@ pub(crate) mod bracket; pub(crate) mod comments; pub(crate) mod expr; pub(crate) mod generics; -pub(crate) mod indent_style; pub(crate) mod item; pub(crate) mod literal; pub(crate) mod newline_style; @@ -11,6 +10,7 @@ pub(crate) mod path; pub(crate) mod pattern; pub(crate) mod program_type; pub(crate) mod punctuated; +pub(crate) mod shape; pub(crate) mod statement; pub(crate) mod ty; pub(crate) mod where_clause; diff --git a/sway-fmt-v2/src/utils/expr.rs b/sway-fmt-v2/src/utils/expr.rs deleted file mode 100644 index 2afd334894e..00000000000 --- a/sway-fmt-v2/src/utils/expr.rs +++ /dev/null @@ -1,783 +0,0 @@ -use crate::{ - config::items::ItemBraceStyle, - fmt::*, - utils::comments::{ByteSpan, LeafSpans}, -}; -use std::{fmt::Write, vec}; -use sway_ast::{ - expr::asm::{AsmBlockContents, AsmFinalExpr}, - keywords::Token, - token::{Delimiter, PunctKind}, - AbiCastArgs, AsmBlock, AsmRegisterDeclaration, Assignable, Expr, ExprArrayDescriptor, - ExprStructField, ExprTupleDescriptor, IfCondition, IfExpr, Instruction, MatchBranch, - MatchBranchKind, -}; -use sway_types::Spanned; - -use super::bracket::CurlyBrace; - -// TODO: -impl Format for Expr { - fn format( - &self, - formatted_code: &mut FormattedCode, - formatter: &mut Formatter, - ) -> Result<(), FormatterError> { - match self { - // Self::Path(path) => {} - // Self::Literal(lit) => {} - // Self::AbiCast { abi_token, args } => {} - Self::Struct { path, fields } => { - path.format(formatted_code, formatter)?; - ExprStructField::open_curly_brace(formatted_code, formatter)?; - writeln!(formatted_code)?; - let fields = fields.clone().into_inner(); - let mut value_pairs_iter = fields.value_separator_pairs.iter().peekable(); - for (expr_struct_field, comma_token) in value_pairs_iter.clone() { - // TypeField - expr_struct_field.format(formatted_code, formatter)?; - - if value_pairs_iter.peek().is_some() { - writeln!(formatted_code, "{}", comma_token.ident().as_str())?; - } - } - if let Some(final_value) = &fields.final_value_opt { - write!( - formatted_code, - "{}", - &formatter.shape.indent.to_string(&formatter.config)? - )?; - final_value.format(formatted_code, formatter)?; - writeln!(formatted_code, "{}", PunctKind::Comma.as_char())?; - } - ExprStructField::close_curly_brace(formatted_code, formatter)?; - } - // Self::Tuple(tuple_descriptor) => {} - // Self::Parens(expr) => {} - // Self::Block(code_block) => {} - // Self::Array(array_descriptor) => {} - // Self::Asm(asm_block) => {} - // Self::Return { - // return_token, - // expr_opt, - // } => {} - // Self::If(if_expr) => {} - // Self::Match { - // match_token, - // value, - // branches, - // } => {} - // Self::While { - // while_token, - // condition, - // block, - // } => {} - // Self::FuncApp { func, args } => {} - // Self::Index { target, arg } => {} - // Self::MethodCall { - // target, - // dot_token, - // name, - // contract_args_opt, - // args, - // } => {} - // Self::FieldProjection { - // target, - // dot_token, - // name, - // } => {} - // Self::TupleFieldProjection { - // target, - // dot_token, - // field, - // field_span, - // } => {} - // Self::Ref { ref_token, expr } => {} - // Self::Deref { deref_token, expr } => {} - // Self::Not { bang_token, expr } => {} - // Self::Mul { - // lhs, - // star_token, - // rhs, - // } => {} - // Self::Div { - // lhs, - // forward_slash_token, - // rhs, - // } => {} - // Self::Modulo { - // lhs, - // percent_token, - // rhs, - // } => {} - // Self::Add { - // lhs, - // add_token, - // rhs, - // } => {} - // Self::Sub { - // lhs, - // sub_token, - // rhs, - // } => {} - // Self::Shl { - // lhs, - // shl_token, - // rhs, - // } => {} - // Self::Shr { - // lhs, - // shr_token, - // rhs, - // } => {} - // Self::BitAnd { - // lhs, - // ampersand_token, - // rhs, - // } => {} - // Self::BitXor { - // lhs, - // caret_token, - // rhs, - // } => {} - // Self::BitOr { - // lhs, - // pipe_token, - // rhs, - // } => {} - // Self::Equal { - // lhs, - // double_eq_token, - // rhs, - // } => {} - // Self::NotEqual { - // lhs, - // bang_eq_token, - // rhs, - // } => {} - // Self::LessThan { - // lhs, - // less_than_token, - // rhs, - // } => {} - // Self::GreaterThan { - // lhs, - // greater_than_token, - // rhs, - // } => {} - // Self::LessThanEq { - // lhs, - // less_than_eq_token, - // rhs, - // } => {} - // Self::GreaterThanEq { - // lhs, - // greater_than_eq_token, - // rhs, - // } => {} - // Self::LogicalAnd { - // lhs, - // double_ampersand_token, - // rhs, - // } => {} - // Self::LogicalOr { - // lhs, - // double_pipe_token, - // rhs, - // } => {} - // Self::Reassignment { - // assignable, - // reassignment_op, - // expr, - // } => {} - _ => write!(formatted_code, "{}", self.span().as_str())?, - } - - Ok(()) - } -} - -impl Format for ExprStructField { - fn format( - &self, - formatted_code: &mut FormattedCode, - formatter: &mut Formatter, - ) -> Result<(), FormatterError> { - write!( - formatted_code, - "{}{}", - formatter.shape.indent.to_string(&formatter.config)?, - self.field_name.span().as_str() - )?; - if let Some((colon_token, expr)) = &self.expr_opt { - write!(formatted_code, "{} ", colon_token.ident().as_str())?; - expr.format(formatted_code, formatter)?; - } - - Ok(()) - } -} - -impl CurlyBrace for ExprStructField { - fn open_curly_brace( - line: &mut String, - formatter: &mut Formatter, - ) -> Result<(), FormatterError> { - let brace_style = formatter.config.items.item_brace_style; - match brace_style { - ItemBraceStyle::AlwaysNextLine => { - // Add openning brace to the next line. - write!(line, "\n{}", Delimiter::Brace.as_open_char())?; - formatter.shape.block_indent(&formatter.config); - } - _ => { - // Add opening brace to the same line - write!(line, " {}", Delimiter::Brace.as_open_char())?; - formatter.shape.block_indent(&formatter.config); - } - } - - Ok(()) - } - - fn close_curly_brace( - line: &mut String, - formatter: &mut Formatter, - ) -> Result<(), FormatterError> { - // Unindent by one block - formatter.shape.block_unindent(&formatter.config); - write!( - line, - "{}{}", - formatter.shape.indent.to_string(&formatter.config)?, - Delimiter::Brace.as_close_char() - )?; - Ok(()) - } -} - -// TODO: Find a better way of handling Boxed version -impl LeafSpans for Box { - fn leaf_spans(&self) -> Vec { - visit_expr(self) - } -} - -impl LeafSpans for Expr { - fn leaf_spans(&self) -> Vec { - visit_expr(self) - } -} - -/// Collects various expr field's ByteSpans. -fn visit_expr(expr: &Expr) -> Vec { - match expr { - Expr::Path(path) => path.leaf_spans(), - Expr::Literal(literal) => literal.leaf_spans(), - Expr::AbiCast { abi_token, args } => { - let mut collected_spans = vec![ByteSpan::from(abi_token.span())]; - collected_spans.append(&mut args.leaf_spans()); - collected_spans - } - Expr::Struct { path, fields } => { - let mut collected_spans = path.leaf_spans(); - collected_spans.append(&mut fields.leaf_spans()); - collected_spans - } - Expr::Tuple(tuple) => tuple.leaf_spans(), - Expr::Parens(parens) => parens.leaf_spans(), - Expr::Block(block) => block.leaf_spans(), - Expr::Array(array) => array.leaf_spans(), - Expr::Asm(asm) => asm.leaf_spans(), - Expr::Return { - return_token, - expr_opt, - } => { - let mut collected_spans = vec![ByteSpan::from(return_token.span())]; - if let Some(expr) = expr_opt { - collected_spans.append(&mut expr.leaf_spans()); - } - collected_spans - } - Expr::If(expr_if) => expr_if.leaf_spans(), - Expr::Match { - match_token, - value, - branches, - } => { - let mut collected_spans = vec![ByteSpan::from(match_token.span())]; - collected_spans.append(&mut value.leaf_spans()); - collected_spans.append(&mut branches.leaf_spans()); - collected_spans - } - Expr::While { - while_token, - condition, - block, - } => { - let mut collected_spans = vec![ByteSpan::from(while_token.span())]; - collected_spans.append(&mut condition.leaf_spans()); - collected_spans.append(&mut block.leaf_spans()); - collected_spans - } - Expr::FuncApp { func, args } => { - let mut collected_spans = Vec::new(); - collected_spans.append(&mut func.leaf_spans()); - collected_spans.append(&mut args.leaf_spans()); - collected_spans - } - Expr::Index { target, arg } => { - let mut collected_spans = Vec::new(); - collected_spans.append(&mut target.leaf_spans()); - collected_spans.append(&mut arg.leaf_spans()); - collected_spans - } - Expr::MethodCall { - target, - dot_token, - name, - contract_args_opt, - args, - } => { - let mut collected_spans = Vec::new(); - collected_spans.append(&mut target.leaf_spans()); - collected_spans.push(ByteSpan::from(dot_token.span())); - collected_spans.push(ByteSpan::from(name.span())); - if let Some(contract_args) = contract_args_opt { - collected_spans.append(&mut contract_args.leaf_spans()); - } - collected_spans.append(&mut args.leaf_spans()); - collected_spans - } - Expr::FieldProjection { - target, - dot_token, - name, - } => { - let mut collected_spans = Vec::new(); - collected_spans.append(&mut target.leaf_spans()); - collected_spans.push(ByteSpan::from(dot_token.span())); - collected_spans.push(ByteSpan::from(name.span())); - collected_spans - } - Expr::TupleFieldProjection { - target, - dot_token, - field: _field, - field_span, - } => { - let mut collected_spans = Vec::new(); - collected_spans.append(&mut target.leaf_spans()); - collected_spans.push(ByteSpan::from(dot_token.span())); - collected_spans.push(ByteSpan::from(field_span.clone())); - collected_spans - } - Expr::Ref { ref_token, expr } => { - let mut collected_spans = vec![ByteSpan::from(ref_token.span())]; - collected_spans.append(&mut expr.leaf_spans()); - collected_spans - } - Expr::Deref { deref_token, expr } => { - let mut collected_spans = vec![ByteSpan::from(deref_token.span())]; - collected_spans.append(&mut expr.leaf_spans()); - collected_spans - } - Expr::Not { bang_token, expr } => { - let mut collected_spans = vec![ByteSpan::from(bang_token.span())]; - collected_spans.append(&mut expr.leaf_spans()); - collected_spans - } - Expr::Mul { - lhs, - star_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(star_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Div { - lhs, - forward_slash_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(forward_slash_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Modulo { - lhs, - percent_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(percent_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Add { - lhs, - add_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(add_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Sub { - lhs, - sub_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(sub_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Shl { - lhs, - shl_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(shl_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Shr { - lhs, - shr_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(shr_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::BitAnd { - lhs, - ampersand_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(ampersand_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::BitXor { - lhs, - caret_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(caret_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::BitOr { - lhs, - pipe_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(pipe_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Equal { - lhs, - double_eq_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(double_eq_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::NotEqual { - lhs, - bang_eq_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(bang_eq_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::LessThan { - lhs, - less_than_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(less_than_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::GreaterThan { - lhs, - greater_than_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(greater_than_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::LessThanEq { - lhs, - less_than_eq_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(less_than_eq_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::GreaterThanEq { - lhs, - greater_than_eq_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(greater_than_eq_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::LogicalAnd { - lhs, - double_ampersand_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(double_ampersand_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::LogicalOr { - lhs, - double_pipe_token, - rhs, - } => { - let mut collected_spans = lhs.leaf_spans(); - collected_spans.push(ByteSpan::from(double_pipe_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - Expr::Reassignment { - assignable, - reassignment_op, - expr, - } => { - let mut collected_spans = assignable.leaf_spans(); - collected_spans.push(ByteSpan::from(reassignment_op.span.clone())); - collected_spans.append(&mut expr.leaf_spans()); - collected_spans - } - } -} - -impl LeafSpans for AbiCastArgs { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = vec![ByteSpan::from(self.name.span())]; - collected_spans.push(ByteSpan::from(self.comma_token.span())); - collected_spans.append(&mut self.address.leaf_spans()); - collected_spans - } -} - -impl LeafSpans for ExprStructField { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = vec![ByteSpan::from(self.field_name.span())]; - if let Some(expr) = &self.expr_opt { - collected_spans.push(ByteSpan::from(expr.0.span())); - collected_spans.append(&mut expr.1.leaf_spans()); - } - collected_spans - } -} - -impl LeafSpans for ExprTupleDescriptor { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = Vec::new(); - if let ExprTupleDescriptor::Cons { - head, - comma_token, - tail, - } = self - { - collected_spans.append(&mut head.leaf_spans()); - collected_spans.push(ByteSpan::from(comma_token.span())); - collected_spans.append(&mut tail.leaf_spans()); - } - collected_spans - } -} - -impl LeafSpans for ExprArrayDescriptor { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = Vec::new(); - if let ExprArrayDescriptor::Repeat { - value, - semicolon_token, - length, - } = self - { - collected_spans.append(&mut value.leaf_spans()); - collected_spans.push(ByteSpan::from(semicolon_token.span())); - collected_spans.append(&mut length.leaf_spans()); - } - collected_spans - } -} - -impl LeafSpans for AsmBlock { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = vec![ByteSpan::from(self.asm_token.span())]; - collected_spans.append(&mut self.registers.leaf_spans()); - collected_spans.append(&mut self.contents.leaf_spans()); - collected_spans - } -} - -impl LeafSpans for AsmRegisterDeclaration { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = vec![ByteSpan::from(self.register.span())]; - if let Some(value) = &self.value_opt { - collected_spans.append(&mut value.leaf_spans()); - } - collected_spans - } -} - -impl LeafSpans for AsmBlockContents { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = Vec::new(); - for instruction in &self.instructions { - collected_spans.append(&mut instruction.leaf_spans()); - } - collected_spans - } -} - -impl LeafSpans for Instruction { - fn leaf_spans(&self) -> Vec { - // Visit instructions as a whole unit, meaning we cannot insert comments inside an instruction. - vec![ByteSpan::from(self.span())] - } -} - -impl LeafSpans for AsmFinalExpr { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = vec![ByteSpan::from(self.register.span())]; - if let Some(ty) = &self.ty_opt { - collected_spans.append(&mut ty.leaf_spans()); - } - collected_spans - } -} - -impl LeafSpans for IfExpr { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = vec![ByteSpan::from(self.if_token.span())]; - collected_spans.append(&mut self.condition.leaf_spans()); - collected_spans.append(&mut self.then_block.leaf_spans()); - if let Some(else_block) = &self.else_opt { - collected_spans.push(ByteSpan::from(else_block.0.span())); - let mut else_body_spans = match &else_block.1 { - std::ops::ControlFlow::Continue(if_expr) => if_expr.leaf_spans(), - std::ops::ControlFlow::Break(else_body) => else_body.leaf_spans(), - }; - collected_spans.append(&mut else_body_spans); - } - collected_spans - } -} - -impl LeafSpans for IfCondition { - fn leaf_spans(&self) -> Vec { - match self { - IfCondition::Expr(expr) => expr.leaf_spans(), - IfCondition::Let { - let_token, - lhs, - eq_token, - rhs, - } => { - let mut collected_spans = vec![ByteSpan::from(let_token.span())]; - collected_spans.append(&mut lhs.leaf_spans()); - collected_spans.push(ByteSpan::from(eq_token.span())); - collected_spans.append(&mut rhs.leaf_spans()); - collected_spans - } - } - } -} - -impl LeafSpans for MatchBranch { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = Vec::new(); - collected_spans.append(&mut self.pattern.leaf_spans()); - collected_spans.push(ByteSpan::from(self.fat_right_arrow_token.span())); - collected_spans.append(&mut self.kind.leaf_spans()); - collected_spans - } -} - -impl LeafSpans for MatchBranchKind { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = Vec::new(); - match self { - MatchBranchKind::Block { - block, - comma_token_opt, - } => { - collected_spans.append(&mut block.leaf_spans()); - if let Some(comma_token) = comma_token_opt { - collected_spans.push(ByteSpan::from(comma_token.span())); - } - } - MatchBranchKind::Expr { expr, comma_token } => { - collected_spans.append(&mut expr.leaf_spans()); - collected_spans.push(ByteSpan::from(comma_token.span())); - } - }; - collected_spans - } -} - -impl LeafSpans for Assignable { - fn leaf_spans(&self) -> Vec { - let mut collected_spans = Vec::new(); - match self { - Assignable::Var(var) => collected_spans.push(ByteSpan::from(var.span())), - Assignable::Index { target, arg } => { - collected_spans.append(&mut target.leaf_spans()); - collected_spans.append(&mut arg.leaf_spans()); - } - Assignable::FieldProjection { - target, - dot_token, - name, - } => { - collected_spans.append(&mut target.leaf_spans()); - collected_spans.push(ByteSpan::from(dot_token.span())); - collected_spans.push(ByteSpan::from(name.span())); - } - Assignable::TupleFieldProjection { - target, - dot_token, - field: _field, - field_span, - } => { - collected_spans.append(&mut target.leaf_spans()); - collected_spans.push(ByteSpan::from(dot_token.span())); - collected_spans.push(ByteSpan::from(field_span.clone())); - } - }; - collected_spans - } -} diff --git a/sway-fmt-v2/src/utils/expr/abi_cast.rs b/sway-fmt-v2/src/utils/expr/abi_cast.rs new file mode 100644 index 00000000000..a9cb5eee8a9 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/abi_cast.rs @@ -0,0 +1,53 @@ +use crate::{ + fmt::*, + utils::{ + bracket::Parenthesis, + comments::{ByteSpan, LeafSpans}, + }, +}; +use std::fmt::Write; +use sway_ast::{token::Delimiter, AbiCastArgs}; +use sway_types::Spanned; + +impl Format for AbiCastArgs { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + Self::open_parenthesis(formatted_code, formatter)?; + self.name.format(formatted_code, formatter)?; + write!(formatted_code, "{} ", self.comma_token.span().as_str())?; + self.address.format(formatted_code, formatter)?; + Self::close_parenthesis(formatted_code, formatter)?; + + Ok(()) + } +} + +impl Parenthesis for AbiCastArgs { + fn open_parenthesis( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Parenthesis.as_open_char())?; + Ok(()) + } + + fn close_parenthesis( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Parenthesis.as_close_char())?; + Ok(()) + } +} + +impl LeafSpans for AbiCastArgs { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = vec![ByteSpan::from(self.name.span())]; + collected_spans.push(ByteSpan::from(self.comma_token.span())); + collected_spans.append(&mut self.address.leaf_spans()); + collected_spans + } +} diff --git a/sway-fmt-v2/src/utils/expr/asm_block.rs b/sway-fmt-v2/src/utils/expr/asm_block.rs new file mode 100644 index 00000000000..5e754ffd4a1 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/asm_block.rs @@ -0,0 +1,175 @@ +use crate::{ + fmt::*, + utils::{ + bracket::{Parenthesis, SquareBracket}, + comments::{ByteSpan, LeafSpans}, + }, +}; +use std::fmt::Write; +use sway_ast::{ + expr::asm::{AsmBlock, AsmBlockContents, AsmFinalExpr, AsmRegisterDeclaration}, + token::Delimiter, + Instruction, +}; +use sway_types::Spanned; + +impl Format for AsmBlock { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(formatted_code, "{} ", self.asm_token.span().as_str())?; + Self::open_parenthesis(formatted_code, formatter)?; + self.registers + .clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_parenthesis(formatted_code, formatter)?; + Self::open_square_bracket(formatted_code, formatter)?; + self.contents + .clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_square_bracket(formatted_code, formatter)?; + + Ok(()) + } +} + +impl Parenthesis for AsmBlock { + fn open_parenthesis( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Parenthesis.as_open_char())?; + Ok(()) + } + fn close_parenthesis( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Parenthesis.as_close_char())?; + Ok(()) + } +} + +impl SquareBracket for AsmBlock { + fn open_square_bracket( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Bracket.as_open_char())?; + Ok(()) + } + fn close_square_bracket( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Bracket.as_close_char())?; + Ok(()) + } +} + +impl Format for AsmRegisterDeclaration { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + self.register.format(formatted_code, formatter)?; + if let Some(value) = &self.value_opt { + write!(formatted_code, "{} ", value.0.span().as_str())?; + value.1.format(formatted_code, formatter)?; + } + + Ok(()) + } +} + +impl Format for AsmBlockContents { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + for pair in self.instructions.iter() { + writeln!( + formatted_code, + "{}{}", + pair.0.span().as_str(), + pair.1.span().as_str() + )?; + } + if let Some(final_expr) = &self.final_expr_opt { + final_expr.format(formatted_code, formatter)?; + } + + Ok(()) + } +} + +impl Format for AsmFinalExpr { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + self.register.format(formatted_code, formatter)?; + if let Some(value) = &self.ty_opt { + write!(formatted_code, "{} ", value.0.span().as_str())?; + value.1.format(formatted_code, formatter)?; + } + + Ok(()) + } +} + +impl LeafSpans for AsmBlock { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = vec![ByteSpan::from(self.asm_token.span())]; + collected_spans.append(&mut self.registers.leaf_spans()); + collected_spans.append(&mut self.contents.leaf_spans()); + collected_spans + } +} + +impl LeafSpans for AsmRegisterDeclaration { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = vec![ByteSpan::from(self.register.span())]; + if let Some(value) = &self.value_opt { + collected_spans.append(&mut value.leaf_spans()); + // TODO: determine if we are allowing comments between `:` and expr + } + collected_spans + } +} + +impl LeafSpans for AsmBlockContents { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = Vec::new(); + for instruction in &self.instructions { + collected_spans.append(&mut instruction.leaf_spans()); + // TODO: probably we shouldn't allow for comments in between the instruction and comma since it may/will result in build failure after formatting + } + collected_spans + } +} + +impl LeafSpans for Instruction { + fn leaf_spans(&self) -> Vec { + // Visit instructions as a whole unit, meaning we cannot insert comments inside an instruction. + vec![ByteSpan::from(self.span())] + } +} + +impl LeafSpans for AsmFinalExpr { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = vec![ByteSpan::from(self.register.span())]; + if let Some(ty) = &self.ty_opt { + collected_spans.append(&mut ty.leaf_spans()); + // TODO: determine if we are allowing comments between `:` and ty + } + collected_spans + } +} diff --git a/sway-fmt-v2/src/utils/expr/assignable.rs b/sway-fmt-v2/src/utils/expr/assignable.rs new file mode 100644 index 00000000000..abf2e5237f1 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/assignable.rs @@ -0,0 +1,103 @@ +use crate::{ + fmt::*, + utils::{ + bracket::SquareBracket, + comments::{ByteSpan, LeafSpans}, + }, +}; +use std::fmt::Write; +use sway_ast::{expr::ReassignmentOp, Assignable, Expr}; +use sway_types::Spanned; + +impl Format for Assignable { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Assignable::Var(name) => { + write!( + formatted_code, + "{}", + formatter.shape.indent.to_string(&formatter.config)? + )?; + name.format(formatted_code, formatter)?; + } + Assignable::Index { target, arg } => { + target.format(formatted_code, formatter)?; + Expr::open_square_bracket(formatted_code, formatter)?; + arg.clone().into_inner().format(formatted_code, formatter)?; + Expr::close_square_bracket(formatted_code, formatter)?; + } + Assignable::FieldProjection { + target, + dot_token, + name, + } => { + target.format(formatted_code, formatter)?; + write!(formatted_code, "{}", dot_token.span().as_str())?; + name.format(formatted_code, formatter)?; + } + Assignable::TupleFieldProjection { + target, + dot_token, + field: _, + field_span, + } => { + target.format(formatted_code, formatter)?; + write!( + formatted_code, + "{}{}", + dot_token.span().as_str(), + field_span.as_str() + )?; + } + } + Ok(()) + } +} + +impl Format for ReassignmentOp { + fn format( + &self, + formatted_code: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(formatted_code, " {} ", self.span.as_str())?; + Ok(()) + } +} + +impl LeafSpans for Assignable { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = Vec::new(); + match self { + Assignable::Var(var) => collected_spans.push(ByteSpan::from(var.span())), + Assignable::Index { target, arg } => { + collected_spans.append(&mut target.leaf_spans()); + collected_spans.append(&mut arg.leaf_spans()); + } + Assignable::FieldProjection { + target, + dot_token, + name, + } => { + collected_spans.append(&mut target.leaf_spans()); + collected_spans.push(ByteSpan::from(dot_token.span())); + collected_spans.push(ByteSpan::from(name.span())); + } + Assignable::TupleFieldProjection { + target, + dot_token, + field: _field, + field_span, + } => { + collected_spans.append(&mut target.leaf_spans()); + collected_spans.push(ByteSpan::from(dot_token.span())); + collected_spans.push(ByteSpan::from(field_span.clone())); + } + }; + collected_spans + } +} diff --git a/sway-fmt-v2/src/utils/expr/code_block.rs b/sway-fmt-v2/src/utils/expr/code_block.rs new file mode 100644 index 00000000000..829e3653da2 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/code_block.rs @@ -0,0 +1,83 @@ +use crate::{ + config::items::ItemBraceStyle, + fmt::*, + utils::{ + bracket::CurlyBrace, + comments::{ByteSpan, LeafSpans}, + }, +}; +use std::fmt::Write; +use sway_ast::{token::Delimiter, CodeBlockContents}; + +impl Format for CodeBlockContents { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + for statement in self.statements.iter() { + statement.format(formatted_code, formatter)?; + } + if let Some(final_expr) = &self.final_expr_opt { + write!( + formatted_code, + "{}", + formatter.shape.indent.to_string(&formatter.config)? + )?; + final_expr.format(formatted_code, formatter)?; + writeln!(formatted_code)?; + } + + Ok(()) + } +} + +impl CurlyBrace for CodeBlockContents { + fn open_curly_brace( + line: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + let brace_style = formatter.config.items.item_brace_style; + match brace_style { + ItemBraceStyle::AlwaysNextLine => { + // Add openning brace to the next line. + write!(line, "\n{}", Delimiter::Brace.as_open_char())?; + formatter.shape.block_indent(&formatter.config); + } + _ => { + // Add opening brace to the same line + write!(line, " {}", Delimiter::Brace.as_open_char())?; + formatter.shape.block_indent(&formatter.config); + } + } + + Ok(()) + } + fn close_curly_brace( + line: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + // Unindent by one block + formatter.shape.block_unindent(&formatter.config); + write!( + line, + "{}{}", + formatter.shape.indent.to_string(&formatter.config)?, + Delimiter::Brace.as_close_char() + )?; + Ok(()) + } +} + +impl LeafSpans for CodeBlockContents { + fn leaf_spans(&self) -> Vec { + let mut collected_span = Vec::new(); + for statement in self.statements.iter() { + collected_span.append(&mut statement.leaf_spans()); + } + if let Some(expr) = &self.final_expr_opt { + collected_span.append(&mut expr.leaf_spans()); + } + collected_span + } +} diff --git a/sway-fmt-v2/src/utils/expr/collections.rs b/sway-fmt-v2/src/utils/expr/collections.rs new file mode 100644 index 00000000000..1c9fd6f2b7e --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/collections.rs @@ -0,0 +1,181 @@ +use crate::{ + fmt::*, + utils::{ + bracket::{Parenthesis, SquareBracket}, + comments::{ByteSpan, LeafSpans}, + shape::LineStyle, + }, +}; +use std::fmt::Write; +use sway_ast::{token::Delimiter, ExprArrayDescriptor, ExprTupleDescriptor}; +use sway_types::Spanned; + +impl Format for ExprTupleDescriptor { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Self::Nil => {} + Self::Cons { + head, + comma_token, + tail, + } => { + Self::open_parenthesis(formatted_code, formatter)?; + match formatter.shape.code_line.line_style { + LineStyle::Multiline => { + write!( + formatted_code, + "{}", + formatter.shape.indent.to_string(&formatter.config)? + )?; + head.format(formatted_code, formatter)?; + write!(formatted_code, "{}", comma_token.span().as_str())?; + tail.format(formatted_code, formatter)?; + } + _ => { + head.format(formatted_code, formatter)?; + write!(formatted_code, "{} ", comma_token.span().as_str())?; + tail.format(formatted_code, formatter)?; + } + } + Self::close_parenthesis(formatted_code, formatter)?; + } + } + + Ok(()) + } +} + +impl Parenthesis for ExprTupleDescriptor { + fn open_parenthesis( + line: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match formatter.shape.code_line.line_style { + LineStyle::Multiline => { + formatter.shape.block_indent(&formatter.config); + writeln!(line, "{}", Delimiter::Parenthesis.as_open_char())?; + } + _ => write!(line, "{}", Delimiter::Parenthesis.as_open_char())?, + } + + Ok(()) + } + fn close_parenthesis( + line: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match formatter.shape.code_line.line_style { + LineStyle::Multiline => { + formatter.shape.block_unindent(&formatter.config); + write!( + line, + "{}{}", + formatter.shape.indent.to_string(&formatter.config)?, + Delimiter::Parenthesis.as_close_char() + )?; + } + _ => write!(line, "{}", Delimiter::Parenthesis.as_close_char())?, + } + + Ok(()) + } +} + +impl Format for ExprArrayDescriptor { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Self::Sequence(punct_expr) => { + punct_expr.format(formatted_code, formatter)?; + } + Self::Repeat { + value, + semicolon_token, + length, + } => { + value.format(formatted_code, formatter)?; + write!(formatted_code, "{}", semicolon_token.span().as_str())?; + length.format(formatted_code, formatter)?; + } + } + + Ok(()) + } +} + +impl SquareBracket for ExprArrayDescriptor { + fn open_square_bracket( + line: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match formatter.shape.code_line.line_style { + LineStyle::Multiline => { + formatter.shape.block_indent(&formatter.config); + writeln!(line, "{}", Delimiter::Bracket.as_open_char())?; + } + _ => write!(line, "{}", Delimiter::Bracket.as_open_char())?, + } + + Ok(()) + } + fn close_square_bracket( + line: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match formatter.shape.code_line.line_style { + LineStyle::Multiline => { + formatter.shape.block_unindent(&formatter.config); + write!( + line, + "\n{}{}", + formatter.shape.indent.to_string(&formatter.config)?, + Delimiter::Bracket.as_close_char() + )?; + } + _ => write!(line, "{}", Delimiter::Bracket.as_close_char())?, + } + + Ok(()) + } +} + +impl LeafSpans for ExprTupleDescriptor { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = Vec::new(); + if let ExprTupleDescriptor::Cons { + head, + comma_token, + tail, + } = self + { + collected_spans.append(&mut head.leaf_spans()); + collected_spans.push(ByteSpan::from(comma_token.span())); + collected_spans.append(&mut tail.leaf_spans()); + } + collected_spans + } +} + +impl LeafSpans for ExprArrayDescriptor { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = Vec::new(); + if let ExprArrayDescriptor::Repeat { + value, + semicolon_token, + length, + } = self + { + collected_spans.append(&mut value.leaf_spans()); + collected_spans.push(ByteSpan::from(semicolon_token.span())); + collected_spans.append(&mut length.leaf_spans()); + } + collected_spans + } +} diff --git a/sway-fmt-v2/src/utils/expr/conditional.rs b/sway-fmt-v2/src/utils/expr/conditional.rs new file mode 100644 index 00000000000..cc02d15d70e --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/conditional.rs @@ -0,0 +1,241 @@ +use crate::{ + fmt::*, + utils::{ + bracket::CurlyBrace, + comments::{ByteSpan, LeafSpans}, + }, +}; +use std::{fmt::Write, ops::ControlFlow}; +use sway_ast::{token::Delimiter, IfCondition, IfExpr, MatchBranch, MatchBranchKind}; +use sway_types::Spanned; + +impl Format for IfExpr { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(formatted_code, "{} ", self.if_token.span().as_str())?; + self.condition.format(formatted_code, formatter)?; + Self::open_curly_brace(formatted_code, formatter)?; + self.then_block + .clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_curly_brace(formatted_code, formatter)?; + if let Some(else_opt) = &self.else_opt { + write!(formatted_code, "{} ", else_opt.0.span().as_str())?; + match &else_opt.1 { + ControlFlow::Continue(if_expr) => if_expr.format(formatted_code, formatter)?, + ControlFlow::Break(code_block_contents) => { + Self::open_curly_brace(formatted_code, formatter)?; + code_block_contents + .clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_curly_brace(formatted_code, formatter)?; + } + } + } + + Ok(()) + } +} + +impl CurlyBrace for IfExpr { + fn open_curly_brace( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Brace.as_open_char())?; + Ok(()) + } + fn close_curly_brace( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Brace.as_close_char())?; + Ok(()) + } +} + +impl Format for IfCondition { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Self::Expr(expr) => { + expr.format(formatted_code, formatter)?; + } + Self::Let { + let_token, + lhs, + eq_token, + rhs, + } => { + write!(formatted_code, "{} ", let_token.span().as_str())?; + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", eq_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + } + + Ok(()) + } +} + +impl Format for MatchBranch { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + self.pattern.format(formatted_code, formatter)?; + write!( + formatted_code, + " {} ", + self.fat_right_arrow_token.span().as_str() + )?; + self.kind.format(formatted_code, formatter)?; + + Ok(()) + } +} + +impl CurlyBrace for MatchBranch { + fn open_curly_brace( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Brace.as_open_char())?; + Ok(()) + } + fn close_curly_brace( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Brace.as_close_char())?; + Ok(()) + } +} + +impl Format for MatchBranchKind { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Self::Block { + block, + comma_token_opt, + } => { + Self::open_curly_brace(formatted_code, formatter)?; + block + .clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_curly_brace(formatted_code, formatter)?; + if let Some(comma_token) = comma_token_opt { + write!(formatted_code, "{}", comma_token.span().as_str())?; + } + } + Self::Expr { expr, comma_token } => { + expr.format(formatted_code, formatter)?; + write!(formatted_code, "{}", comma_token.span().as_str())?; + } + } + + Ok(()) + } +} + +impl CurlyBrace for MatchBranchKind { + fn open_curly_brace( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Brace.as_open_char())?; + Ok(()) + } + fn close_curly_brace( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Brace.as_close_char())?; + Ok(()) + } +} + +impl LeafSpans for IfExpr { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = vec![ByteSpan::from(self.if_token.span())]; + collected_spans.append(&mut self.condition.leaf_spans()); + collected_spans.append(&mut self.then_block.leaf_spans()); + if let Some(else_block) = &self.else_opt { + collected_spans.push(ByteSpan::from(else_block.0.span())); + let mut else_body_spans = match &else_block.1 { + std::ops::ControlFlow::Continue(if_expr) => if_expr.leaf_spans(), + std::ops::ControlFlow::Break(else_body) => else_body.leaf_spans(), + }; + collected_spans.append(&mut else_body_spans); + } + collected_spans + } +} + +impl LeafSpans for IfCondition { + fn leaf_spans(&self) -> Vec { + match self { + IfCondition::Expr(expr) => expr.leaf_spans(), + IfCondition::Let { + let_token, + lhs, + eq_token, + rhs, + } => { + let mut collected_spans = vec![ByteSpan::from(let_token.span())]; + collected_spans.append(&mut lhs.leaf_spans()); + collected_spans.push(ByteSpan::from(eq_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + } + } +} + +impl LeafSpans for MatchBranch { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = Vec::new(); + collected_spans.append(&mut self.pattern.leaf_spans()); + collected_spans.push(ByteSpan::from(self.fat_right_arrow_token.span())); + collected_spans.append(&mut self.kind.leaf_spans()); + collected_spans + } +} + +impl LeafSpans for MatchBranchKind { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = Vec::new(); + match self { + MatchBranchKind::Block { + block, + comma_token_opt, + } => { + collected_spans.append(&mut block.leaf_spans()); + // TODO: determine if we allow comments between block and comma_token + if let Some(comma_token) = comma_token_opt { + collected_spans.push(ByteSpan::from(comma_token.span())); + } + } + MatchBranchKind::Expr { expr, comma_token } => { + collected_spans.append(&mut expr.leaf_spans()); + // TODO: determine if we allow comments between expr and comma_token + collected_spans.push(ByteSpan::from(comma_token.span())); + } + }; + collected_spans + } +} diff --git a/sway-fmt-v2/src/utils/expr/mod.rs b/sway-fmt-v2/src/utils/expr/mod.rs new file mode 100644 index 00000000000..0567f343014 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/mod.rs @@ -0,0 +1,953 @@ +use crate::{ + fmt::*, + utils::comments::{ByteSpan, LeafSpans}, +}; +use std::fmt::Write; +use sway_ast::{ + brackets::Parens, + keywords::{CommaToken, DotToken}, + punctuated::Punctuated, + token::Delimiter, + Braces, CodeBlockContents, Expr, ExprArrayDescriptor, ExprStructField, ExprTupleDescriptor, + MatchBranch, PathExpr, +}; +use sway_types::{Ident, Spanned}; + +use super::{ + bracket::{CurlyBrace, Parenthesis, SquareBracket}, + shape::{ExprKind, LineStyle}, +}; + +pub(crate) mod abi_cast; +pub(crate) mod asm_block; +pub(crate) mod assignable; +pub(crate) mod code_block; +pub(crate) mod collections; +pub(crate) mod conditional; +pub(crate) mod struct_field; + +#[cfg(test)] +mod tests; + +impl Format for Expr { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + match self { + Self::Path(path) => path.format(formatted_code, formatter)?, + Self::Literal(lit) => lit.format(formatted_code, formatter)?, + Self::AbiCast { abi_token, args } => { + write!(formatted_code, "{}", abi_token.span().as_str())?; + args.clone() + .into_inner() + .format(formatted_code, formatter)?; + } + Self::Struct { path, fields } => { + // store previous state and update expr kind + let prev_state = formatter.shape.code_line; + formatter.shape.code_line.update_expr_kind(ExprKind::Struct); + + // get the length in chars of the code_line in a single line format, + // this include the path + let mut buf = FormattedCode::new(); + let mut temp_formatter = Formatter::default(); + temp_formatter + .shape + .code_line + .update_line_style(LineStyle::Inline); + format_expr_struct(path, fields, &mut buf, &mut temp_formatter)?; + + // get the largest field size and the size of the body + let (field_width, body_width) = + get_field_width(&fields.clone().into_inner(), &mut formatter.clone())?; + + // changes to the actual formatter + let expr_width = buf.chars().count() as usize; + formatter.shape.add_width(expr_width); + formatter + .shape + .get_line_style(Some(field_width), body_width, &formatter.config); + debug_expr(buf, field_width, body_width, expr_width, formatter); + + format_expr_struct(path, fields, formatted_code, formatter)?; + + // revert to previous state + formatter.shape.sub_width(expr_width); + formatter.shape.update_line_settings(prev_state); + } + Self::Tuple(tuple_descriptor) => { + // store previous state and update expr kind + let prev_state = formatter.shape.code_line; + formatter + .shape + .code_line + .update_expr_kind(ExprKind::Collection); + + // get the length in chars of the code_line in a normal line format + let mut buf = FormattedCode::new(); + let mut temp_formatter = Formatter::default(); + format_tuple(tuple_descriptor, &mut buf, &mut temp_formatter)?; + let body_width = buf.chars().count() as usize; + + formatter.shape.add_width(body_width); + formatter + .shape + .get_line_style(None, body_width, &formatter.config); + + format_tuple(tuple_descriptor, formatted_code, formatter)?; + + // revert to previous state + formatter.shape.sub_width(body_width); + formatter.shape.update_line_settings(prev_state); + } + Self::Parens(expr) => { + Self::open_parenthesis(formatted_code, formatter)?; + expr.clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_parenthesis(formatted_code, formatter)?; + } + Self::Block(code_block) => { + CodeBlockContents::open_curly_brace(formatted_code, formatter)?; + code_block + .clone() + .into_inner() + .format(formatted_code, formatter)?; + CodeBlockContents::close_curly_brace(formatted_code, formatter)?; + } + Self::Array(array_descriptor) => { + ExprArrayDescriptor::open_square_bracket(formatted_code, formatter)?; + array_descriptor + .clone() + .into_inner() + .format(formatted_code, formatter)?; + ExprArrayDescriptor::close_square_bracket(formatted_code, formatter)?; + } + Self::Asm(asm_block) => asm_block.format(formatted_code, formatter)?, + Self::Return { + return_token, + expr_opt, + } => { + write!(formatted_code, "{}", return_token.span().as_str())?; + if let Some(expr) = &expr_opt { + write!(formatted_code, " ")?; + expr.format(formatted_code, formatter)?; + } + } + Self::If(if_expr) => if_expr.format(formatted_code, formatter)?, + Self::Match { + match_token, + value, + branches, + } => { + write!(formatted_code, "{} ", match_token.span().as_str())?; + value.format(formatted_code, formatter)?; + MatchBranch::open_curly_brace(formatted_code, formatter)?; + let branches = branches.clone().into_inner(); + for match_branch in branches.iter() { + match_branch.format(formatted_code, formatter)?; + } + MatchBranch::close_curly_brace(formatted_code, formatter)?; + } + Self::While { + while_token, + condition, + block, + } => { + write!(formatted_code, "{} ", while_token.span().as_str())?; + condition.format(formatted_code, formatter)?; + CodeBlockContents::open_curly_brace(formatted_code, formatter)?; + block + .clone() + .into_inner() + .format(formatted_code, formatter)?; + CodeBlockContents::close_curly_brace(formatted_code, formatter)?; + } + Self::FuncApp { func, args } => { + // don't indent unless on new line + if formatted_code.ends_with('\n') { + write!( + formatted_code, + "{}", + formatter.shape.indent.to_string(&formatter.config)? + )?; + } + func.format(formatted_code, formatter)?; + Self::open_parenthesis(formatted_code, formatter)?; + args.clone() + .into_inner() + .format(formatted_code, formatter)?; + Self::close_parenthesis(formatted_code, formatter)?; + } + Self::Index { target, arg } => { + target.format(formatted_code, formatter)?; + Self::open_square_bracket(formatted_code, formatter)?; + arg.clone().into_inner().format(formatted_code, formatter)?; + Self::close_square_bracket(formatted_code, formatter)?; + } + Self::MethodCall { + target, + dot_token, + name, + contract_args_opt, + args, + } => { + let prev_state = formatter.shape.code_line; + // get the length in chars of the code_line in a single line format + let mut buf = FormattedCode::new(); + let mut temp_formatter = Formatter::default(); + temp_formatter + .shape + .code_line + .update_line_style(LineStyle::Inline); + format_method_call( + target, + dot_token, + name, + contract_args_opt, + args, + &mut buf, + &mut temp_formatter, + )?; + + // get the largest field size + let (mut field_width, mut body_width): (usize, usize) = (0, 0); + if let Some(contract_args) = &contract_args_opt { + (field_width, body_width) = get_field_width( + &contract_args.clone().into_inner(), + &mut formatter.clone(), + )?; + } + + // changes to the actual formatter + let expr_width = buf.chars().count() as usize; + formatter.shape.add_width(expr_width); + formatter.shape.code_line.update_expr_kind(ExprKind::Struct); + formatter + .shape + .get_line_style(Some(field_width), body_width, &formatter.config); + + format_method_call( + target, + dot_token, + name, + contract_args_opt, + args, + formatted_code, + formatter, + )?; + + // revert to previous state + formatter.shape.sub_width(expr_width); + formatter.shape.update_line_settings(prev_state); + } + Self::FieldProjection { + target, + dot_token, + name, + } => { + target.format(formatted_code, formatter)?; + write!(formatted_code, "{}", dot_token.span().as_str())?; + name.format(formatted_code, formatter)?; + } + Self::TupleFieldProjection { + target, + dot_token, + field: _, + field_span, + } => { + target.format(formatted_code, formatter)?; + write!( + formatted_code, + "{}{}", + dot_token.span().as_str(), + field_span.as_str(), + )?; + } + Self::Ref { ref_token, expr } => { + write!(formatted_code, "{} ", ref_token.span().as_str())?; + expr.format(formatted_code, formatter)?; + } + Self::Deref { deref_token, expr } => { + write!(formatted_code, "{} ", deref_token.span().as_str())?; + expr.format(formatted_code, formatter)?; + } + Self::Not { bang_token, expr } => { + write!(formatted_code, "{}", bang_token.span().as_str())?; + expr.format(formatted_code, formatter)?; + } + Self::Mul { + lhs, + star_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", star_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Div { + lhs, + forward_slash_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", forward_slash_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Modulo { + lhs, + percent_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", percent_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Add { + lhs, + add_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", add_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Sub { + lhs, + sub_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", sub_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Shl { + lhs, + shl_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", shl_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Shr { + lhs, + shr_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", shr_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::BitAnd { + lhs, + ampersand_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", ampersand_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::BitXor { + lhs, + caret_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", caret_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::BitOr { + lhs, + pipe_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", pipe_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Equal { + lhs, + double_eq_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", double_eq_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::NotEqual { + lhs, + bang_eq_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", bang_eq_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::LessThan { + lhs, + less_than_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", less_than_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::GreaterThan { + lhs, + greater_than_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", greater_than_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::LessThanEq { + lhs, + less_than_eq_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", less_than_eq_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::GreaterThanEq { + lhs, + greater_than_eq_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!( + formatted_code, + " {} ", + greater_than_eq_token.span().as_str() + )?; + rhs.format(formatted_code, formatter)?; + } + Self::LogicalAnd { + lhs, + double_ampersand_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!( + formatted_code, + " {} ", + double_ampersand_token.span().as_str() + )?; + rhs.format(formatted_code, formatter)?; + } + Self::LogicalOr { + lhs, + double_pipe_token, + rhs, + } => { + lhs.format(formatted_code, formatter)?; + write!(formatted_code, " {} ", double_pipe_token.span().as_str())?; + rhs.format(formatted_code, formatter)?; + } + Self::Reassignment { + assignable, + reassignment_op, + expr, + } => { + assignable.format(formatted_code, formatter)?; + reassignment_op.format(formatted_code, formatter)?; + expr.format(formatted_code, formatter)?; + } + } + + Ok(()) + } +} + +impl Parenthesis for Expr { + fn open_parenthesis( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Parenthesis.as_open_char())?; + Ok(()) + } + fn close_parenthesis( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Parenthesis.as_close_char())?; + Ok(()) + } +} + +impl SquareBracket for Expr { + fn open_square_bracket( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Bracket.as_open_char())?; + Ok(()) + } + fn close_square_bracket( + line: &mut FormattedCode, + _formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(line, "{}", Delimiter::Bracket.as_close_char())?; + Ok(()) + } +} + +pub(super) fn debug_expr( + buf: FormattedCode, + field_width: usize, + body_width: usize, + expr_width: usize, + formatter: &mut Formatter, +) { + println!( + "line: {buf}\nfield: {field_width}, body: {body_width}, expr: {expr_width}, width: {}", + formatter.shape.width + ); + println!("{:?}", formatter.shape.code_line); + println!("{:?}", formatter.shape.width_heuristics); +} + +fn format_expr_struct( + path: &PathExpr, + fields: &Braces>, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, +) -> Result<(), FormatterError> { + path.format(formatted_code, formatter)?; + ExprStructField::open_curly_brace(formatted_code, formatter)?; + let fields = &fields.clone().into_inner(); + match formatter.shape.code_line.line_style { + LineStyle::Inline => fields.format(formatted_code, formatter)?, + // TODO: add field alignment + _ => fields.format(formatted_code, formatter)?, + } + ExprStructField::close_curly_brace(formatted_code, formatter)?; + + Ok(()) +} + +fn format_tuple( + tuple_descriptor: &Parens, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, +) -> Result<(), FormatterError> { + tuple_descriptor + .clone() + .into_inner() + .format(formatted_code, formatter)?; + + Ok(()) +} + +fn format_method_call( + target: &Expr, + dot_token: &DotToken, + name: &Ident, + contract_args_opt: &Option>>, + args: &Parens>, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, +) -> Result<(), FormatterError> { + // don't indent unless on new line + if formatted_code.ends_with('\n') { + write!( + formatted_code, + "{}", + formatter.shape.indent.to_string(&formatter.config)? + )?; + } + target.format(formatted_code, formatter)?; + write!(formatted_code, "{}", dot_token.span().as_str())?; + name.format(formatted_code, formatter)?; + if let Some(contract_args) = &contract_args_opt { + ExprStructField::open_curly_brace(formatted_code, formatter)?; + let contract_args = &contract_args.clone().into_inner(); + match formatter.shape.code_line.line_style { + LineStyle::Inline => { + contract_args.format(formatted_code, formatter)?; + } + _ => { + contract_args.format(formatted_code, formatter)?; + } + } + ExprStructField::close_curly_brace(formatted_code, formatter)?; + } + Expr::open_parenthesis(formatted_code, formatter)?; + formatter.shape.reset_line_settings(); + args.clone() + .into_inner() + .format(formatted_code, formatter)?; + Expr::close_parenthesis(formatted_code, formatter)?; + + Ok(()) +} + +fn get_field_width( + fields: &Punctuated, + formatter: &mut Formatter, +) -> Result<(usize, usize), FormatterError> { + let mut largest_field: usize = 0; + let mut body_width: usize = 3; // this is taking into account the opening brace, the following space and the ending brace. + for (field, comma_token) in &fields.value_separator_pairs { + let mut field_length = field.field_name.as_str().chars().count() as usize; + if let Some((colon_token, expr)) = &field.expr_opt { + let mut buf = String::new(); + write!(buf, "{} ", colon_token.span().as_str())?; + expr.format(&mut buf, formatter)?; + field_length += buf.chars().count() as usize; + } + field_length += comma_token.span().as_str().chars().count() as usize; + body_width += &field_length + 1; // accounting for the following space + + if field_length > largest_field { + largest_field = field_length; + } + } + if let Some(final_value) = &fields.final_value_opt { + let mut field_length = final_value.field_name.as_str().chars().count() as usize; + if let Some((colon_token, expr)) = &final_value.expr_opt { + let mut buf = String::new(); + write!(buf, "{} ", colon_token.span().as_str())?; + expr.format(&mut buf, formatter)?; + field_length += buf.chars().count() as usize; + } + body_width += &field_length + 1; // accounting for the following space + + if field_length > largest_field { + largest_field = field_length; + } + } + + Ok((largest_field, body_width)) +} + +// Leaf Spans + +// TODO: Find a better way of handling Boxed version +impl LeafSpans for Box { + fn leaf_spans(&self) -> Vec { + visit_expr(self) + } +} + +impl LeafSpans for Expr { + fn leaf_spans(&self) -> Vec { + visit_expr(self) + } +} + +/// Collects various expr field's ByteSpans. +fn visit_expr(expr: &Expr) -> Vec { + match expr { + Expr::Path(path) => path.leaf_spans(), + Expr::Literal(literal) => literal.leaf_spans(), + Expr::AbiCast { abi_token, args } => { + let mut collected_spans = vec![ByteSpan::from(abi_token.span())]; + collected_spans.append(&mut args.leaf_spans()); + collected_spans + } + Expr::Struct { path, fields } => { + let mut collected_spans = path.leaf_spans(); + collected_spans.append(&mut fields.leaf_spans()); + collected_spans + } + Expr::Tuple(tuple) => tuple.leaf_spans(), + Expr::Parens(parens) => parens.leaf_spans(), + Expr::Block(block) => block.leaf_spans(), + Expr::Array(array) => array.leaf_spans(), + Expr::Asm(asm) => asm.leaf_spans(), + Expr::Return { + return_token, + expr_opt, + } => { + let mut collected_spans = vec![ByteSpan::from(return_token.span())]; + if let Some(expr) = expr_opt { + collected_spans.append(&mut expr.leaf_spans()); + } + collected_spans + } + Expr::If(expr_if) => expr_if.leaf_spans(), + Expr::Match { + match_token, + value, + branches, + } => { + let mut collected_spans = vec![ByteSpan::from(match_token.span())]; + collected_spans.append(&mut value.leaf_spans()); + collected_spans.append(&mut branches.leaf_spans()); + collected_spans + } + Expr::While { + while_token, + condition, + block, + } => { + let mut collected_spans = vec![ByteSpan::from(while_token.span())]; + collected_spans.append(&mut condition.leaf_spans()); + collected_spans.append(&mut block.leaf_spans()); + collected_spans + } + Expr::FuncApp { func, args } => { + let mut collected_spans = Vec::new(); + collected_spans.append(&mut func.leaf_spans()); + collected_spans.append(&mut args.leaf_spans()); + collected_spans + } + Expr::Index { target, arg } => { + let mut collected_spans = Vec::new(); + collected_spans.append(&mut target.leaf_spans()); + collected_spans.append(&mut arg.leaf_spans()); + collected_spans + } + Expr::MethodCall { + target, + dot_token, + name, + contract_args_opt, + args, + } => { + let mut collected_spans = Vec::new(); + collected_spans.append(&mut target.leaf_spans()); + collected_spans.push(ByteSpan::from(dot_token.span())); + collected_spans.push(ByteSpan::from(name.span())); + if let Some(contract_args) = contract_args_opt { + collected_spans.append(&mut contract_args.leaf_spans()); + } + collected_spans.append(&mut args.leaf_spans()); + collected_spans + } + Expr::FieldProjection { + target, + dot_token, + name, + } => { + let mut collected_spans = Vec::new(); + collected_spans.append(&mut target.leaf_spans()); + collected_spans.push(ByteSpan::from(dot_token.span())); + collected_spans.push(ByteSpan::from(name.span())); + collected_spans + } + Expr::TupleFieldProjection { + target, + dot_token, + field: _field, + field_span, + } => { + let mut collected_spans = Vec::new(); + collected_spans.append(&mut target.leaf_spans()); + collected_spans.push(ByteSpan::from(dot_token.span())); + collected_spans.push(ByteSpan::from(field_span.clone())); + collected_spans + } + Expr::Ref { ref_token, expr } => { + let mut collected_spans = vec![ByteSpan::from(ref_token.span())]; + collected_spans.append(&mut expr.leaf_spans()); + collected_spans + } + Expr::Deref { deref_token, expr } => { + let mut collected_spans = vec![ByteSpan::from(deref_token.span())]; + collected_spans.append(&mut expr.leaf_spans()); + collected_spans + } + Expr::Not { bang_token, expr } => { + let mut collected_spans = vec![ByteSpan::from(bang_token.span())]; + collected_spans.append(&mut expr.leaf_spans()); + collected_spans + } + Expr::Mul { + lhs, + star_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(star_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Div { + lhs, + forward_slash_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(forward_slash_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Modulo { + lhs, + percent_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(percent_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Add { + lhs, + add_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(add_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Sub { + lhs, + sub_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(sub_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Shl { + lhs, + shl_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(shl_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Shr { + lhs, + shr_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(shr_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::BitAnd { + lhs, + ampersand_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(ampersand_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::BitXor { + lhs, + caret_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(caret_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::BitOr { + lhs, + pipe_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(pipe_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Equal { + lhs, + double_eq_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(double_eq_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::NotEqual { + lhs, + bang_eq_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(bang_eq_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::LessThan { + lhs, + less_than_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(less_than_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::GreaterThan { + lhs, + greater_than_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(greater_than_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::LessThanEq { + lhs, + less_than_eq_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(less_than_eq_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::GreaterThanEq { + lhs, + greater_than_eq_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(greater_than_eq_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::LogicalAnd { + lhs, + double_ampersand_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(double_ampersand_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::LogicalOr { + lhs, + double_pipe_token, + rhs, + } => { + let mut collected_spans = lhs.leaf_spans(); + collected_spans.push(ByteSpan::from(double_pipe_token.span())); + collected_spans.append(&mut rhs.leaf_spans()); + collected_spans + } + Expr::Reassignment { + assignable, + reassignment_op, + expr, + } => { + let mut collected_spans = assignable.leaf_spans(); + collected_spans.push(ByteSpan::from(reassignment_op.span.clone())); + collected_spans.append(&mut expr.leaf_spans()); + collected_spans + } + } +} diff --git a/sway-fmt-v2/src/utils/expr/struct_field.rs b/sway-fmt-v2/src/utils/expr/struct_field.rs new file mode 100644 index 00000000000..c74d7907666 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/struct_field.rs @@ -0,0 +1,82 @@ +use crate::{ + config::items::ItemBraceStyle, + fmt::*, + utils::{ + bracket::CurlyBrace, + comments::{ByteSpan, LeafSpans}, + shape::LineStyle, + }, +}; +use std::fmt::Write; +use sway_ast::{token::Delimiter, ExprStructField}; +use sway_types::Spanned; + +impl Format for ExprStructField { + fn format( + &self, + formatted_code: &mut FormattedCode, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + write!(formatted_code, "{}", self.field_name.span().as_str())?; + if let Some((colon_token, expr)) = &self.expr_opt { + write!(formatted_code, "{} ", colon_token.span().as_str())?; + expr.format(formatted_code, formatter)?; + } + + Ok(()) + } +} + +impl CurlyBrace for ExprStructField { + fn open_curly_brace( + line: &mut String, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + let brace_style = formatter.config.items.item_brace_style; + match brace_style { + ItemBraceStyle::AlwaysNextLine => { + // Add openning brace to the next line. + write!(line, "\n{}", Delimiter::Brace.as_open_char())?; + formatter.shape.block_indent(&formatter.config); + } + _ => { + // Add opening brace to the same line + write!(line, " {}", Delimiter::Brace.as_open_char())?; + formatter.shape.block_indent(&formatter.config); + } + } + + Ok(()) + } + + fn close_curly_brace( + line: &mut String, + formatter: &mut Formatter, + ) -> Result<(), FormatterError> { + // Unindent by one block + formatter.shape.block_unindent(&formatter.config); + match formatter.shape.code_line.line_style { + LineStyle::Inline => write!(line, "{}", Delimiter::Brace.as_close_char())?, + _ => write!( + line, + "{}{}", + formatter.shape.indent.to_string(&formatter.config)?, + Delimiter::Brace.as_close_char() + )?, + } + + Ok(()) + } +} + +impl LeafSpans for ExprStructField { + fn leaf_spans(&self) -> Vec { + let mut collected_spans = vec![ByteSpan::from(self.field_name.span())]; + if let Some((colon_token, expr)) = &self.expr_opt { + collected_spans.push(ByteSpan::from(colon_token.span())); + // TODO: determine if we are allowing comments between `:` and expr + collected_spans.append(&mut expr.leaf_spans()); + } + collected_spans + } +} diff --git a/sway-fmt-v2/src/utils/expr/tests.rs b/sway-fmt-v2/src/utils/expr/tests.rs new file mode 100644 index 00000000000..0b4570956a1 --- /dev/null +++ b/sway-fmt-v2/src/utils/expr/tests.rs @@ -0,0 +1,117 @@ +use crate::{Format, Formatter}; +use forc_util::{println_green, println_red}; +use paste::paste; +use prettydiff::{basic::DiffOp, diff_lines}; +use sway_ast::Expr; +use sway_parse::*; + +fn format_code(input: &str) -> String { + let mut errors = vec![]; + let mut formatter: Formatter = Default::default(); + let input_arc = std::sync::Arc::from(input); + let token_stream = lex(&input_arc, 0, input.len(), None).unwrap(); + let mut parser = Parser::new(&token_stream, &mut errors); + let expression: Expr = Parse::parse(&mut parser).unwrap(); + + let mut buf = Default::default(); + expression.format(&mut buf, &mut formatter).unwrap(); + + buf +} + +macro_rules! fmt_test { + ($scope:ident $desired_output:expr, $($name:ident $y:expr),+) => { + fmt_test_inner!($scope $desired_output, + $($name $y)+ + , + remove_trailing_whitespace format!("{} \n\n\t ", $desired_output).as_str(), + remove_beginning_whitespace format!(" \n\t{}", $desired_output).as_str(), + identity $desired_output, /* test return is valid */ + remove_beginning_and_trailing_whitespace format!(" \n\t {} \n\t ", $desired_output).as_str() + ); + }; +} + +macro_rules! fmt_test_inner { + ($scope:ident $desired_output:expr, $($name:ident $y:expr),+) => { + $( + paste! { + #[test] + fn [<$scope _ $name>] () { + let formatted_code = format_code($y); + let changeset = diff_lines(&formatted_code, $desired_output); + let count_of_updates = changeset.diff().len(); + if count_of_updates != 0 { + println!("FAILED: {count_of_updates} diff items."); + } + for diff in changeset.diff() { + match diff { + DiffOp::Equal(old) => { + for o in old { + println!("{}", o) + } + } + DiffOp::Insert(new) => { + for n in new { + println_green(&format!("+{}", n)); + } + } + DiffOp::Remove(old) => { + for o in old { + println_red(&format!("-{}", o)); + } + } + DiffOp::Replace(old, new) => { + for o in old { + println_red(&format!("-{}", o)); + } + for n in new { + println_green(&format!("+{}", n)); + } + } + } + } + assert_eq!(&formatted_code, $desired_output); + } + } + )+ +} +} + +// TODO: +// path, literal, abicast, struct, tuple, parens, block, array, asm, return, if, match, while, func app, index, field projection, tuple field projection, ref, deref, operators, bitwise stuff, comp operators +// logical operators, reassignment + +fmt_test!(literal "5", extra_whitespace " 5 " +); + +fmt_test!( path_foo_bar "foo::bar::baz::quux::quuz", + intermediate_whitespace "foo :: bar :: baz :: quux :: quuz"); + +fmt_test!( field_proj_foobar "foo.bar.baz.quux", + intermediate_whitespace "foo . bar . baz . quux"); + +fmt_test!( abi_cast "abi(MyAbi, 0x1111111111111111111111111111111111111111111111111111111111111111)", + intermediate_whitespace " abi ( + MyAbi + , + 0x1111111111111111111111111111111111111111111111111111111111111111 + ) " +); + +fmt_test!( basic_func_app "foo()", + intermediate_whitespace " foo ( + + ) " +); + +fmt_test!( nested_args_func_app "foo(a_struct { hello: \"hi\" }, a_var, foo.bar.baz.quux)", + intermediate_whitespace "foo(a_struct { + hello : \"hi\" + }, a_var , foo . bar . baz . quux)" +); + +fmt_test!( multiline_tuple "(\n \"reallyreallylongstring\",\n \"yetanotherreallyreallyreallylongstring\",\n \"okaynowthatsjustaridiculouslylongstringrightthere\",\n)", + intermediate_whitespace "(\"reallyreallylongstring\", \"yetanotherreallyreallyreallylongstring\", + \"okaynowthatsjustaridiculouslylongstringrightthere\")" +); diff --git a/sway-fmt-v2/src/utils/generics.rs b/sway-fmt-v2/src/utils/generics.rs index 9552ddb081d..04dc6c5827c 100644 --- a/sway-fmt-v2/src/utils/generics.rs +++ b/sway-fmt-v2/src/utils/generics.rs @@ -2,6 +2,8 @@ use super::bracket::{close_angle_bracket, open_angle_bracket}; use crate::fmt::{Format, FormattedCode, Formatter, FormatterError}; use sway_ast::{GenericArgs, GenericParams}; +use super::shape::LineStyle; + // In the future we will need to determine whether the generic arguments // are better suited with a `where` clause. At present they will be // formatted in line. @@ -13,6 +15,11 @@ impl Format for GenericParams { formatter: &mut Formatter, ) -> Result<(), FormatterError> { let params = self.parameters.clone().into_inner(); + let prev_state = formatter.shape.code_line; + formatter + .shape + .code_line + .update_line_style(LineStyle::Normal); // `<` open_angle_bracket(formatted_code)?; @@ -21,6 +28,8 @@ impl Format for GenericParams { // `>` close_angle_bracket(formatted_code)?; + formatter.shape.update_line_settings(prev_state); + Ok(()) } } diff --git a/sway-fmt-v2/src/utils/item.rs b/sway-fmt-v2/src/utils/item.rs index d86c0d822c6..c46f75f3333 100644 --- a/sway-fmt-v2/src/utils/item.rs +++ b/sway-fmt-v2/src/utils/item.rs @@ -1,5 +1,5 @@ use crate::{ - fmt::{Format, FormattedCode, Formatter, FormatterError}, + fmt::*, utils::comments::{ByteSpan, LeafSpans}, }; use sway_ast::{Item, ItemKind::*}; diff --git a/sway-fmt-v2/src/utils/pattern.rs b/sway-fmt-v2/src/utils/pattern.rs index 1a2a568d245..df171510834 100644 --- a/sway-fmt-v2/src/utils/pattern.rs +++ b/sway-fmt-v2/src/utils/pattern.rs @@ -22,8 +22,7 @@ impl Format for Pattern { if let Some(mut_token) = mutable { write!(formatted_code, "{} ", mut_token.span().as_str())?; } - // maybe add `Ident::format()`, not sure if needed yet. - formatted_code.push_str(name.span().as_str()); + name.format(formatted_code, formatter)?; } Self::Literal(lit) => lit.format(formatted_code, formatter)?, Self::Constant(path) => path.format(formatted_code, formatter)?, diff --git a/sway-fmt-v2/src/utils/punctuated.rs b/sway-fmt-v2/src/utils/punctuated.rs index c89b0b77377..7211c725bd7 100644 --- a/sway-fmt-v2/src/utils/punctuated.rs +++ b/sway-fmt-v2/src/utils/punctuated.rs @@ -4,9 +4,13 @@ use crate::{ FormatterError, }; use std::fmt::Write; -use sway_ast::{keywords::CommaToken, punctuated::Punctuated, StorageField, TypeField}; +use sway_ast::{ + keywords::CommaToken, punctuated::Punctuated, token::PunctKind, StorageField, TypeField, +}; use sway_types::{Ident, Spanned}; +use super::shape::LineStyle; + impl LeafSpans for Punctuated where T: LeafSpans + Clone, @@ -51,16 +55,64 @@ where formatted_code: &mut FormattedCode, formatter: &mut Formatter, ) -> Result<(), FormatterError> { - // format and add Type & Punct - let value_pairs = &self.value_separator_pairs; - for pair in value_pairs.iter() { - pair.0.format(formatted_code, formatter)?; - pair.1.format(formatted_code, formatter)?; - } + match formatter.shape.code_line.line_style { + LineStyle::Normal => { + let value_pairs = &self.value_separator_pairs; + for (type_field, punctuation) in value_pairs.iter() { + type_field.format(formatted_code, formatter)?; + punctuation.format(formatted_code, formatter)?; + write!(formatted_code, " ")?; + } - // add final value, if any - if let Some(final_value) = &self.final_value_opt { - final_value.format(formatted_code, formatter)?; + if let Some(final_value) = &self.final_value_opt { + final_value.format(formatted_code, formatter)?; + } + } + LineStyle::Inline => { + write!(formatted_code, " ")?; + let mut value_pairs_iter = self.value_separator_pairs.iter().peekable(); + for (type_field, punctuation) in value_pairs_iter.clone() { + type_field.format(formatted_code, formatter)?; + punctuation.format(formatted_code, formatter)?; + + if value_pairs_iter.peek().is_some() { + write!(formatted_code, " ")?; + } + } + if let Some(final_value) = &self.final_value_opt { + final_value.format(formatted_code, formatter)?; + } else { + formatted_code.pop(); + formatted_code.pop(); + } + write!(formatted_code, " ")?; + } + LineStyle::Multiline => { + writeln!(formatted_code)?; + let mut value_pairs_iter = self.value_separator_pairs.iter().peekable(); + for (type_field, comma_token) in value_pairs_iter.clone() { + write!( + formatted_code, + "{}", + &formatter.shape.indent.to_string(&formatter.config)? + )?; + type_field.format(formatted_code, formatter)?; + + if value_pairs_iter.peek().is_some() { + comma_token.format(formatted_code, formatter)?; + writeln!(formatted_code)?; + } + } + if let Some(final_value) = &self.final_value_opt { + write!( + formatted_code, + "{}", + &formatter.shape.indent.to_string(&formatter.config)? + )?; + final_value.format(formatted_code, formatter)?; + writeln!(formatted_code, "{}", PunctKind::Comma.as_char())?; + } + } } Ok(()) @@ -120,7 +172,7 @@ impl Format for CommaToken { formatted_code: &mut FormattedCode, _formatter: &mut Formatter, ) -> Result<(), FormatterError> { - write!(formatted_code, "{} ", self.span().as_str())?; + write!(formatted_code, "{}", self.span().as_str())?; Ok(()) } } diff --git a/sway-fmt-v2/src/utils/indent_style.rs b/sway-fmt-v2/src/utils/shape.rs similarity index 53% rename from sway-fmt-v2/src/utils/indent_style.rs rename to sway-fmt-v2/src/utils/shape.rs index 778cc43351b..24b77d364dd 100644 --- a/sway-fmt-v2/src/utils/indent_style.rs +++ b/sway-fmt-v2/src/utils/shape.rs @@ -1,6 +1,6 @@ //! Associated functions and tests for handling of indentation. use crate::{ - config::manifest::Config, + config::{heuristics::WidthHeuristics, manifest::Config}, constants::{HARD_TAB, INDENT_BUFFER, INDENT_BUFFER_LEN}, FormatterError, }; @@ -104,41 +104,81 @@ impl Sub for Indent { } } +#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] +pub(crate) struct CodeLine { + pub(crate) line_style: LineStyle, + pub(crate) expr_kind: ExprKind, +} + +impl CodeLine { + /// Update `CodeLine::line_style` with a given LineStyle. + pub(crate) fn update_line_style(&mut self, line_style: LineStyle) { + self.line_style = line_style + } + /// Update `CodeLine::expr_kind` with a given ExprKind. + pub(crate) fn update_expr_kind(&mut self, expr_kind: ExprKind) { + self.expr_kind = expr_kind + } +} + #[derive(Copy, Clone, Debug, PartialEq, Eq)] pub(crate) enum LineStyle { + Normal, Inline, Multiline, } impl Default for LineStyle { fn default() -> Self { - Self::Multiline + Self::Normal + } +} + +/// The type of expression to determine which part of `Config::heuristics` to use. +#[derive(Copy, Clone, Debug, PartialEq, Eq)] +pub(crate) enum ExprKind { + Variable, + Function, + Struct, + Collection, + MethodChain, + Conditional, + Undetermined, +} + +impl Default for ExprKind { + fn default() -> Self { + Self::Undetermined } } +/// The current shape of the formatter. #[derive(Copy, Clone, Debug, PartialEq, Eq, Default)] pub(crate) struct Shape { - /// The current number of characters in the given `Item`. + /// The current number of characters in the given code line. pub(crate) width: usize, /// The current indentation of code. pub(crate) indent: Indent, - /// Determines whether a code line is inline, or multiline. - pub(crate) line_style: LineStyle, + /// Used in determining which heuristics settings to use from the config + /// and whether a code line is normal, inline or multiline. + pub(crate) code_line: CodeLine, + /// The definitive width settings from the `Config`. + pub(crate) width_heuristics: WidthHeuristics, /// Used in determining `SameLineWhere` formatting. /// Default is false. pub(crate) has_where_clause: bool, } impl Shape { - /// `indent` is the indentation of the first line. The next lines - /// should begin with at least `indent` spaces (except backwards - /// indentation). The first line should not begin with indentation. - /// `width` is the maximum number of characters on the last line - /// (excluding `indent`). The width of other lines is not limited by - /// `width`. - /// - /// Note that in reality, we sometimes use width for lines other than the - /// last (i.e., we are conservative). + // `indent` is the indentation of the first line. The next lines + // should begin with at least `indent` spaces (except backwards + // indentation). The first line should not begin with indentation. + // `width` is the maximum number of characters on the last line + // (excluding `indent`). The width of other lines is not limited by + // `width`. + // + // Note that in reality, we sometimes use width for lines other than the + // last (i.e., we are conservative). // .......*-------* // | | // | *-* @@ -147,22 +187,11 @@ impl Shape { // |<---->| indent // |<--->| width // - /// Construct a new `Shape` with a given width and level of indentation. - pub(crate) fn legacy(&self, width: usize, indent: Indent) -> Self { - Self { - width, - indent, - line_style: self.line_style, - has_where_clause: self.has_where_clause, - } - } - /// Construct a new `Shape` that takes into account the current level of indentation. - pub(crate) fn indented(&self, indent: Indent, config: &Config) -> Self { - Self { - width: config.whitespace.max_width.saturating_sub(indent.width()), - indent, - line_style: self.line_style, - has_where_clause: self.has_where_clause, + /// A wrapper for `to_width_heuristics()` that also checks if the settings are default. + pub(crate) fn apply_width_heuristics(&mut self, width_heuristics: WidthHeuristics) { + if width_heuristics == WidthHeuristics::default() { + } else { + self.width_heuristics = width_heuristics } } /// A wrapper for `Indent::block_indent()`. @@ -176,36 +205,74 @@ impl Shape { /// Removes a level of indentation specified by the `Formatter::config` to the current `block_indent`. /// If the current level of indentation would be negative, leave it as is. pub(crate) fn block_unindent(&mut self, config: &Config) { - self.indent.block_unindent(config); + self.indent.block_unindent(config) } /// Updates `Shape::width` to the current width of the `Item`. pub(crate) fn update_width(&mut self, len_chars: usize) { - self.width = len_chars; + self.width = len_chars + } + pub(crate) fn add_width(&mut self, len_chars: usize) { + self.width += len_chars + } + pub(crate) fn sub_width(&mut self, len_chars: usize) { + self.width -= len_chars + } + pub(crate) fn reset_width(&mut self) { + self.update_width(0) } /// Checks the config, and if `small_structure_single_line` is enabled, /// determines whether the `Shape::width` is greater than the `structure_lit_width` /// threshold. If it isn't, the `Shape::line_style` is updated to `Inline`. - pub(crate) fn get_line_style(&mut self, config: &Config) { - let allow_inline_style = config.structures.small_structures_single_line; - // Get the width limit of a structure to be formatted into single line if `allow_inline_style` is true. - if allow_inline_style { - let width_heuristics = config - .heuristics - .heuristics_pref - .to_width_heuristics(&config.whitespace); - - if self.width > width_heuristics.structure_lit_width { - self.line_style = LineStyle::Multiline - } else { - self.line_style = LineStyle::Inline + pub(crate) fn get_line_style( + &mut self, + field_width: Option, + body_width: usize, + config: &Config, + ) { + // we can maybe do this at the beginning and store it on shape since it won't change during formatting + // let width_heuristics = config + // .heuristics + // .heuristics_pref + // .to_width_heuristics(config.whitespace.max_width); + match self.code_line.expr_kind { + ExprKind::Struct => { + // Get the width limit of a structure to be formatted into single line if `allow_inline_style` is true. + if config.structures.small_structures_single_line { + if let Some(field_width) = field_width { + if field_width > self.width_heuristics.structure_field_width { + self.code_line.update_line_style(LineStyle::Multiline) + } + } + if self.width > config.whitespace.max_width + || body_width > self.width_heuristics.structure_lit_width + { + self.code_line.update_line_style(LineStyle::Multiline) + } else { + self.code_line.update_line_style(LineStyle::Inline) + } + } else { + self.code_line.update_line_style(LineStyle::Multiline) + } } - } else { - self.line_style = LineStyle::Multiline + ExprKind::Collection => { + if self.width > config.whitespace.max_width + || body_width > self.width_heuristics.collection_width + { + self.code_line.update_line_style(LineStyle::Multiline) + } else { + self.code_line.update_line_style(LineStyle::Normal) + } + } + _ => self.code_line.update_line_style(LineStyle::default()), } } - /// Reset `Shape::line_style` to default. - pub(crate) fn reset_line_style(&mut self) { - self.line_style = LineStyle::default() + /// Update `Shape::code_line` with the provided settings. + pub(crate) fn update_line_settings(&mut self, line_settings: CodeLine) { + self.code_line = line_settings + } + /// Reset `Shape::code_line` to default. + pub(crate) fn reset_line_settings(&mut self) { + self.update_line_settings(CodeLine::default()) } /// Update the value of `has_where_clause`. pub(crate) fn update_where_clause(&mut self) { @@ -257,36 +324,45 @@ mod test { #[test] fn shape_block_indent() { let mut formatter = Formatter::default(); - formatter.config.whitespace.tab_spaces = 20; + formatter.config.whitespace.tab_spaces = 24; let max_width = formatter.config.whitespace.max_width; - let indent = Indent::new(4); - let mut shape = Shape::legacy(&formatter.shape, max_width, indent); - shape.block_indent(&formatter.config); + formatter.shape.update_width(max_width); + formatter.shape.block_indent(&formatter.config); - assert_eq!(max_width, shape.width); - assert_eq!(24, shape.indent.block_indent); + assert_eq!(max_width, formatter.shape.width); + assert_eq!(24, formatter.shape.indent.block_indent); } #[test] - fn test_line_style() { + fn test_get_line_style_struct() { let mut formatter = Formatter::default(); - formatter.shape.get_line_style(&formatter.config); - assert_eq!(LineStyle::Inline, formatter.shape.line_style); + formatter.shape.code_line.update_expr_kind(ExprKind::Struct); + formatter + .shape + .get_line_style(Some(9), 18, &formatter.config); + assert_eq!(LineStyle::Inline, formatter.shape.code_line.line_style); - formatter.shape.width = 19; - formatter.shape.get_line_style(&formatter.config); - assert_eq!(LineStyle::Multiline, formatter.shape.line_style); + formatter + .shape + .get_line_style(Some(10), 19, &formatter.config); + assert_eq!(LineStyle::Multiline, formatter.shape.code_line.line_style); } #[test] fn test_reset_line_style() { let mut formatter = Formatter::default(); - formatter.shape.line_style = LineStyle::Inline; + formatter.shape.code_line.update_expr_kind(ExprKind::Struct); + formatter + .shape + .code_line + .update_line_style(LineStyle::Inline); - formatter.shape.get_line_style(&formatter.config); - assert_eq!(LineStyle::Inline, formatter.shape.line_style); + formatter + .shape + .get_line_style(Some(8), 18, &formatter.config); + assert_eq!(LineStyle::Inline, formatter.shape.code_line.line_style); - formatter.shape.reset_line_style(); - assert_eq!(LineStyle::Multiline, formatter.shape.line_style); + formatter.shape.reset_line_settings(); + assert_eq!(LineStyle::Normal, formatter.shape.code_line.line_style); } } diff --git a/sway-fmt-v2/src/utils/statement.rs b/sway-fmt-v2/src/utils/statement.rs index 10687661de2..8da92728348 100644 --- a/sway-fmt-v2/src/utils/statement.rs +++ b/sway-fmt-v2/src/utils/statement.rs @@ -21,7 +21,7 @@ impl Format for Statement { } => { expr.format(formatted_code, formatter)?; if let Some(semicolon) = semicolon_token_opt { - write!(formatted_code, "{}", semicolon.span().as_str())?; + writeln!(formatted_code, "{}", semicolon.span().as_str())?; } } } diff --git a/sway-fmt-v2/src/utils/ty.rs b/sway-fmt-v2/src/utils/ty.rs index dc1415f3c6b..887e2538bd5 100644 --- a/sway-fmt-v2/src/utils/ty.rs +++ b/sway-fmt-v2/src/utils/ty.rs @@ -12,6 +12,8 @@ use sway_ast::{ }; use sway_types::Spanned; +use super::shape::LineStyle; + impl Format for Ty { fn format( &self, @@ -96,10 +98,19 @@ impl Format for TyTupleDescriptor { tail, } = self { + let prev_state = formatter.shape.code_line; + formatter + .shape + .code_line + .update_line_style(LineStyle::Normal); + head.format(formatted_code, formatter)?; write!(formatted_code, "{} ", comma_token.ident().as_str())?; tail.format(formatted_code, formatter)?; + + formatter.shape.update_line_settings(prev_state); } + Ok(()) } }