1use syntax::{
2 AstNode,
3 SyntaxKind::{
4 self, ASSOC_ITEM_LIST, CONST, ENUM, FN, MACRO_DEF, MODULE, SOURCE_FILE, STATIC, STRUCT,
5 TRAIT, TYPE_ALIAS, USE, VISIBILITY,
6 },
7 SyntaxNode, T,
8 ast::{self, HasName, HasVisibility},
9};
10
11use crate::{AssistContext, AssistId, Assists, utils::vis_offset};
12
13pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
25 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
26 return change_vis(acc, vis);
27 }
28 add_vis(acc, ctx)
29}
30
31fn add_vis(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
32 let item_keyword = ctx.token_at_offset().find(|leaf| {
33 matches!(
34 leaf.kind(),
35 T![const]
36 | T![static]
37 | T![fn]
38 | T![mod]
39 | T![struct]
40 | T![enum]
41 | T![trait]
42 | T![type]
43 | T![use]
44 | T![macro]
45 )
46 });
47
48 let (offset, target) = if let Some(keyword) = item_keyword {
49 let parent = keyword.parent()?;
50
51 if !can_add(&parent) {
52 return None;
53 }
54 if parent.children().any(|child| child.kind() == VISIBILITY) {
56 return None;
57 }
58 (vis_offset(&parent), keyword.text_range())
59 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
60 let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
61 if field.name()? != field_name {
62 cov_mark::hit!(change_visibility_field_false_positive);
63 return None;
64 }
65 if field.visibility().is_some() {
66 return None;
67 }
68 check_is_not_variant(&field)?;
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 check_is_not_variant(&field)?;
75 (vis_offset(field.syntax()), field.syntax().text_range())
76 } else {
77 return None;
78 };
79
80 acc.add(
81 AssistId::refactor_rewrite("change_visibility"),
82 "Change visibility to pub(crate)",
83 target,
84 |edit| {
85 edit.insert(offset, "pub(crate) ");
86 },
87 )
88}
89
90fn can_add(node: &SyntaxNode) -> bool {
91 const LEGAL: &[SyntaxKind] =
92 &[CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT, USE, MACRO_DEF];
93
94 LEGAL.contains(&node.kind()) && {
95 let Some(p) = node.parent() else {
96 return false;
97 };
98
99 if p.kind() == ASSOC_ITEM_LIST {
100 p.parent()
101 .and_then(ast::Impl::cast)
102 .filter(|imp| imp.for_token().is_none())
106 .is_some()
107 } else {
108 matches!(p.kind(), SOURCE_FILE | MODULE)
109 }
110 }
111}
112
113fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
114 if vis.syntax().text() == "pub" {
115 let target = vis.syntax().text_range();
116 return acc.add(
117 AssistId::refactor_rewrite("change_visibility"),
118 "Change Visibility to pub(crate)",
119 target,
120 |edit| {
121 edit.replace(vis.syntax().text_range(), "pub(crate)");
122 },
123 );
124 }
125 if vis.syntax().text() == "pub(crate)" {
126 let target = vis.syntax().text_range();
127 return acc.add(
128 AssistId::refactor_rewrite("change_visibility"),
129 "Change visibility to pub",
130 target,
131 |edit| {
132 edit.replace(vis.syntax().text_range(), "pub");
133 },
134 );
135 }
136 None
137}
138
139fn check_is_not_variant(field: &impl AstNode) -> Option<()> {
140 let kind = field.syntax().parent()?.parent()?.kind();
141 (kind != SyntaxKind::VARIANT).then_some(())
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
147
148 use super::*;
149
150 #[test]
151 fn change_visibility_adds_pub_crate_to_items() {
152 check_assist(change_visibility, "$0fn foo() {}", "pub(crate) fn foo() {}");
153 check_assist(change_visibility, "f$0n foo() {}", "pub(crate) fn foo() {}");
154 check_assist(change_visibility, "$0struct Foo {}", "pub(crate) struct Foo {}");
155 check_assist(change_visibility, "$0mod foo {}", "pub(crate) mod foo {}");
156 check_assist(change_visibility, "$0trait Foo {}", "pub(crate) trait Foo {}");
157 check_assist(change_visibility, "m$0od {}", "pub(crate) mod {}");
158 check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}");
159 check_assist(change_visibility, "$0macro foo() {}", "pub(crate) macro foo() {}");
160 check_assist(change_visibility, "$0use foo;", "pub(crate) use foo;");
161 check_assist(
162 change_visibility,
163 "impl Foo { f$0n foo() {} }",
164 "impl Foo { pub(crate) fn foo() {} }",
165 );
166 check_assist(
167 change_visibility,
168 "fn bar() { impl Foo { f$0n foo() {} } }",
169 "fn bar() { impl Foo { pub(crate) fn foo() {} } }",
170 );
171 }
172
173 #[test]
174 fn change_visibility_works_with_struct_fields() {
175 check_assist(
176 change_visibility,
177 r"struct S { $0field: u32 }",
178 r"struct S { pub(crate) field: u32 }",
179 );
180 check_assist(change_visibility, r"struct S ( $0u32 )", r"struct S ( pub(crate) u32 )");
181 }
182
183 #[test]
184 fn change_visibility_field_false_positive() {
185 cov_mark::check!(change_visibility_field_false_positive);
186 check_assist_not_applicable(
187 change_visibility,
188 r"struct S { field: [(); { let $0x = ();}] }",
189 )
190 }
191
192 #[test]
193 fn change_visibility_pub_to_pub_crate() {
194 check_assist(change_visibility, "$0pub fn foo() {}", "pub(crate) fn foo() {}")
195 }
196
197 #[test]
198 fn change_visibility_pub_crate_to_pub() {
199 check_assist(change_visibility, "$0pub(crate) fn foo() {}", "pub fn foo() {}")
200 }
201
202 #[test]
203 fn change_visibility_const() {
204 check_assist(change_visibility, "$0const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
205 }
206
207 #[test]
208 fn change_visibility_static() {
209 check_assist(change_visibility, "$0static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
210 }
211
212 #[test]
213 fn change_visibility_type_alias() {
214 check_assist(change_visibility, "$0type T = ();", "pub(crate) type T = ();");
215 }
216
217 #[test]
218 fn change_visibility_handles_comment_attrs() {
219 check_assist(
220 change_visibility,
221 r"
222 /// docs
223
224 // comments
225
226 #[derive(Debug)]
227 $0struct Foo;
228 ",
229 r"
230 /// docs
231
232 // comments
233
234 #[derive(Debug)]
235 pub(crate) struct Foo;
236 ",
237 )
238 }
239
240 #[test]
241 fn not_applicable_for_enum_variants() {
242 check_assist_not_applicable(
243 change_visibility,
244 r"mod foo { pub enum Foo {Foo1} }
245 fn main() { foo::Foo::Foo1$0 } ",
246 );
247 }
248
249 #[test]
250 fn not_applicable_for_enum_variant_fields() {
251 check_assist_not_applicable(change_visibility, r"pub enum Foo { Foo1($0i32) }");
252
253 check_assist_not_applicable(change_visibility, r"pub enum Foo { Foo1 { $0n: i32 } }");
254 }
255
256 #[test]
257 fn change_visibility_target() {
258 check_assist_target(change_visibility, "$0fn foo() {}", "fn");
259 check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
260 check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
261 }
262
263 #[test]
264 fn not_applicable_for_items_within_traits() {
265 check_assist_not_applicable(change_visibility, "trait Foo { f$0n run() {} }");
266 check_assist_not_applicable(change_visibility, "trait Foo { con$0st FOO: u8 = 69; }");
267 check_assist_not_applicable(change_visibility, "impl Foo for Bar { f$0n quox() {} }");
268 }
269
270 #[test]
271 fn not_applicable_for_items_within_fns() {
272 check_assist_not_applicable(change_visibility, "fn foo() { f$0n inner() {} }");
273 check_assist_not_applicable(change_visibility, "fn foo() { unsafe f$0n inner() {} }");
274 check_assist_not_applicable(change_visibility, "fn foo() { const f$0n inner() {} }");
275 check_assist_not_applicable(change_visibility, "fn foo() { con$0st FOO: u8 = 69; }");
276 check_assist_not_applicable(change_visibility, "fn foo() { en$0um Foo {} }");
277 check_assist_not_applicable(change_visibility, "fn foo() { stru$0ct Foo {} }");
278 check_assist_not_applicable(change_visibility, "fn foo() { mo$0d foo {} }");
279 check_assist_not_applicable(change_visibility, "fn foo() { $0use foo; }");
280 check_assist_not_applicable(change_visibility, "fn foo() { $0type Foo = Bar<T>; }");
281 check_assist_not_applicable(change_visibility, "fn foo() { tr$0ait Foo {} }");
282 check_assist_not_applicable(
283 change_visibility,
284 "fn foo() { impl Trait for Bar { f$0n bar() {} } }",
285 );
286 check_assist_not_applicable(
287 change_visibility,
288 "fn foo() { impl Trait for Bar { con$0st FOO: u8 = 69; } }",
289 );
290 }
291}