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