diff options
author | Dmitry <[email protected]> | 2020-08-14 19:32:05 +0100 |
---|---|---|
committer | Dmitry <[email protected]> | 2020-08-14 19:32:05 +0100 |
commit | 178c3e135a2a249692f7784712492e7884ae0c00 (patch) | |
tree | ac6b769dbf7162150caa0c1624786a4dd79ff3be /crates/assists/src/handlers/change_visibility.rs | |
parent | 06ff8e6c760ff05f10e868b5d1f9d79e42fbb49c (diff) | |
parent | c2594daf2974dbd4ce3d9b7ec72481764abaceb5 (diff) |
Merge remote-tracking branch 'origin/master'
Diffstat (limited to 'crates/assists/src/handlers/change_visibility.rs')
-rw-r--r-- | crates/assists/src/handlers/change_visibility.rs | 200 |
1 files changed, 200 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/change_visibility.rs b/crates/assists/src/handlers/change_visibility.rs new file mode 100644 index 000000000..32dc05378 --- /dev/null +++ b/crates/assists/src/handlers/change_visibility.rs | |||
@@ -0,0 +1,200 @@ | |||
1 | use syntax::{ | ||
2 | ast::{self, NameOwner, VisibilityOwner}, | ||
3 | AstNode, | ||
4 | SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, 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 | // <|>fn 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] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait] | ||
34 | ) | ||
35 | }); | ||
36 | |||
37 | let (offset, target) = if let Some(keyword) = item_keyword { | ||
38 | let parent = keyword.parent(); | ||
39 | let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT]; | ||
40 | // Parent is not a definition, can't add visibility | ||
41 | if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) { | ||
42 | return None; | ||
43 | } | ||
44 | // Already have visibility, do nothing | ||
45 | if parent.children().any(|child| child.kind() == VISIBILITY) { | ||
46 | return None; | ||
47 | } | ||
48 | (vis_offset(&parent), keyword.text_range()) | ||
49 | } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() { | ||
50 | let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?; | ||
51 | if field.name()? != field_name { | ||
52 | mark::hit!(change_visibility_field_false_positive); | ||
53 | return None; | ||
54 | } | ||
55 | if field.visibility().is_some() { | ||
56 | return None; | ||
57 | } | ||
58 | (vis_offset(field.syntax()), field_name.syntax().text_range()) | ||
59 | } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() { | ||
60 | if field.visibility().is_some() { | ||
61 | return None; | ||
62 | } | ||
63 | (vis_offset(field.syntax()), field.syntax().text_range()) | ||
64 | } else { | ||
65 | return None; | ||
66 | }; | ||
67 | |||
68 | acc.add( | ||
69 | AssistId("change_visibility", AssistKind::RefactorRewrite), | ||
70 | "Change visibility to pub(crate)", | ||
71 | target, | ||
72 | |edit| { | ||
73 | edit.insert(offset, "pub(crate) "); | ||
74 | }, | ||
75 | ) | ||
76 | } | ||
77 | |||
78 | fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> { | ||
79 | if vis.syntax().text() == "pub" { | ||
80 | let target = vis.syntax().text_range(); | ||
81 | return acc.add( | ||
82 | AssistId("change_visibility", AssistKind::RefactorRewrite), | ||
83 | "Change Visibility to pub(crate)", | ||
84 | target, | ||
85 | |edit| { | ||
86 | edit.replace(vis.syntax().text_range(), "pub(crate)"); | ||
87 | }, | ||
88 | ); | ||
89 | } | ||
90 | if vis.syntax().text() == "pub(crate)" { | ||
91 | let target = vis.syntax().text_range(); | ||
92 | return acc.add( | ||
93 | AssistId("change_visibility", AssistKind::RefactorRewrite), | ||
94 | "Change visibility to pub", | ||
95 | target, | ||
96 | |edit| { | ||
97 | edit.replace(vis.syntax().text_range(), "pub"); | ||
98 | }, | ||
99 | ); | ||
100 | } | ||
101 | None | ||
102 | } | ||
103 | |||
104 | #[cfg(test)] | ||
105 | mod tests { | ||
106 | use test_utils::mark; | ||
107 | |||
108 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | ||
109 | |||
110 | use super::*; | ||
111 | |||
112 | #[test] | ||
113 | fn change_visibility_adds_pub_crate_to_items() { | ||
114 | check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}"); | ||
115 | check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}"); | ||
116 | check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}"); | ||
117 | check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}"); | ||
118 | check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}"); | ||
119 | check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}"); | ||
120 | check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}"); | ||
121 | } | ||
122 | |||
123 | #[test] | ||
124 | fn change_visibility_works_with_struct_fields() { | ||
125 | check_assist( | ||
126 | change_visibility, | ||
127 | r"struct S { <|>field: u32 }", | ||
128 | r"struct S { pub(crate) field: u32 }", | ||
129 | ); | ||
130 | check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )"); | ||
131 | } | ||
132 | |||
133 | #[test] | ||
134 | fn change_visibility_field_false_positive() { | ||
135 | mark::check!(change_visibility_field_false_positive); | ||
136 | check_assist_not_applicable( | ||
137 | change_visibility, | ||
138 | r"struct S { field: [(); { let <|>x = ();}] }", | ||
139 | ) | ||
140 | } | ||
141 | |||
142 | #[test] | ||
143 | fn change_visibility_pub_to_pub_crate() { | ||
144 | check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}") | ||
145 | } | ||
146 | |||
147 | #[test] | ||
148 | fn change_visibility_pub_crate_to_pub() { | ||
149 | check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}") | ||
150 | } | ||
151 | |||
152 | #[test] | ||
153 | fn change_visibility_const() { | ||
154 | check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;"); | ||
155 | } | ||
156 | |||
157 | #[test] | ||
158 | fn change_visibility_static() { | ||
159 | check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;"); | ||
160 | } | ||
161 | |||
162 | #[test] | ||
163 | fn change_visibility_handles_comment_attrs() { | ||
164 | check_assist( | ||
165 | change_visibility, | ||
166 | r" | ||
167 | /// docs | ||
168 | |||
169 | // comments | ||
170 | |||
171 | #[derive(Debug)] | ||
172 | <|>struct Foo; | ||
173 | ", | ||
174 | r" | ||
175 | /// docs | ||
176 | |||
177 | // comments | ||
178 | |||
179 | #[derive(Debug)] | ||
180 | pub(crate) struct Foo; | ||
181 | ", | ||
182 | ) | ||
183 | } | ||
184 | |||
185 | #[test] | ||
186 | fn not_applicable_for_enum_variants() { | ||
187 | check_assist_not_applicable( | ||
188 | change_visibility, | ||
189 | r"mod foo { pub enum Foo {Foo1} } | ||
190 | fn main() { foo::Foo::Foo1<|> } ", | ||
191 | ); | ||
192 | } | ||
193 | |||
194 | #[test] | ||
195 | fn change_visibility_target() { | ||
196 | check_assist_target(change_visibility, "<|>fn foo() {}", "fn"); | ||
197 | check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)"); | ||
198 | check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field"); | ||
199 | } | ||
200 | } | ||