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