aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/change_visibility.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/change_visibility.rs')
-rw-r--r--crates/ide_assists/src/handlers/change_visibility.rs212
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 @@
1use syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, VISIBILITY},
5 T,
6};
7use test_utils::mark;
8
9use 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// ```
22pub(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
29fn 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
85fn 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)]
112mod 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}