]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/change_visibility.rs
Merge #6822
[rust.git] / crates / ide_assists / src / handlers / change_visibility.rs
1 use syntax::{
2     ast::{self, NameOwner, VisibilityOwner},
3     AstNode,
4     SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, VISIBILITY},
5     T,
6 };
7
8 use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
9
10 // Assist: change_visibility
11 //
12 // Adds or changes existing visibility specifier.
13 //
14 // ```
15 // $0fn frobnicate() {}
16 // ```
17 // ->
18 // ```
19 // pub(crate) fn frobnicate() {}
20 // ```
21 pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
22     if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
23         return change_vis(acc, vis);
24     }
25     add_vis(acc, ctx)
26 }
27
28 fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29     let item_keyword = ctx.token_at_offset().find(|leaf| {
30         matches!(
31             leaf.kind(),
32             T![const]
33                 | T![static]
34                 | T![fn]
35                 | T![mod]
36                 | T![struct]
37                 | T![enum]
38                 | T![trait]
39                 | T![type]
40         )
41     });
42
43     let (offset, target) = if let Some(keyword) = item_keyword {
44         let parent = keyword.parent();
45         let def_kws = vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT];
46         // Parent is not a definition, can't add visibility
47         if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
48             return None;
49         }
50         // Already have visibility, do nothing
51         if parent.children().any(|child| child.kind() == VISIBILITY) {
52             return None;
53         }
54         (vis_offset(&parent), keyword.text_range())
55     } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
56         let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
57         if field.name()? != field_name {
58             cov_mark::hit!(change_visibility_field_false_positive);
59             return None;
60         }
61         if field.visibility().is_some() {
62             return None;
63         }
64         (vis_offset(field.syntax()), field_name.syntax().text_range())
65     } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
66         if field.visibility().is_some() {
67             return None;
68         }
69         (vis_offset(field.syntax()), field.syntax().text_range())
70     } else {
71         return None;
72     };
73
74     acc.add(
75         AssistId("change_visibility", AssistKind::RefactorRewrite),
76         "Change visibility to pub(crate)",
77         target,
78         |edit| {
79             edit.insert(offset, "pub(crate) ");
80         },
81     )
82 }
83
84 fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
85     if vis.syntax().text() == "pub" {
86         let target = vis.syntax().text_range();
87         return acc.add(
88             AssistId("change_visibility", AssistKind::RefactorRewrite),
89             "Change Visibility to pub(crate)",
90             target,
91             |edit| {
92                 edit.replace(vis.syntax().text_range(), "pub(crate)");
93             },
94         );
95     }
96     if vis.syntax().text() == "pub(crate)" {
97         let target = vis.syntax().text_range();
98         return acc.add(
99             AssistId("change_visibility", AssistKind::RefactorRewrite),
100             "Change visibility to pub",
101             target,
102             |edit| {
103                 edit.replace(vis.syntax().text_range(), "pub");
104             },
105         );
106     }
107     None
108 }
109
110 #[cfg(test)]
111 mod tests {
112     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
113
114     use super::*;
115
116     #[test]
117     fn change_visibility_adds_pub_crate_to_items() {
118         check_assist(change_visibility, "$0fn foo() {}", "pub(crate) fn foo() {}");
119         check_assist(change_visibility, "f$0n foo() {}", "pub(crate) fn foo() {}");
120         check_assist(change_visibility, "$0struct Foo {}", "pub(crate) struct Foo {}");
121         check_assist(change_visibility, "$0mod foo {}", "pub(crate) mod foo {}");
122         check_assist(change_visibility, "$0trait Foo {}", "pub(crate) trait Foo {}");
123         check_assist(change_visibility, "m$0od {}", "pub(crate) mod {}");
124         check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}");
125     }
126
127     #[test]
128     fn change_visibility_works_with_struct_fields() {
129         check_assist(
130             change_visibility,
131             r"struct S { $0field: u32 }",
132             r"struct S { pub(crate) field: u32 }",
133         );
134         check_assist(change_visibility, r"struct S ( $0u32 )", r"struct S ( pub(crate) u32 )");
135     }
136
137     #[test]
138     fn change_visibility_field_false_positive() {
139         cov_mark::check!(change_visibility_field_false_positive);
140         check_assist_not_applicable(
141             change_visibility,
142             r"struct S { field: [(); { let $0x = ();}] }",
143         )
144     }
145
146     #[test]
147     fn change_visibility_pub_to_pub_crate() {
148         check_assist(change_visibility, "$0pub fn foo() {}", "pub(crate) fn foo() {}")
149     }
150
151     #[test]
152     fn change_visibility_pub_crate_to_pub() {
153         check_assist(change_visibility, "$0pub(crate) fn foo() {}", "pub fn foo() {}")
154     }
155
156     #[test]
157     fn change_visibility_const() {
158         check_assist(change_visibility, "$0const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
159     }
160
161     #[test]
162     fn change_visibility_static() {
163         check_assist(change_visibility, "$0static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
164     }
165
166     #[test]
167     fn change_visibility_type_alias() {
168         check_assist(change_visibility, "$0type T = ();", "pub(crate) type T = ();");
169     }
170
171     #[test]
172     fn change_visibility_handles_comment_attrs() {
173         check_assist(
174             change_visibility,
175             r"
176             /// docs
177
178             // comments
179
180             #[derive(Debug)]
181             $0struct Foo;
182             ",
183             r"
184             /// docs
185
186             // comments
187
188             #[derive(Debug)]
189             pub(crate) struct Foo;
190             ",
191         )
192     }
193
194     #[test]
195     fn not_applicable_for_enum_variants() {
196         check_assist_not_applicable(
197             change_visibility,
198             r"mod foo { pub enum Foo {Foo1} }
199               fn main() { foo::Foo::Foo1$0 } ",
200         );
201     }
202
203     #[test]
204     fn change_visibility_target() {
205         check_assist_target(change_visibility, "$0fn foo() {}", "fn");
206         check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
207         check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
208     }
209 }