|
1 | 1 | use either::Either::{self, Left, Right};
|
2 | 2 | use ide_db::assists::AssistId;
|
3 | 3 | 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; |
8 | 4 | 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}, |
11 | 10 | };
|
12 | 11 |
|
13 | 12 | use crate::assist_context::{AssistContext, Assists};
|
@@ -63,6 +62,94 @@ pub(crate) fn convert_attr_cfg_to_if(acc: &mut Assists, ctx: &AssistContext<'_>)
|
63 | 62 | )
|
64 | 63 | }
|
65 | 64 |
|
| 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 | + |
66 | 153 | fn take_else_branch(
|
67 | 154 | origin: &impl AstNode,
|
68 | 155 | cfg_tt: ast::TokenTree,
|
@@ -189,9 +276,31 @@ fn is_cfg(attr: &ast::Attr) -> bool {
|
189 | 276 | attr.path().and_then(|p| p.as_single_name_ref()).is_some_and(|name| name.text() == "cfg")
|
190 | 277 | }
|
191 | 278 |
|
| 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 | + |
192 | 301 | #[cfg(test)]
|
193 | 302 | mod tests {
|
194 |
| - use crate::tests::check_assist; |
| 303 | + use crate::tests::{check_assist, check_assist_not_applicable}; |
195 | 304 |
|
196 | 305 | use super::*;
|
197 | 306 |
|
@@ -561,6 +670,177 @@ mod a {
|
561 | 670 | };
|
562 | 671 | }
|
563 | 672 | }
|
| 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 | + } |
564 | 844 | }
|
565 | 845 | "#,
|
566 | 846 | );
|
|
0 commit comments