Skip to content

Commit

Permalink
Format long use stmnts + tests (FuelLabs#1480)
Browse files Browse the repository at this point in the history
* Format long use stmnts + tests

Update formatting + comments

Cleanup implementation

Update 'use' formatting in examples

Update with prettier formatting

Update implementation

Fix merge conflicts

Fix off indent

Update test with ALREADY_FORMATTED_LINE_PATTERN

Fix line end in get_already_formatted_line_pattern

* Improve deeply nested formatting

Add space between commas

CI updates: formatting and fix examples

Fix clippy issues

Improve formatting

Remove unwrap_or() for unwrap() for possible panic

* Add some clarifying comments

Co-authored-by: Rashad Alston <[email protected]>
Co-authored-by: Rashad Alston <[email protected]>
  • Loading branch information
3 people authored May 10, 2022
1 parent 0f8de55 commit 10a7147
Show file tree
Hide file tree
Showing 6 changed files with 243 additions and 39 deletions.
9 changes: 8 additions & 1 deletion examples/liquidity_pool/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
contract;

use std::{address::Address, assert::assert, context::call_frames::{contract_id, msg_asset_id}, context::msg_amount, contract_id::ContractId, token::{mint_to_address, transfer_to_output}};
use std::{
address::Address,
assert::assert,
context::call_frames::{contract_id, msg_asset_id},
context::msg_amount,
contract_id::ContractId,
token::{mint_to_address, transfer_to_output}
};

abi LiquidityPool {
fn deposit(recipient: Address);
Expand Down
8 changes: 7 additions & 1 deletion examples/msg_sender/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
contract;

use std::{address::Address, assert::assert, chain::auth::{AuthError, Sender, msg_sender}, result::*, revert::revert};
use std::{
address::Address,
assert::assert,
chain::auth::{AuthError, Sender, msg_sender},
result::*,
revert::revert,
};

abi MyOwnedContract {
fn receive(field_1: u64) -> bool;
Expand Down
10 changes: 9 additions & 1 deletion examples/subcurrency/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,7 +1,15 @@
// ANCHOR: body
contract;

use std::{address::Address, assert::assert, chain::auth::{AuthError, Sender, msg_sender}, hash::*, result::*, revert::revert, storage::{get, store}};
use std::{
address::Address,
assert::assert,
chain::auth::{AuthError, Sender, msg_sender},
hash::*,
result::*,
revert::revert,
storage::{get, store}
};

////////////////////////////////////////
// Event declarations
Expand Down
12 changes: 11 additions & 1 deletion examples/wallet_smart_contract/src/main.sw
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
contract;

use std::{address::Address, assert::assert, chain::auth::{AuthError, Sender, msg_sender}, constants::NATIVE_ASSET_ID, context::{call_frames::msg_asset_id, msg_amount}, contract_id::ContractId, result::*, revert::revert, token::transfer_to_output};
use std::{
address::Address,
assert::assert,
chain::auth::{AuthError, Sender, msg_sender},
constants::NATIVE_ASSET_ID,
context::{call_frames::msg_asset_id, msg_amount},
contract_id::ContractId,
result::*,
revert::revert,
token::transfer_to_output,
};

const OWNER_ADDRESS: b256 = 0x8900c5bec4ca97d4febf9ceb4754a60d782abbf3cd815836c1872116f203f861;

Expand Down
13 changes: 10 additions & 3 deletions sway-fmt/src/code_builder_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,9 +192,16 @@ pub fn get_new_line_pattern(line: &str) -> Option<&str> {
pub fn get_already_formatted_line_pattern(line: &str) -> Option<(&str, &str)> {
let pattern_len = ALREADY_FORMATTED_LINE_PATTERN.len();

if line.len() >= pattern_len && &line[0..pattern_len] == ALREADY_FORMATTED_LINE_PATTERN {
// already formatted single lines end with ';'
let end = line.find(';').unwrap();
if line.starts_with(ALREADY_FORMATTED_LINE_PATTERN) {
let char_idxs = vec![
line.find(';').unwrap_or(0),
line.rfind(',').unwrap_or(0),
line.rfind('}').unwrap_or(0),
line.rfind('{').unwrap_or(0),
];

let end = char_idxs.iter().max().unwrap();

let formatted_line = &line[pattern_len..end + 1];
// rest, if any
let rest = &line[end + 1..];
Expand Down
230 changes: 198 additions & 32 deletions sway-fmt/src/traversal_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -85,35 +85,36 @@ pub fn format_delineated_path(line: &str) -> String {
line.chars().filter(|c| !c.is_whitespace()).collect()
}

/// Trims whitespaces and reorders compound import statements lexicographically
/// a::{c, b, d::{self, f, e}} -> a::{b,c,d::{self,e,f}}
fn sort_and_filter_use_expression(line: &str) -> String {
/// Tokenizes the line on separators keeping the separators.
fn tokenize(line: &str) -> Vec<String> {
let mut buffer: Vec<String> = Vec::new();
let mut current = 0;
for (index, separator) in line.match_indices(|c: char| c == ',' || c == '{' || c == '}') {
if index != current {
// Chomp all whitespace including newlines, and only push
// resulting token if what's left is not an empty string. This
// is needed to ignore trailing commas with newlines.
let to_push: String = line[current..index]
.to_string()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
if !to_push.is_empty() {
buffer.push(to_push);
}
/// Tokenizes the line on separators keeping the separators.
fn tokenize(line: &str) -> Vec<String> {
let mut buffer: Vec<String> = Vec::new();
let mut current = 0;
for (index, separator) in line.match_indices(|c: char| c == ',' || c == '{' || c == '}') {
if index != current {
// Chomp all whitespace including newlines, and only push
// resulting token if what's left is not an empty string. This
// is needed to ignore trailing commas with newlines.
let to_push: String = line[current..index]
.to_string()
.chars()
.filter(|c| !c.is_whitespace())
.collect();
if !to_push.is_empty() {
buffer.push(to_push);
}
buffer.push(separator.to_string());
current = index + separator.len();
}
if current < line.len() {
buffer.push(line[current..].to_string());
}
buffer
buffer.push(separator.to_string());
current = index + separator.len();
}
if current < line.len() {
buffer.push(line[current..].to_string());
}
buffer
}

/// Trims whitespaces and reorders compound import statements lexicographically
/// a::{c, b, d::{self, f, e}} -> a::{b,c,d::{self,e,f}}
fn sort_and_filter_use_expression(line: &str) -> String {
let tokens: Vec<String> = tokenize(line);
let mut buffer: Vec<String> = Vec::new();

Expand Down Expand Up @@ -156,14 +157,144 @@ fn sort_and_filter_use_expression(line: &str) -> String {
buffer.concat()
}

fn format_use_statement_length(s: &str, max_length: usize, level: usize) -> String {
let s = match s.starts_with(ALREADY_FORMATTED_LINE_PATTERN) {
true => s[ALREADY_FORMATTED_LINE_PATTERN.len()..].trim(),
false => s,
};

let buff = tokenize(s);
let mut without_newline = buff.iter().rev().collect::<Vec<&String>>();

let len: usize = buff.iter().map(|x| x.len()).sum();
if len <= max_length {
return s.to_owned();
}

// Receive tokens and push them to a string until a full line is made
fn make_line(token: &str, line: &mut String, open_brackets: &mut u8, remainder: usize) -> bool {
let mut is_line = false;

line.push_str(token);

if token == "," {
line.push(' ');
}

match token {
"," => {
if *open_brackets == 1 {
is_line = true;
}
}
"{" => {
if *open_brackets == 0 {
is_line = true;
}
*open_brackets += 1;
}
"}" => {
*open_brackets -= 1;
// Using `remainder` to see if we're at either a 2-char terminator for the full
// use statement (i.e., '};') or at a single char terminator (e.g., '}') for individual
// formatted lines
if *open_brackets == 1 && (remainder == 2 || remainder == 1) {
is_line = true;
}
}
_ => {
if remainder == 2 && *open_brackets == 1 {
line.push(',');
is_line = true;
}
}
}

is_line
}

fn format_line(input: &str, open_brackets: u8, level: usize) -> String {
let input = input.trim();
let mut tabs = open_brackets as usize + level;

let mut output = match input.starts_with(ALREADY_FORMATTED_LINE_PATTERN) {
true => input.to_owned(),
false => ALREADY_FORMATTED_LINE_PATTERN.to_owned(),
};

// If this is the end of nested brackets, decrement `tabs` if we have any
if (input.ends_with('{') || input.ends_with("};") || open_brackets > 1) && tabs > 0 {
tabs -= 1;
}

let prefix = " ".repeat(tabs);
output.push_str(&prefix);
output.push_str(input);

if tabs > 0 || input.ends_with('{') || input.ends_with("};") {
output.push('\n');
}

output
}

let mut with_newline: Vec<String> = Vec::new();

let mut curr_line = String::new();
let mut open_brackets = 0u8;

while let Some(token) = without_newline.pop() {
let is_line = make_line(
token,
&mut curr_line,
&mut open_brackets,
without_newline.len(),
);

if !is_line {
continue;
}

curr_line = format_line(&curr_line, open_brackets, level);

if curr_line.len() > max_length {
curr_line = format_use_statement_length(&curr_line, max_length, level + 1);
}

with_newline.push(curr_line);
curr_line = String::new();
}

if !curr_line.is_empty() {
curr_line = format_line(&curr_line, open_brackets, level);
with_newline.push(curr_line);
}

with_newline.concat()
}

pub fn format_use_statement(line: &str) -> String {
let use_keyword = extract_keyword(line, Rule::use_keyword).unwrap();
let (_, right) = line.split_once(&use_keyword).unwrap();
let right: String = sort_and_filter_use_expression(right);
format!(
"{}{} {}",
ALREADY_FORMATTED_LINE_PATTERN, use_keyword, right
)
let mut right: String = sort_and_filter_use_expression(right);

let max_length = 100usize;

// This is mostly to satisfy a failing fmt test
if right.len() > max_length {
right = format_use_statement_length(&right, max_length, 0usize);
right.insert_str(
ALREADY_FORMATTED_LINE_PATTERN.len(),
&format!("{} ", use_keyword),
);
} else {
right = format!(
"{}{} {}",
ALREADY_FORMATTED_LINE_PATTERN, use_keyword, right
)
}

right
}

pub fn format_include_statement(line: &str) -> String {
Expand Down Expand Up @@ -238,7 +369,8 @@ fn get_data_field_type(line: &str, iter: &mut Peekable<Enumerate<Chars>>) -> Str

#[cfg(test)]
mod tests {
use super::sort_and_filter_use_expression;
use super::{format_use_statement_length, sort_and_filter_use_expression};
use crate::constants::ALREADY_FORMATTED_LINE_PATTERN;

#[test]
fn test_sort_and_filter_use_expression() {
Expand Down Expand Up @@ -278,4 +410,38 @@ mod tests {
"a::{bar, foo};"
);
}

#[test]
fn test_format_use_statement_length_leaves_input_unchanged() {
let s = "a::b::{c, d::{self, f}};";
assert_eq!(format_use_statement_length(s, 100, 0), s);
}

#[test]
fn test_format_use_statement_length_formats_long_input() {
let s = "std::{address::*, assert::assert, block::*, chain::auth::*, context::{*,text::{call_frames::*, dial_frames::{Transaction, TransactionParameters}, token_storage::{CallData, Parameters}}}, contract_id::ContractId, hash::*, panic::panic, storage::*, token::*};";
let expected = format!(
r#"{ALREADY_FORMATTED_LINE_PATTERN}std::{{
{ALREADY_FORMATTED_LINE_PATTERN} address::*,
{ALREADY_FORMATTED_LINE_PATTERN} assert::assert,
{ALREADY_FORMATTED_LINE_PATTERN} block::*,
{ALREADY_FORMATTED_LINE_PATTERN} chain::auth::*,
{ALREADY_FORMATTED_LINE_PATTERN} context::{{
{ALREADY_FORMATTED_LINE_PATTERN} *,
{ALREADY_FORMATTED_LINE_PATTERN} text::{{
{ALREADY_FORMATTED_LINE_PATTERN} call_frames::*,
{ALREADY_FORMATTED_LINE_PATTERN} dial_frames::{{Transaction, TransactionParameters}},
{ALREADY_FORMATTED_LINE_PATTERN} token_storage::{{CallData, Parameters}}
{ALREADY_FORMATTED_LINE_PATTERN} }}
{ALREADY_FORMATTED_LINE_PATTERN} }},
{ALREADY_FORMATTED_LINE_PATTERN} contract_id::ContractId,
{ALREADY_FORMATTED_LINE_PATTERN} hash::*,
{ALREADY_FORMATTED_LINE_PATTERN} panic::panic,
{ALREADY_FORMATTED_LINE_PATTERN} storage::*,
{ALREADY_FORMATTED_LINE_PATTERN} token::*,
{ALREADY_FORMATTED_LINE_PATTERN}}};
"#
);
assert_eq!(format_use_statement_length(s, 100, 0), expected);
}
}

0 comments on commit 10a7147

Please sign in to comment.