Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Reorder import items of a use statement lexicographically. #797

Merged
merged 6 commits into from
Feb 16, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 15 additions & 5 deletions sway-fmt/src/fmt.rs
Original file line number Diff line number Diff line change
Expand Up @@ -645,17 +645,27 @@ fn one_liner() -> bool {
#[test]
// Test that the use statements with multiple imports are properly formatted
fn test_use_statement() {
let expected_sway = r#"script;
let test_sway = r#"script;
use std::chain::{panic,log_u8};
use std::chain::assert;
use std::hash::{HashMethod,hash_value,hash_pair};
use a::b::{c,d::{e,f}};
use a::b::{c,d::{self,f}};
use std::hash::{HashMethod, hash_value, hash_pair };
use a::b::{c,d::{f,e}};
use a::b::{c,d::{f,self}};

fn main() {
}
"#;
let expected_sway = r#"script;
use std::chain::{log_u8, panic};
use std::chain::assert;
use std::hash::{HashMethod, hash_pair, hash_value};
use a::b::{c, d::{e, f}};
use a::b::{c, d::{self, f}};

fn main() {
}
"#;
let result = get_formatted_data(expected_sway.into(), OPTIONS);
let result = get_formatted_data(test_sway.into(), OPTIONS);
assert!(result.is_ok());
let (_, formatted_code) = result.unwrap();
assert_eq!(formatted_code, expected_sway);
Expand Down
95 changes: 94 additions & 1 deletion sway-fmt/src/traversal_helper.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::code_builder_helpers::{is_comment, is_multiline_comment, is_newline_i
use crate::code_builder_helpers::clean_all_whitespace;
use crate::constants::{ALREADY_FORMATTED_LINE_PATTERN, NEW_LINE_PATTERN};
use std::iter::{Enumerate, Peekable};
use std::slice::Iter;
use std::str::Chars;
use sway_core::{extract_keyword, Rule};

Expand Down Expand Up @@ -84,10 +85,71 @@ 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 {
sagunb marked this conversation as resolved.
Show resolved Hide resolved
/// 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 {
buffer.push(line[current..index].to_string());
}
buffer.push(separator.to_string());
current = index + separator.len();
}
if current < line.len() {
buffer.push(line[current..].to_string());
}
buffer
}
let tokens: Vec<String> = tokenize(line);
let mut buffer: Vec<String> = Vec::new();

fn sort_imports(tokens: &mut Iter<String>, buffer: &mut Vec<String>) {
let token = tokens.next();
match token.map(|t| t.trim()) {
None => return,
Some(",") => (),
Some("{") => {
let mut inner_buffer: Vec<String> = Vec::new();
sort_imports(tokens, &mut inner_buffer);
if !inner_buffer.is_empty() {
if let Some(buff) = buffer.last_mut() {
buff.push_str(inner_buffer[0].as_str());
} else {
buffer.append(&mut inner_buffer);
}
}
}
Some("}") => {
buffer.sort_by(|a, b| {
if *a == "self" {
std::cmp::Ordering::Less
sagunb marked this conversation as resolved.
Show resolved Hide resolved
} else if *b == "self" {
std::cmp::Ordering::Greater
} else {
a.cmp(b)
}
});
if buffer.len() > 1 {
*buffer = vec![format!("{{{}}}", buffer.join(", "))];
}
return;
}
Some(c) => buffer.push(c.to_string()),
}
sort_imports(tokens, buffer);
}
sort_imports(&mut tokens.iter(), &mut buffer);
buffer.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 = right.chars().filter(|c| !c.is_whitespace()).collect();
let right: String = sort_and_filter_use_expression(right);
format!(
"{}{} {}",
ALREADY_FORMATTED_LINE_PATTERN, use_keyword, right
Expand Down Expand Up @@ -163,3 +225,34 @@ fn get_data_field_type(line: &str, iter: &mut Peekable<Enumerate<Chars>>) -> Str

result
}

#[cfg(test)]
mod tests {
use super::sort_and_filter_use_expression;

#[test]
fn test_sort_and_filter_use_expression() {
assert_eq!(sort_and_filter_use_expression("::a::b::c;"), "::a::b::c;");
assert_eq!(
sort_and_filter_use_expression("::a::c::b::{c, b, ba};"),
"::a::c::b::{b, ba, c};"
);
assert_eq!(
sort_and_filter_use_expression("{s,e,l,f,self};"),
"{self, e, f, l, s};"
);
assert_eq!(
sort_and_filter_use_expression("a::{d::{f, self}, c, b};"),
"a::{b, c, d::{self, f}};"
);
assert_eq!(
sort_and_filter_use_expression("a::b::{c,d::{self,f}};"),
"a::b::{c, d::{self, f}};"
);
assert_eq!(sort_and_filter_use_expression("a::b::{c};"), "a::b::c;");
assert_eq!(
sort_and_filter_use_expression("a::b::{c,d::{e}};"),
"a::b::{c, d::e};"
);
}
}