Skip to content

Commit

Permalink
Auto merge of #13216 - DesmondWillowbrook:move_format_string_arg, r=D…
Browse files Browse the repository at this point in the history
…esmondWillowbrook

New assist: move_format_string_arg

The name might need some improving.

```rust
fn main() {
    print!("{x + 1}");
}
```
to
```rust
fn main() {
    print!("{}"$0, x + 1);
}
```

fixes #13180

ref to #5988 for similar work

* extracted `format_like`'s parser to it's own module in `ide-db`
* reworked the parser's API to be more direct
* added assist to extract expressions in format args
  • Loading branch information
bors committed Sep 12, 2022
2 parents b1a4ba3 + 54e9324 commit f64c956
Show file tree
Hide file tree
Showing 6 changed files with 581 additions and 235 deletions.
268 changes: 268 additions & 0 deletions crates/ide-assists/src/handlers/move_format_string_arg.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
use crate::{AssistContext, Assists};
use ide_db::{
assists::{AssistId, AssistKind},
syntax_helpers::{
format_string::is_format_string,
format_string_exprs::{parse_format_exprs, Arg},
},
};
use itertools::Itertools;
use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};

// Assist: move_format_string_arg
//
// Move an expression out of a format string.
//
// ```
// macro_rules! format_args {
// ($lit:literal $(tt:tt)*) => { 0 },
// }
// macro_rules! print {
// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
// }
//
// fn main() {
// print!("{x + 1}$0");
// }
// ```
// ->
// ```
// macro_rules! format_args {
// ($lit:literal $(tt:tt)*) => { 0 },
// }
// macro_rules! print {
// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
// }
//
// fn main() {
// print!("{}"$0, x + 1);
// }
// ```

pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
let tt = fmt_string.syntax().parent().and_then(ast::TokenTree::cast)?;

let expanded_t = ast::String::cast(
ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
)?;
if !is_format_string(&expanded_t) {
return None;
}

let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
if extracted_args.is_empty() {
return None;
}

acc.add(
AssistId(
"move_format_string_arg",
// if there aren't any expressions, then make the assist a RefactorExtract
if extracted_args.iter().filter(|f| matches!(f, Arg::Expr(_))).count() == 0 {
AssistKind::RefactorExtract
} else {
AssistKind::QuickFix
},
),
"Extract format args",
tt.syntax().text_range(),
|edit| {
let fmt_range = fmt_string.syntax().text_range();

// Replace old format string with new format string whose arguments have been extracted
edit.replace(fmt_range, new_fmt);

// Insert cursor at end of format string
edit.insert(fmt_range.end(), "$0");

// Extract existing arguments in macro
let tokens =
tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec();

let mut existing_args: Vec<String> = vec![];

let mut current_arg = String::new();
if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] =
tokens.as_slice()
{
for t in tokens {
if t.kind() == COMMA {
existing_args.push(current_arg.trim().into());
current_arg.clear();
} else {
current_arg.push_str(t.text());
}
}
existing_args.push(current_arg.trim().into());

// delete everything after the format string till end bracket
// we're going to insert the new arguments later
edit.delete(TextRange::new(
format_string.text_range().end(),
end_bracket.text_range().start(),
));
}

// Start building the new args
let mut existing_args = existing_args.into_iter();
let mut args = String::new();

let mut placeholder_idx = 1;

for extracted_args in extracted_args {
// remove expr from format string
args.push_str(", ");

match extracted_args {
Arg::Ident(s) | Arg::Expr(s) => {
// insert arg
args.push_str(&s);
}
Arg::Placeholder => {
// try matching with existing argument
match existing_args.next() {
Some(ea) => {
args.push_str(&ea);
}
None => {
// insert placeholder
args.push_str(&format!("${placeholder_idx}"));
placeholder_idx += 1;
}
}
}
}
}

// Insert new args
edit.insert(fmt_range.end(), args);
},
);

Some(())
}

#[cfg(test)]
mod tests {
use super::*;
use crate::tests::check_assist;

const MACRO_DECL: &'static str = r#"
macro_rules! format_args {
($lit:literal $(tt:tt)*) => { 0 },
}
macro_rules! print {
($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
}
"#;

fn add_macro_decl(s: &'static str) -> String {
MACRO_DECL.to_string() + s
}

#[test]
fn multiple_middle_arg() {
check_assist(
move_format_string_arg,
&add_macro_decl(
r#"
fn main() {
print!("{} {x + 1:b} {}$0", y + 2, 2);
}
"#,
),
&add_macro_decl(
r#"
fn main() {
print!("{} {:b} {}"$0, y + 2, x + 1, 2);
}
"#,
),
);
}

#[test]
fn single_arg() {
check_assist(
move_format_string_arg,
&add_macro_decl(
r#"
fn main() {
print!("{obj.value:b}$0",);
}
"#,
),
&add_macro_decl(
r#"
fn main() {
print!("{:b}"$0, obj.value);
}
"#,
),
);
}

#[test]
fn multiple_middle_placeholders_arg() {
check_assist(
move_format_string_arg,
&add_macro_decl(
r#"
fn main() {
print!("{} {x + 1:b} {} {}$0", y + 2, 2);
}
"#,
),
&add_macro_decl(
r#"
fn main() {
print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
}
"#,
),
);
}

#[test]
fn multiple_trailing_args() {
check_assist(
move_format_string_arg,
&add_macro_decl(
r#"
fn main() {
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
}
"#,
),
&add_macro_decl(
r#"
fn main() {
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
}
"#,
),
);
}

#[test]
fn improper_commas() {
check_assist(
move_format_string_arg,
&add_macro_decl(
r#"
fn main() {
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
}
"#,
),
&add_macro_decl(
r#"
fn main() {
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
}
"#,
),
);
}
}
2 changes: 2 additions & 0 deletions crates/ide-assists/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ mod handlers {
mod flip_binexpr;
mod flip_comma;
mod flip_trait_bound;
mod move_format_string_arg;
mod generate_constant;
mod generate_default_from_enum_variant;
mod generate_default_from_new;
Expand Down Expand Up @@ -254,6 +255,7 @@ mod handlers {
merge_imports::merge_imports,
merge_match_arms::merge_match_arms,
move_bounds::move_bounds_to_where_clause,
move_format_string_arg::move_format_string_arg,
move_guard::move_arm_cond_to_match_guard,
move_guard::move_guard_to_arm_body,
move_module_to_file::move_module_to_file,
Expand Down
31 changes: 31 additions & 0 deletions crates/ide-assists/src/tests/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1591,6 +1591,37 @@ fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
)
}

#[test]
fn doctest_move_format_string_arg() {
check_doc_test(
"move_format_string_arg",
r#####"
macro_rules! format_args {
($lit:literal $(tt:tt)*) => { 0 },
}
macro_rules! print {
($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
}
fn main() {
print!("{x + 1}$0");
}
"#####,
r#####"
macro_rules! format_args {
($lit:literal $(tt:tt)*) => { 0 },
}
macro_rules! print {
($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
}
fn main() {
print!("{}"$0, x + 1);
}
"#####,
)
}

#[test]
fn doctest_move_from_mod_rs() {
check_doc_test(
Expand Down
Loading

0 comments on commit f64c956

Please sign in to comment.