]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/change_visibility.rs
6d9a4eec2fbf46dbd2dd08f96b2146a45b45a2c7
[rust.git] / crates / ra_assists / src / change_visibility.rs
1 use hir::db::HirDatabase;
2 use ra_syntax::{
3     AstNode, SyntaxNode, TextUnit,
4     ast::{self, VisibilityOwner, NameOwner},
5     SyntaxKind::{VISIBILITY, FN_KW, MOD_KW, STRUCT_KW, ENUM_KW, TRAIT_KW, FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF, IDENT, WHITESPACE, COMMENT, ATTR},
6 };
7
8 use crate::{AssistCtx, Assist};
9
10 pub(crate) fn change_visibility(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11     if let Some(vis) = ctx.node_at_offset::<ast::Visibility>() {
12         return change_vis(ctx, vis);
13     }
14     add_vis(ctx)
15 }
16
17 fn add_vis(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
18     let item_keyword = ctx.leaf_at_offset().find(|leaf| match leaf.kind() {
19         FN_KW | MOD_KW | STRUCT_KW | ENUM_KW | TRAIT_KW => true,
20         _ => false,
21     });
22
23     let (offset, target) = if let Some(keyword) = item_keyword {
24         let parent = keyword.parent()?;
25         let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
26         // Parent is not a definition, can't add visibility
27         if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
28             return None;
29         }
30         // Already have visibility, do nothing
31         if parent.children().any(|child| child.kind() == VISIBILITY) {
32             return None;
33         }
34         (vis_offset(parent), keyword.range())
35     } else {
36         let ident = ctx.leaf_at_offset().find(|leaf| leaf.kind() == IDENT)?;
37         let field = ident.ancestors().find_map(ast::NamedFieldDef::cast)?;
38         if field.name()?.syntax().range() != ident.range() && field.visibility().is_some() {
39             return None;
40         }
41         (vis_offset(field.syntax()), ident.range())
42     };
43
44     ctx.build("make pub(crate)", |edit| {
45         edit.target(target);
46         edit.insert(offset, "pub(crate) ");
47         edit.set_cursor(offset);
48     })
49 }
50
51 fn vis_offset(node: &SyntaxNode) -> TextUnit {
52     node.children()
53         .skip_while(|it| match it.kind() {
54             WHITESPACE | COMMENT | ATTR => true,
55             _ => false,
56         })
57         .next()
58         .map(|it| it.range().start())
59         .unwrap_or(node.range().start())
60 }
61
62 fn change_vis(ctx: AssistCtx<impl HirDatabase>, vis: &ast::Visibility) -> Option<Assist> {
63     if vis.syntax().text() == "pub" {
64         return ctx.build("change to pub(crate)", |edit| {
65             edit.target(vis.syntax().range());
66             edit.replace(vis.syntax().range(), "pub(crate)");
67             edit.set_cursor(vis.syntax().range().start());
68         });
69     }
70     if vis.syntax().text() == "pub(crate)" {
71         return ctx.build("change to pub", |edit| {
72             edit.target(vis.syntax().range());
73             edit.replace(vis.syntax().range(), "pub");
74             edit.set_cursor(vis.syntax().range().start());
75         });
76     }
77     None
78 }
79
80 #[cfg(test)]
81 mod tests {
82     use super::*;
83     use crate::helpers::{check_assist, check_assist_target};
84
85     #[test]
86     fn change_visibility_adds_pub_crate_to_items() {
87         check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}");
88         check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}");
89         check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}");
90         check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}");
91         check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}");
92         check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}");
93         check_assist(
94             change_visibility,
95             "unsafe f<|>n foo() {}",
96             "<|>pub(crate) unsafe fn foo() {}",
97         );
98     }
99
100     #[test]
101     fn change_visibility_works_with_struct_fields() {
102         check_assist(
103             change_visibility,
104             "struct S { <|>field: u32 }",
105             "struct S { <|>pub(crate) field: u32 }",
106         )
107     }
108
109     #[test]
110     fn change_visibility_pub_to_pub_crate() {
111         check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}")
112     }
113
114     #[test]
115     fn change_visibility_pub_crate_to_pub() {
116         check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}")
117     }
118
119     #[test]
120     fn change_visibility_handles_comment_attrs() {
121         check_assist(
122             change_visibility,
123             "
124             /// docs
125
126             // comments
127
128             #[derive(Debug)]
129             <|>struct Foo;
130             ",
131             "
132             /// docs
133
134             // comments
135
136             #[derive(Debug)]
137             <|>pub(crate) struct Foo;
138             ",
139         )
140     }
141
142     #[test]
143     fn change_visibility_target() {
144         check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
145         check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
146         check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
147     }
148 }