Skip to content

Commit 2d81b62

Browse files
committed
Add convert_if_cfg_to_attr
1 parent f986658 commit 2d81b62

File tree

3 files changed

+310
-7
lines changed

3 files changed

+310
-7
lines changed

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

Lines changed: 287 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,94 @@ 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 `#[cfg(...)] {}` to `if 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(_)) => {
97+
cov_mark::hit!(has_else_if);
98+
return None;
99+
}
100+
};
101+
if macro_call.path()?.segment()?.name_ref()?.text() != "cfg"
102+
|| ctx.offset() > block_expr.stmt_list()?.l_curly_token()?.text_range().end()
103+
{
104+
return None;
105+
}
106+
let cfg_cond = macro_call.token_tree()?;
107+
108+
acc.add(
109+
AssistId::refactor_rewrite("convert_if_cfg_to_attr"),
110+
"Convert `if cfg!()` to `#[cfg()]`",
111+
macro_call.syntax().text_range(),
112+
|builder| {
113+
let mut edit = builder.make_editor(if_.syntax());
114+
115+
let indent = format!("\n{}", if_.indent_level());
116+
if let Some(else_block) = else_block {
117+
let attr_cfg_not = make_cfg(make::token_tree(
118+
T!['('],
119+
[Token(make::tokens::ident("not")), Node(cfg_cond.clone())],
120+
));
121+
edit.insert_all(
122+
Position::before(if_.syntax()),
123+
vec![
124+
make::tokens::whitespace(&indent).into(),
125+
attr_cfg_not.syntax().clone_for_update().into(),
126+
make::tokens::whitespace(&indent).into(),
127+
else_block.syntax().clone_for_update().into(),
128+
],
129+
);
130+
edit.insert_all(
131+
Position::before(if_.syntax()),
132+
indent_attributes(&if_, &indent, false).clone(),
133+
);
134+
}
135+
136+
let attr_cfg = make_cfg(cfg_cond);
137+
edit.insert_all(
138+
Position::before(if_.syntax()),
139+
vec![
140+
attr_cfg.syntax().clone_for_update().into(),
141+
make::tokens::whitespace(&indent).into(),
142+
block_expr.syntax().clone_for_update().into(),
143+
],
144+
);
145+
edit.insert_all(Position::before(if_.syntax()), indent_attributes(&if_, &indent, true));
146+
147+
edit.delete(if_.syntax());
148+
builder.add_file_edits(ctx.vfs_file_id(), edit);
149+
},
150+
)
151+
}
152+
66153
fn take_else_branch(
67154
origin: &impl AstNode,
68155
cfg_tt: ast::TokenTree,
@@ -189,9 +276,31 @@ fn is_cfg(attr: &ast::Attr) -> bool {
189276
attr.path().and_then(|p| p.as_single_name_ref()).is_some_and(|name| name.text() == "cfg")
190277
}
191278

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

196305
use super::*;
197306

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

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)