aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/change_visibility.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/change_visibility.rs')
-rw-r--r--crates/assists/src/handlers/change_visibility.rs200
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 @@
1use syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, 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// <|>fn 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] | 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
78fn 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)]
105mod 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}