Skip to content

Commit f2ba087

Browse files
committed
Add convert_if_cfg_to_attr
```rust fn foo() { if $0cfg!(feature = "foo") { let _x = 2; } } ``` -> ```rust fn foo() { #[cfg(feature = "foo")] { let _x = 2; } } ```
1 parent f986658 commit f2ba087

File tree

3 files changed

+307
-7
lines changed

3 files changed

+307
-7
lines changed

crates/ide-assists/src/handlers/convert_attr_cfg_to_if.rs

Lines changed: 284 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,12 @@
11
use either::Either::{self, Left, Right};
22
use ide_db::assists::AssistId;
33
use itertools::Itertools;
4-
use syntax::NodeOrToken::{Node, Token};
5-
use syntax::SyntaxNode;
6-
use syntax::ast::edit_in_place::Indent;
7-
use syntax::syntax_editor::SyntaxEditor;
84
use syntax::{
9-
AstNode, SyntaxKind,
10-
ast::{self, make},
5+
AstNode,
6+
NodeOrToken::{self, Node, Token},
7+
SyntaxKind, SyntaxNode, SyntaxToken, T,
8+
ast::{self, HasAttrs, edit_in_place::Indent, make},
9+
syntax_editor::{Position, SyntaxEditor},
1110
};
1211

1312
use crate::assist_context::{AssistContext, Assists};
@@ -63,6 +62,91 @@ pub(crate) fn convert_attr_cfg_to_if(acc: &mut Assists, ctx: &AssistContext<'_>)
6362
)
6463
}
6564

65+
// Assist: convert_if_cfg_to_attr
66+
//
67+
// Convert `if cfg!(...) {}` to `#[cfg(...)] {}`.
68+
//
69+
// ```
70+
// fn foo() {
71+
// if $0cfg!(feature = "foo") {
72+
// let _x = 2;
73+
// }
74+
// }
75+
// ```
76+
// ->
77+
// ```
78+
// fn foo() {
79+
// #[cfg(feature = "foo")]
80+
// {
81+
// let _x = 2;
82+
// }
83+
// }
84+
// ```
85+
pub(crate) fn convert_if_cfg_to_attr(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
86+
let if_ = ctx.find_node_at_offset::<ast::IfExpr>()?;
87+
let ast::Expr::MacroExpr(cfg) = if_.condition()? else {
88+
return None;
89+
};
90+
let macro_call = cfg.macro_call()?;
91+
92+
let block_expr = if_.then_branch()?;
93+
let else_block = match if_.else_branch() {
94+
Some(ast::ElseBranch::Block(block)) => Some(block),
95+
None => None,
96+
Some(ast::ElseBranch::IfExpr(_)) => return None,
97+
};
98+
if macro_call.path()?.segment()?.name_ref()?.text() != "cfg"
99+
|| ctx.offset() > block_expr.stmt_list()?.l_curly_token()?.text_range().end()
100+
{
101+
return None;
102+
}
103+
let cfg_cond = macro_call.token_tree()?;
104+
105+
acc.add(
106+
AssistId::refactor_rewrite("convert_if_cfg_to_attr"),
107+
"Convert `if cfg!()` to `#[cfg()]`",
108+
macro_call.syntax().text_range(),
109+
|builder| {
110+
let mut edit = builder.make_editor(if_.syntax());
111+
112+
let indent = format!("\n{}", if_.indent_level());
113+
if let Some(else_block) = else_block {
114+
let attr_cfg_not = make_cfg(make::token_tree(
115+
T!['('],
116+
[Token(make::tokens::ident("not")), Node(cfg_cond.clone())],
117+
));
118+
edit.insert_all(
119+
Position::before(if_.syntax()),
120+
vec![
121+
make::tokens::whitespace(&indent).into(),
122+
attr_cfg_not.syntax().clone_for_update().into(),
123+
make::tokens::whitespace(&indent).into(),
124+
else_block.syntax().clone_for_update().into(),
125+
],
126+
);
127+
edit.insert_all(
128+
Position::before(if_.syntax()),
129+
indent_attributes(&if_, &indent, false).clone(),
130+
);
131+
}
132+
133+
let attr_cfg = make_cfg(cfg_cond);
134+
edit.insert_all(
135+
Position::before(if_.syntax()),
136+
vec![
137+
attr_cfg.syntax().clone_for_update().into(),
138+
make::tokens::whitespace(&indent).into(),
139+
block_expr.syntax().clone_for_update().into(),
140+
],
141+
);
142+
edit.insert_all(Position::before(if_.syntax()), indent_attributes(&if_, &indent, true));
143+
144+
edit.delete(if_.syntax());
145+
builder.add_file_edits(ctx.vfs_file_id(), edit);
146+
},
147+
)
148+
}
149+
66150
fn take_else_branch(
67151
origin: &impl AstNode,
68152
cfg_tt: ast::TokenTree,
@@ -189,9 +273,31 @@ fn is_cfg(attr: &ast::Attr) -> bool {
189273
attr.path().and_then(|p| p.as_single_name_ref()).is_some_and(|name| name.text() == "cfg")
190274
}
191275

276+
fn make_cfg(tt: ast::TokenTree) -> ast::Attr {
277+
let cfg_path = make::ext::ident_path("cfg");
278+
make::attr_outer(make::meta_token_tree(cfg_path, tt))
279+
}
280+
281+
fn indent_attributes(
282+
if_: &ast::IfExpr,
283+
indent: &str,
284+
before: bool,
285+
) -> Vec<NodeOrToken<SyntaxNode, SyntaxToken>> {
286+
if_.attrs()
287+
.flat_map(|attr| {
288+
let mut tts =
289+
[Token(make::tokens::whitespace(indent)), Node(attr.syntax().clone_for_update())];
290+
if before {
291+
tts.reverse();
292+
}
293+
tts
294+
})
295+
.collect_vec()
296+
}
297+
192298
#[cfg(test)]
193299
mod tests {
194-
use crate::tests::check_assist;
300+
use crate::tests::{check_assist, check_assist_not_applicable};
195301

196302
use super::*;
197303

@@ -561,6 +667,177 @@ mod a {
561667
};
562668
}
563669
}
670+
}
671+
"#,
672+
);
673+
}
674+
675+
#[test]
676+
fn test_convert_if_cfg_to_attr_else_block() {
677+
check_assist(
678+
convert_if_cfg_to_attr,
679+
r#"
680+
fn foo() {
681+
$0if cfg!(feature = "foo") {
682+
let x = 2;
683+
let _ = x+1;
684+
} else {
685+
let _ = 3;
686+
}
687+
// needless comment
688+
}
689+
"#,
690+
r#"
691+
fn foo() {
692+
#[cfg(feature = "foo")]
693+
{
694+
let x = 2;
695+
let _ = x+1;
696+
}
697+
#[cfg(not(feature = "foo"))]
698+
{
699+
let _ = 3;
700+
}
701+
// needless comment
702+
}
703+
"#,
704+
);
705+
}
706+
707+
#[test]
708+
fn test_convert_if_cfg_to_attr_not_applicable_after_curly() {
709+
check_assist_not_applicable(
710+
convert_if_cfg_to_attr,
711+
r#"
712+
fn foo() {
713+
if cfg!(feature = "foo") {
714+
$0let x = 2;
715+
let _ = x+1;
716+
} else {
717+
let _ = 3;
718+
}
719+
// needless comment
720+
}
721+
"#,
722+
);
723+
}
724+
725+
#[test]
726+
fn test_convert_if_cfg_to_attr_indent() {
727+
check_assist(
728+
convert_if_cfg_to_attr,
729+
r#"
730+
mod a {
731+
fn foo() {
732+
$0if cfg!(feature = "foo") {
733+
#[allow(unused)]
734+
match () {
735+
() => {
736+
todo!()
737+
},
738+
}
739+
}
740+
}
741+
}
742+
"#,
743+
r#"
744+
mod a {
745+
fn foo() {
746+
#[cfg(feature = "foo")]
747+
{
748+
#[allow(unused)]
749+
match () {
750+
() => {
751+
todo!()
752+
},
753+
}
754+
}
755+
}
756+
}
757+
"#,
758+
);
759+
760+
check_assist(
761+
convert_if_cfg_to_attr,
762+
r#"
763+
mod a {
764+
fn foo() {
765+
$0if cfg!(feature = "foo") {
766+
#[allow(unused)]
767+
match () {
768+
() => {
769+
todo!()
770+
},
771+
}
772+
} else {
773+
#[allow(unused)]
774+
match () {
775+
() => {
776+
todo!("")
777+
},
778+
}
779+
}
780+
}
781+
}
782+
"#,
783+
r#"
784+
mod a {
785+
fn foo() {
786+
#[cfg(feature = "foo")]
787+
{
788+
#[allow(unused)]
789+
match () {
790+
() => {
791+
todo!()
792+
},
793+
}
794+
}
795+
#[cfg(not(feature = "foo"))]
796+
{
797+
#[allow(unused)]
798+
match () {
799+
() => {
800+
todo!("")
801+
},
802+
}
803+
}
804+
}
805+
}
806+
"#,
807+
);
808+
}
809+
810+
#[test]
811+
fn test_convert_if_cfg_to_attr_attributes() {
812+
check_assist(
813+
convert_if_cfg_to_attr,
814+
r#"
815+
fn foo() {
816+
#[foo]
817+
#[allow(unused)]
818+
$0if cfg!(feature = "foo") {
819+
let x = 2;
820+
let _ = x+1;
821+
} else {
822+
let _ = 3;
823+
}
824+
}
825+
"#,
826+
r#"
827+
fn foo() {
828+
#[foo]
829+
#[allow(unused)]
830+
#[cfg(feature = "foo")]
831+
{
832+
let x = 2;
833+
let _ = x+1;
834+
}
835+
#[foo]
836+
#[allow(unused)]
837+
#[cfg(not(feature = "foo"))]
838+
{
839+
let _ = 3;
840+
}
564841
}
565842
"#,
566843
);

crates/ide-assists/src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -256,6 +256,7 @@ mod handlers {
256256
convert_bool_to_enum::convert_bool_to_enum,
257257
convert_closure_to_fn::convert_closure_to_fn,
258258
convert_attr_cfg_to_if::convert_attr_cfg_to_if,
259+
convert_attr_cfg_to_if::convert_if_cfg_to_attr,
259260
convert_comment_block::convert_comment_block,
260261
convert_comment_from_or_to_doc::convert_comment_from_or_to_doc,
261262
convert_for_to_while_let::convert_for_loop_to_while_let,

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

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -538,6 +538,28 @@ impl TryFrom<usize> for Thing {
538538
)
539539
}
540540

541+
#[test]
542+
fn doctest_convert_if_cfg_to_attr() {
543+
check_doc_test(
544+
"convert_if_cfg_to_attr",
545+
r#####"
546+
fn foo() {
547+
if $0cfg!(feature = "foo") {
548+
let _x = 2;
549+
}
550+
}
551+
"#####,
552+
r#####"
553+
fn foo() {
554+
#[cfg(feature = "foo")]
555+
{
556+
let _x = 2;
557+
}
558+
}
559+
"#####,
560+
)
561+
}
562+
541563
#[test]
542564
fn doctest_convert_if_to_bool_then() {
543565
check_doc_test(

0 commit comments

Comments
 (0)