aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/handlers/change_visibility.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/handlers/change_visibility.rs')
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs167
1 files changed, 167 insertions, 0 deletions
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
new file mode 100644
index 000000000..f325b6f92
--- /dev/null
+++ b/crates/ra_assists/src/handlers/change_visibility.rs
@@ -0,0 +1,167 @@
1use ra_syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{
5 ATTR, COMMENT, ENUM_DEF, FN_DEF, IDENT, MODULE, STRUCT_DEF, TRAIT_DEF, VISIBILITY,
6 WHITESPACE,
7 },
8 SyntaxNode, TextUnit, T,
9};
10
11use crate::{Assist, AssistCtx, AssistId};
12
13// Assist: change_visibility
14//
15// Adds or changes existing visibility specifier.
16//
17// ```
18// <|>fn frobnicate() {}
19// ```
20// ->
21// ```
22// pub(crate) fn frobnicate() {}
23// ```
24pub(crate) fn change_visibility(ctx: AssistCtx) -> Option<Assist> {
25 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
26 return change_vis(ctx, vis);
27 }
28 add_vis(ctx)
29}
30
31fn add_vis(ctx: AssistCtx) -> Option<Assist> {
32 let item_keyword = ctx.token_at_offset().find(|leaf| match leaf.kind() {
33 T![fn] | T![mod] | T![struct] | T![enum] | T![trait] => true,
34 _ => false,
35 });
36
37 let (offset, target) = if let Some(keyword) = item_keyword {
38 let parent = keyword.parent();
39 let def_kws = vec![FN_DEF, MODULE, STRUCT_DEF, ENUM_DEF, TRAIT_DEF];
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 {
50 let ident = ctx.token_at_offset().find(|leaf| leaf.kind() == IDENT)?;
51 let field = ident.parent().ancestors().find_map(ast::RecordFieldDef::cast)?;
52 if field.name()?.syntax().text_range() != ident.text_range() && field.visibility().is_some()
53 {
54 return None;
55 }
56 (vis_offset(field.syntax()), ident.text_range())
57 };
58
59 ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub(crate)", |edit| {
60 edit.target(target);
61 edit.insert(offset, "pub(crate) ");
62 edit.set_cursor(offset);
63 })
64}
65
66fn vis_offset(node: &SyntaxNode) -> TextUnit {
67 node.children_with_tokens()
68 .skip_while(|it| match it.kind() {
69 WHITESPACE | COMMENT | ATTR => true,
70 _ => false,
71 })
72 .next()
73 .map(|it| it.text_range().start())
74 .unwrap_or_else(|| node.text_range().start())
75}
76
77fn change_vis(ctx: AssistCtx, vis: ast::Visibility) -> Option<Assist> {
78 if vis.syntax().text() == "pub" {
79 return ctx.add_assist(
80 AssistId("change_visibility"),
81 "Change Visibility to pub(crate)",
82 |edit| {
83 edit.target(vis.syntax().text_range());
84 edit.replace(vis.syntax().text_range(), "pub(crate)");
85 edit.set_cursor(vis.syntax().text_range().start())
86 },
87 );
88 }
89 if vis.syntax().text() == "pub(crate)" {
90 return ctx.add_assist(AssistId("change_visibility"), "Change visibility to pub", |edit| {
91 edit.target(vis.syntax().text_range());
92 edit.replace(vis.syntax().text_range(), "pub");
93 edit.set_cursor(vis.syntax().text_range().start());
94 });
95 }
96 None
97}
98
99#[cfg(test)]
100mod tests {
101 use super::*;
102 use crate::helpers::{check_assist, check_assist_target};
103
104 #[test]
105 fn change_visibility_adds_pub_crate_to_items() {
106 check_assist(change_visibility, "<|>fn foo() {}", "<|>pub(crate) fn foo() {}");
107 check_assist(change_visibility, "f<|>n foo() {}", "<|>pub(crate) fn foo() {}");
108 check_assist(change_visibility, "<|>struct Foo {}", "<|>pub(crate) struct Foo {}");
109 check_assist(change_visibility, "<|>mod foo {}", "<|>pub(crate) mod foo {}");
110 check_assist(change_visibility, "<|>trait Foo {}", "<|>pub(crate) trait Foo {}");
111 check_assist(change_visibility, "m<|>od {}", "<|>pub(crate) mod {}");
112 check_assist(
113 change_visibility,
114 "unsafe f<|>n foo() {}",
115 "<|>pub(crate) unsafe fn foo() {}",
116 );
117 }
118
119 #[test]
120 fn change_visibility_works_with_struct_fields() {
121 check_assist(
122 change_visibility,
123 "struct S { <|>field: u32 }",
124 "struct S { <|>pub(crate) field: u32 }",
125 )
126 }
127
128 #[test]
129 fn change_visibility_pub_to_pub_crate() {
130 check_assist(change_visibility, "<|>pub fn foo() {}", "<|>pub(crate) fn foo() {}")
131 }
132
133 #[test]
134 fn change_visibility_pub_crate_to_pub() {
135 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "<|>pub fn foo() {}")
136 }
137
138 #[test]
139 fn change_visibility_handles_comment_attrs() {
140 check_assist(
141 change_visibility,
142 "
143 /// docs
144
145 // comments
146
147 #[derive(Debug)]
148 <|>struct Foo;
149 ",
150 "
151 /// docs
152
153 // comments
154
155 #[derive(Debug)]
156 <|>pub(crate) struct Foo;
157 ",
158 )
159 }
160
161 #[test]
162 fn change_visibility_target() {
163 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
164 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
165 check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
166 }
167}