Skip to content

Commit cc72008

Browse files
committed
new lint: move_format_string_arg
The name might need some improving. extract 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
1 parent 2584d48 commit cc72008

File tree

5 files changed

+192
-132
lines changed

5 files changed

+192
-132
lines changed
Lines changed: 143 additions & 95 deletions
Original file line numberDiff line numberDiff line change
@@ -1,100 +1,133 @@
1-
use ide_db::{syntax_helpers::{format_string::is_format_string, format_string_exprs::{parse_format_exprs, Arg}}, assists::{AssistId, AssistKind}};
1+
use crate::{AssistContext, Assists};
2+
use ide_db::{
3+
assists::{AssistId, AssistKind},
4+
syntax_helpers::{
5+
format_string::is_format_string,
6+
format_string_exprs::{parse_format_exprs, Arg},
7+
},
8+
};
29
use itertools::Itertools;
3-
use syntax::{ast, AstToken, AstNode, NodeOrToken, SyntaxKind::COMMA, TextRange};
10+
use syntax::{ast, AstNode, AstToken, NodeOrToken, SyntaxKind::COMMA, TextRange};
411

512
// Assist: move_format_string_arg
613
//
714
// Move an expression out of a format string.
815
//
916
// ```
17+
// macro_rules! format_args {
18+
// ($lit:literal $(tt:tt)*) => { 0 },
19+
// }
20+
// macro_rules! print {
21+
// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
22+
// }
23+
//
1024
// fn main() {
11-
// println!("{x + 1}$0");
25+
// print!("{x + 1}$0");
1226
// }
1327
// ```
1428
// ->
1529
// ```
30+
// macro_rules! format_args {
31+
// ($lit:literal $(tt:tt)*) => { 0 },
32+
// }
33+
// macro_rules! print {
34+
// ($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
35+
// }
36+
//
1637
// fn main() {
17-
// println!("{a}", a$0 = x + 1);
38+
// print!("{}"$0, x + 1);
1839
// }
1940
// ```
2041

21-
use crate::{AssistContext, /* AssistId, AssistKind, */ Assists};
22-
23-
pub(crate) fn move_format_string_arg (acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
24-
let t = ctx.find_token_at_offset::<ast::String>()?;
25-
let tt = t.syntax().parent_ancestors().find_map(ast::TokenTree::cast)?;
26-
27-
let expanded_t = ast::String::cast(ctx.sema.descend_into_macros_with_kind_preference(t.syntax().clone()))?;
42+
pub(crate) fn move_format_string_arg(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
43+
let fmt_string = ctx.find_token_at_offset::<ast::String>()?;
44+
let tt = fmt_string.syntax().parent_ancestors().find_map(ast::TokenTree::cast)?;
2845

46+
let expanded_t = ast::String::cast(
47+
ctx.sema.descend_into_macros_with_kind_preference(fmt_string.syntax().clone()),
48+
)?;
2949
if !is_format_string(&expanded_t) {
3050
return None;
3151
}
3252

33-
let target = tt.syntax().text_range();
34-
let extracted_args = parse_format_exprs(&t).ok()?;
35-
let str_range = t.syntax().text_range();
36-
37-
let tokens =
38-
tt.token_trees_and_tokens()
39-
.filter_map(NodeOrToken::into_token)
40-
.collect_vec();
41-
42-
acc.add(AssistId("move_format_string_arg", AssistKind::QuickFix), "Extract format args", target, |edit| {
43-
let mut existing_args: Vec<String> = vec![];
44-
let mut current_arg = String::new();
45-
46-
if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] = tokens.as_slice() {
47-
for t in tokens {
48-
if t.kind() == COMMA {
49-
existing_args.push(current_arg.trim().into());
50-
current_arg.clear();
51-
} else {
52-
current_arg.push_str(t.text());
53+
let (new_fmt, extracted_args) = parse_format_exprs(fmt_string.text()).ok()?;
54+
55+
acc.add(
56+
AssistId("move_format_string_arg", AssistKind::QuickFix),
57+
"Extract format args",
58+
tt.syntax().text_range(),
59+
|edit| {
60+
let fmt_range = fmt_string.syntax().text_range();
61+
62+
// Replace old format string with new format string whose arguments have been extracted
63+
edit.replace(fmt_range, new_fmt);
64+
65+
// Insert cursor at end of format string
66+
edit.insert(fmt_range.end(), "$0");
67+
68+
// Extract existing arguments in macro
69+
let tokens =
70+
tt.token_trees_and_tokens().filter_map(NodeOrToken::into_token).collect_vec();
71+
72+
let mut existing_args: Vec<String> = vec![];
73+
74+
let mut current_arg = String::new();
75+
if let [_opening_bracket, format_string, _args_start_comma, tokens @ .., end_bracket] =
76+
tokens.as_slice()
77+
{
78+
for t in tokens {
79+
if t.kind() == COMMA {
80+
existing_args.push(current_arg.trim().into());
81+
current_arg.clear();
82+
} else {
83+
current_arg.push_str(t.text());
84+
}
5385
}
86+
existing_args.push(current_arg.trim().into());
87+
88+
// delete everything after the format string till end bracket
89+
// we're going to insert the new arguments later
90+
edit.delete(TextRange::new(
91+
format_string.text_range().end(),
92+
end_bracket.text_range().start(),
93+
));
5494
}
55-
existing_args.push(current_arg.trim().into());
56-
57-
// delete everything after the format string to the end bracket
58-
// we're going to insert the new arguments later
59-
edit.delete(TextRange::new(format_string.text_range().end(), end_bracket.text_range().start()));
60-
}
61-
62-
let mut existing_args = existing_args.into_iter();
63-
64-
// insert cursor at end of format string
65-
edit.insert(str_range.end(), "$0");
66-
let mut placeholder_idx = 1;
67-
let mut args = String::new();
68-
69-
for (text, extracted_args) in extracted_args {
70-
// remove expr from format string
71-
edit.delete(text);
72-
73-
args.push_str(", ");
74-
75-
match extracted_args {
76-
Arg::Expr(s) => {
77-
// insert arg
78-
args.push_str(&s);
79-
},
80-
Arg::Placeholder => {
81-
// try matching with existing argument
82-
match existing_args.next() {
83-
Some(ea) => {
84-
args.push_str(&ea);
85-
},
86-
None => {
87-
// insert placeholder
88-
args.push_str(&format!("${placeholder_idx}"));
89-
placeholder_idx += 1;
95+
96+
// Start building the new args
97+
let mut existing_args = existing_args.into_iter();
98+
let mut args = String::new();
99+
100+
let mut placeholder_idx = 1;
101+
102+
for extracted_args in extracted_args {
103+
// remove expr from format string
104+
args.push_str(", ");
105+
106+
match extracted_args {
107+
Arg::Expr(s) => {
108+
// insert arg
109+
args.push_str(&s);
110+
}
111+
Arg::Placeholder => {
112+
// try matching with existing argument
113+
match existing_args.next() {
114+
Some(ea) => {
115+
args.push_str(&ea);
116+
}
117+
None => {
118+
// insert placeholder
119+
args.push_str(&format!("${placeholder_idx}"));
120+
placeholder_idx += 1;
121+
}
90122
}
91123
}
92124
}
93125
}
94-
}
95126

96-
edit.insert(str_range.end(), args);
97-
});
127+
// Insert new args
128+
edit.insert(fmt_range.end(), args);
129+
},
130+
);
98131

99132
Some(())
100133
}
@@ -113,97 +146,112 @@ macro_rules! print {
113146
}
114147
"#;
115148

116-
fn add_macro_decl (s: &'static str) -> String {
149+
fn add_macro_decl(s: &'static str) -> String {
117150
MACRO_DECL.to_string() + s
118151
}
119152

120153
#[test]
121154
fn multiple_middle_arg() {
122155
check_assist(
123156
move_format_string_arg,
124-
&add_macro_decl(r#"
157+
&add_macro_decl(
158+
r#"
125159
fn main() {
126160
print!("{} {x + 1:b} {}$0", y + 2, 2);
127161
}
128-
"#),
129-
130-
&add_macro_decl(r#"
162+
"#,
163+
),
164+
&add_macro_decl(
165+
r#"
131166
fn main() {
132167
print!("{} {:b} {}"$0, y + 2, x + 1, 2);
133168
}
134-
"#),
169+
"#,
170+
),
135171
);
136172
}
137173

138174
#[test]
139175
fn single_arg() {
140176
check_assist(
141177
move_format_string_arg,
142-
&add_macro_decl(r#"
178+
&add_macro_decl(
179+
r#"
143180
fn main() {
144181
print!("{obj.value:b}$0",);
145182
}
146-
"#),
147-
&add_macro_decl(r#"
183+
"#,
184+
),
185+
&add_macro_decl(
186+
r#"
148187
fn main() {
149188
print!("{:b}"$0, obj.value);
150189
}
151-
"#),
190+
"#,
191+
),
152192
);
153193
}
154194

155195
#[test]
156196
fn multiple_middle_placeholders_arg() {
157197
check_assist(
158198
move_format_string_arg,
159-
&add_macro_decl(r#"
199+
&add_macro_decl(
200+
r#"
160201
fn main() {
161202
print!("{} {x + 1:b} {} {}$0", y + 2, 2);
162203
}
163-
"#),
164-
165-
&add_macro_decl(r#"
204+
"#,
205+
),
206+
&add_macro_decl(
207+
r#"
166208
fn main() {
167209
print!("{} {:b} {} {}"$0, y + 2, x + 1, 2, $1);
168210
}
169-
"#),
211+
"#,
212+
),
170213
);
171214
}
172215

173216
#[test]
174217
fn multiple_trailing_args() {
175218
check_assist(
176219
move_format_string_arg,
177-
&add_macro_decl(r#"
220+
&add_macro_decl(
221+
r#"
178222
fn main() {
179223
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1);
180224
}
181-
"#),
182-
183-
&add_macro_decl(r#"
225+
"#,
226+
),
227+
&add_macro_decl(
228+
r#"
184229
fn main() {
185230
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
186231
}
187-
"#),
232+
"#,
233+
),
188234
);
189235
}
190236

191237
#[test]
192238
fn improper_commas() {
193239
check_assist(
194240
move_format_string_arg,
195-
&add_macro_decl(r#"
241+
&add_macro_decl(
242+
r#"
196243
fn main() {
197244
print!("{} {x + 1:b} {Struct(1, 2)}$0", 1,);
198245
}
199-
"#),
200-
201-
&add_macro_decl(r#"
246+
"#,
247+
),
248+
&add_macro_decl(
249+
r#"
202250
fn main() {
203251
print!("{} {:b} {}"$0, 1, x + 1, Struct(1, 2));
204252
}
205-
"#),
253+
"#,
254+
),
206255
);
207256
}
208-
209257
}

crates/ide-assists/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ mod handlers {
255255
merge_imports::merge_imports,
256256
merge_match_arms::merge_match_arms,
257257
move_bounds::move_bounds_to_where_clause,
258+
move_format_string_arg::move_format_string_arg,
258259
move_guard::move_arm_cond_to_match_guard,
259260
move_guard::move_guard_to_arm_body,
260261
move_module_to_file::move_module_to_file,

crates/ide-assists/src/tests/generated.rs

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1596,13 +1596,27 @@ fn doctest_move_format_string_arg() {
15961596
check_doc_test(
15971597
"move_format_string_arg",
15981598
r#####"
1599+
macro_rules! format_args {
1600+
($lit:literal $(tt:tt)*) => { 0 },
1601+
}
1602+
macro_rules! print {
1603+
($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
1604+
}
1605+
15991606
fn main() {
1600-
println!("{x + 1}$0");
1607+
print!("{x + 1}$0");
16011608
}
16021609
"#####,
16031610
r#####"
1611+
macro_rules! format_args {
1612+
($lit:literal $(tt:tt)*) => { 0 },
1613+
}
1614+
macro_rules! print {
1615+
($($arg:tt)*) => (std::io::_print(format_args!($($arg)*)));
1616+
}
1617+
16041618
fn main() {
1605-
println!("{a}", a$0 = x + 1);
1619+
print!("{}"$0, x + 1);
16061620
}
16071621
"#####,
16081622
)

0 commit comments

Comments
 (0)