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