diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2021-02-10 10:32:36 +0000 |
---|---|---|
committer | GitHub <[email protected]> | 2021-02-10 10:32:36 +0000 |
commit | 5e39d7a68032ba40d3ed76d41d0f01ad628f5c95 (patch) | |
tree | 2cee8cecf4214092f42dbee3432afa944e219671 /crates/assists/src | |
parent | ff5ef2830c4cc6bf4116b99b440885bf0c94b459 (diff) | |
parent | e8d7bcc35507425f384cff25feb564ac41a5c5a7 (diff) |
Merge #7617
7617: Add getter/setter assists r=Veykril a=yoshuawuyts
This patch makes progress towards the design outlined in https://github.com/rust-analyzer/rust-analyzer/issues/5943, and includes a small refactor which closes https://github.com/rust-analyzer/rust-analyzer/issues/7607. All together this patch does 4 things:
- Adds a `generate_getter` assist.
- Adds a `generate_getter_mut` assist.
- Adds a `generate_setter` assist.
- Moves the `generate_impl_text` function from `generate_new` into `utils` (which closes #7607).
## Design Notes
I've chosen to follow the [Rust API guidelines on getters](https://rust-lang.github.io/api-guidelines/naming.html#getter-names-follow-rust-convention-c-getter) as closely as possible. This deliberately leaves "builder pattern"-style setters out of scope.
Also, similar to https://github.com/rust-analyzer/rust-analyzer/pull/7570 this assist generates doc comments. I think this should work well in most cases, and for the few where it doesn't it's probably easily edited. This makes it slightly less correct than the #7570 implementation, but I think this is still useful enough to include for many of the same reasons.
The reason why this PR contains 3 assists, rather than 1, is because each of them is so similar to the others that it felt more noisy to do them separately than all at once. The amount of code added does not necessarily reflect that, but hope that still makes sense.
## Examples
**Input**
```rust
struct Person {
name: String, // <- cursor on "name"
}
```
**generate getter**
```rust
struct Person {
name: String,
}
impl Person {
/// Get a reference to the person's name.
fn name(&self) -> &String {
&self.name
}
}
```
**generate mut getter**
```rust
struct Person {
name: String,
}
impl Person {
/// Get a mutable reference to the person's name.
fn name_mut(&mut self) -> &mut String {
&mut self.name
}
}
```
**generate setter**
```rust
struct Person {
name: String,
}
impl Person {
/// Set the person's name.
fn set_name(&mut self, name: String) {
self.name = name;
}
}
```
Co-authored-by: Yoshua Wuyts <[email protected]>
Diffstat (limited to 'crates/assists/src')
-rw-r--r-- | crates/assists/src/handlers/generate_enum_match_method.rs | 14 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_getter.rs | 156 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_getter_mut.rs | 159 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_new.rs | 69 | ||||
-rw-r--r-- | crates/assists/src/handlers/generate_setter.rs | 162 | ||||
-rw-r--r-- | crates/assists/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/assists/src/tests.rs | 3 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 71 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 31 |
9 files changed, 603 insertions, 68 deletions
diff --git a/crates/assists/src/handlers/generate_enum_match_method.rs b/crates/assists/src/handlers/generate_enum_match_method.rs index 9d6b161c9..c3ff38b66 100644 --- a/crates/assists/src/handlers/generate_enum_match_method.rs +++ b/crates/assists/src/handlers/generate_enum_match_method.rs | |||
@@ -4,7 +4,7 @@ use syntax::ast::{self, AstNode, NameOwner}; | |||
4 | use test_utils::mark; | 4 | use test_utils::mark; |
5 | 5 | ||
6 | use crate::{ | 6 | use crate::{ |
7 | utils::{find_impl_block, find_struct_impl}, | 7 | utils::{find_impl_block, find_struct_impl, generate_impl_text}, |
8 | AssistContext, AssistId, AssistKind, Assists, | 8 | AssistContext, AssistId, AssistKind, Assists, |
9 | }; | 9 | }; |
10 | 10 | ||
@@ -82,7 +82,7 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) | |||
82 | let start_offset = impl_def | 82 | let start_offset = impl_def |
83 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) | 83 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) |
84 | .unwrap_or_else(|| { | 84 | .unwrap_or_else(|| { |
85 | buf = generate_impl_text(&parent_enum, &buf); | 85 | buf = generate_impl_text(&ast::Adt::Enum(parent_enum.clone()), &buf); |
86 | parent_enum.syntax().text_range().end() | 86 | parent_enum.syntax().text_range().end() |
87 | }); | 87 | }); |
88 | 88 | ||
@@ -91,16 +91,6 @@ pub(crate) fn generate_enum_match_method(acc: &mut Assists, ctx: &AssistContext) | |||
91 | ) | 91 | ) |
92 | } | 92 | } |
93 | 93 | ||
94 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
95 | // parameters | ||
96 | fn generate_impl_text(strukt: &ast::Enum, code: &str) -> String { | ||
97 | let mut buf = String::with_capacity(code.len()); | ||
98 | buf.push_str("\n\nimpl "); | ||
99 | buf.push_str(strukt.name().unwrap().text()); | ||
100 | format_to!(buf, " {{\n{}\n}}", code); | ||
101 | buf | ||
102 | } | ||
103 | |||
104 | #[cfg(test)] | 94 | #[cfg(test)] |
105 | mod tests { | 95 | mod tests { |
106 | use test_utils::mark; | 96 | use test_utils::mark; |
diff --git a/crates/assists/src/handlers/generate_getter.rs b/crates/assists/src/handlers/generate_getter.rs new file mode 100644 index 000000000..b63dfce41 --- /dev/null +++ b/crates/assists/src/handlers/generate_getter.rs | |||
@@ -0,0 +1,156 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | ||
2 | use syntax::ast::VisibilityOwner; | ||
3 | use syntax::ast::{self, AstNode, NameOwner}; | ||
4 | |||
5 | use crate::{ | ||
6 | utils::{find_impl_block, find_struct_impl, generate_impl_text}, | ||
7 | AssistContext, AssistId, AssistKind, Assists, | ||
8 | }; | ||
9 | |||
10 | // Assist: generate_getter | ||
11 | // | ||
12 | // Generate a getter method. | ||
13 | // | ||
14 | // ``` | ||
15 | // struct Person { | ||
16 | // nam$0e: String, | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // struct Person { | ||
22 | // name: String, | ||
23 | // } | ||
24 | // | ||
25 | // impl Person { | ||
26 | // /// Get a reference to the person's name. | ||
27 | // fn name(&self) -> &String { | ||
28 | // &self.name | ||
29 | // } | ||
30 | // } | ||
31 | // ``` | ||
32 | pub(crate) fn generate_getter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
33 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
34 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | ||
35 | |||
36 | let strukt_name = strukt.name()?; | ||
37 | let field_name = field.name()?; | ||
38 | let field_ty = field.ty()?; | ||
39 | |||
40 | // Return early if we've found an existing fn | ||
41 | let fn_name = to_lower_snake_case(&field_name.to_string()); | ||
42 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), fn_name.as_str())?; | ||
43 | |||
44 | let target = field.syntax().text_range(); | ||
45 | acc.add( | ||
46 | AssistId("generate_getter", AssistKind::Generate), | ||
47 | "Generate a getter method", | ||
48 | target, | ||
49 | |builder| { | ||
50 | let mut buf = String::with_capacity(512); | ||
51 | |||
52 | let fn_name_spaced = fn_name.replace('_', " "); | ||
53 | let strukt_name_spaced = | ||
54 | to_lower_snake_case(&strukt_name.to_string()).replace('_', " "); | ||
55 | |||
56 | if impl_def.is_some() { | ||
57 | buf.push('\n'); | ||
58 | } | ||
59 | |||
60 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
61 | format_to!( | ||
62 | buf, | ||
63 | " /// Get a reference to the {}'s {}. | ||
64 | {}fn {}(&self) -> &{} {{ | ||
65 | &self.{} | ||
66 | }}", | ||
67 | strukt_name_spaced, | ||
68 | fn_name_spaced, | ||
69 | vis, | ||
70 | fn_name, | ||
71 | field_ty, | ||
72 | fn_name, | ||
73 | ); | ||
74 | |||
75 | let start_offset = impl_def | ||
76 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) | ||
77 | .unwrap_or_else(|| { | ||
78 | buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); | ||
79 | strukt.syntax().text_range().end() | ||
80 | }); | ||
81 | |||
82 | builder.insert(start_offset, buf); | ||
83 | }, | ||
84 | ) | ||
85 | } | ||
86 | |||
87 | #[cfg(test)] | ||
88 | mod tests { | ||
89 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
90 | |||
91 | use super::*; | ||
92 | |||
93 | fn check_not_applicable(ra_fixture: &str) { | ||
94 | check_assist_not_applicable(generate_getter, ra_fixture) | ||
95 | } | ||
96 | |||
97 | #[test] | ||
98 | fn test_generate_getter_from_field() { | ||
99 | check_assist( | ||
100 | generate_getter, | ||
101 | r#" | ||
102 | struct Context<T: Clone> { | ||
103 | dat$0a: T, | ||
104 | }"#, | ||
105 | r#" | ||
106 | struct Context<T: Clone> { | ||
107 | data: T, | ||
108 | } | ||
109 | |||
110 | impl<T: Clone> Context<T> { | ||
111 | /// Get a reference to the context's data. | ||
112 | fn data(&self) -> &T { | ||
113 | &self.data | ||
114 | } | ||
115 | }"#, | ||
116 | ); | ||
117 | } | ||
118 | |||
119 | #[test] | ||
120 | fn test_generate_getter_already_implemented() { | ||
121 | check_not_applicable( | ||
122 | r#" | ||
123 | struct Context<T: Clone> { | ||
124 | dat$0a: T, | ||
125 | } | ||
126 | |||
127 | impl<T: Clone> Context<T> { | ||
128 | fn data(&self) -> &T { | ||
129 | &self.data | ||
130 | } | ||
131 | }"#, | ||
132 | ); | ||
133 | } | ||
134 | |||
135 | #[test] | ||
136 | fn test_generate_getter_from_field_with_visibility_marker() { | ||
137 | check_assist( | ||
138 | generate_getter, | ||
139 | r#" | ||
140 | pub(crate) struct Context<T: Clone> { | ||
141 | dat$0a: T, | ||
142 | }"#, | ||
143 | r#" | ||
144 | pub(crate) struct Context<T: Clone> { | ||
145 | data: T, | ||
146 | } | ||
147 | |||
148 | impl<T: Clone> Context<T> { | ||
149 | /// Get a reference to the context's data. | ||
150 | pub(crate) fn data(&self) -> &T { | ||
151 | &self.data | ||
152 | } | ||
153 | }"#, | ||
154 | ); | ||
155 | } | ||
156 | } | ||
diff --git a/crates/assists/src/handlers/generate_getter_mut.rs b/crates/assists/src/handlers/generate_getter_mut.rs new file mode 100644 index 000000000..b5085035e --- /dev/null +++ b/crates/assists/src/handlers/generate_getter_mut.rs | |||
@@ -0,0 +1,159 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | ||
2 | use syntax::ast::VisibilityOwner; | ||
3 | use syntax::ast::{self, AstNode, NameOwner}; | ||
4 | |||
5 | use crate::{ | ||
6 | utils::{find_impl_block, find_struct_impl, generate_impl_text}, | ||
7 | AssistContext, AssistId, AssistKind, Assists, | ||
8 | }; | ||
9 | |||
10 | // Assist: generate_getter_mut | ||
11 | // | ||
12 | // Generate a mut getter method. | ||
13 | // | ||
14 | // ``` | ||
15 | // struct Person { | ||
16 | // nam$0e: String, | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // struct Person { | ||
22 | // name: String, | ||
23 | // } | ||
24 | // | ||
25 | // impl Person { | ||
26 | // /// Get a mutable reference to the person's name. | ||
27 | // fn name_mut(&mut self) -> &mut String { | ||
28 | // &mut self.name | ||
29 | // } | ||
30 | // } | ||
31 | // ``` | ||
32 | pub(crate) fn generate_getter_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
33 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
34 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | ||
35 | |||
36 | let strukt_name = strukt.name()?; | ||
37 | let field_name = field.name()?; | ||
38 | let field_ty = field.ty()?; | ||
39 | |||
40 | // Return early if we've found an existing fn | ||
41 | let fn_name = to_lower_snake_case(&field_name.to_string()); | ||
42 | let impl_def = find_struct_impl( | ||
43 | &ctx, | ||
44 | &ast::Adt::Struct(strukt.clone()), | ||
45 | format!("{}_mut", fn_name).as_str(), | ||
46 | )?; | ||
47 | |||
48 | let target = field.syntax().text_range(); | ||
49 | acc.add( | ||
50 | AssistId("generate_getter_mut", AssistKind::Generate), | ||
51 | "Generate a mut getter method", | ||
52 | target, | ||
53 | |builder| { | ||
54 | let mut buf = String::with_capacity(512); | ||
55 | let fn_name_spaced = fn_name.replace('_', " "); | ||
56 | let strukt_name_spaced = | ||
57 | to_lower_snake_case(&strukt_name.to_string()).replace('_', " "); | ||
58 | |||
59 | if impl_def.is_some() { | ||
60 | buf.push('\n'); | ||
61 | } | ||
62 | |||
63 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
64 | format_to!( | ||
65 | buf, | ||
66 | " /// Get a mutable reference to the {}'s {}. | ||
67 | {}fn {}_mut(&mut self) -> &mut {} {{ | ||
68 | &mut self.{} | ||
69 | }}", | ||
70 | strukt_name_spaced, | ||
71 | fn_name_spaced, | ||
72 | vis, | ||
73 | fn_name, | ||
74 | field_ty, | ||
75 | fn_name, | ||
76 | ); | ||
77 | |||
78 | let start_offset = impl_def | ||
79 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) | ||
80 | .unwrap_or_else(|| { | ||
81 | buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); | ||
82 | strukt.syntax().text_range().end() | ||
83 | }); | ||
84 | |||
85 | builder.insert(start_offset, buf); | ||
86 | }, | ||
87 | ) | ||
88 | } | ||
89 | |||
90 | #[cfg(test)] | ||
91 | mod tests { | ||
92 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
93 | |||
94 | use super::*; | ||
95 | |||
96 | fn check_not_applicable(ra_fixture: &str) { | ||
97 | check_assist_not_applicable(generate_getter_mut, ra_fixture) | ||
98 | } | ||
99 | |||
100 | #[test] | ||
101 | fn test_generate_getter_mut_from_field() { | ||
102 | check_assist( | ||
103 | generate_getter_mut, | ||
104 | r#" | ||
105 | struct Context<T: Clone> { | ||
106 | dat$0a: T, | ||
107 | }"#, | ||
108 | r#" | ||
109 | struct Context<T: Clone> { | ||
110 | data: T, | ||
111 | } | ||
112 | |||
113 | impl<T: Clone> Context<T> { | ||
114 | /// Get a mutable reference to the context's data. | ||
115 | fn data_mut(&mut self) -> &mut T { | ||
116 | &mut self.data | ||
117 | } | ||
118 | }"#, | ||
119 | ); | ||
120 | } | ||
121 | |||
122 | #[test] | ||
123 | fn test_generate_getter_mut_already_implemented() { | ||
124 | check_not_applicable( | ||
125 | r#" | ||
126 | struct Context<T: Clone> { | ||
127 | dat$0a: T, | ||
128 | } | ||
129 | |||
130 | impl<T: Clone> Context<T> { | ||
131 | fn data_mut(&mut self) -> &mut T { | ||
132 | &mut self.data | ||
133 | } | ||
134 | }"#, | ||
135 | ); | ||
136 | } | ||
137 | |||
138 | #[test] | ||
139 | fn test_generate_getter_mut_from_field_with_visibility_marker() { | ||
140 | check_assist( | ||
141 | generate_getter_mut, | ||
142 | r#" | ||
143 | pub(crate) struct Context<T: Clone> { | ||
144 | dat$0a: T, | ||
145 | }"#, | ||
146 | r#" | ||
147 | pub(crate) struct Context<T: Clone> { | ||
148 | data: T, | ||
149 | } | ||
150 | |||
151 | impl<T: Clone> Context<T> { | ||
152 | /// Get a mutable reference to the context's data. | ||
153 | pub(crate) fn data_mut(&mut self) -> &mut T { | ||
154 | &mut self.data | ||
155 | } | ||
156 | }"#, | ||
157 | ); | ||
158 | } | ||
159 | } | ||
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs index a9203d33f..c29077225 100644 --- a/crates/assists/src/handlers/generate_new.rs +++ b/crates/assists/src/handlers/generate_new.rs | |||
@@ -1,12 +1,10 @@ | |||
1 | use ast::Adt; | ||
1 | use itertools::Itertools; | 2 | use itertools::Itertools; |
2 | use stdx::format_to; | 3 | use stdx::format_to; |
3 | use syntax::{ | 4 | use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner}; |
4 | ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner}, | ||
5 | SmolStr, | ||
6 | }; | ||
7 | 5 | ||
8 | use crate::{ | 6 | use crate::{ |
9 | utils::{find_impl_block, find_struct_impl}, | 7 | utils::{find_impl_block, find_struct_impl, generate_impl_text}, |
10 | AssistContext, AssistId, AssistKind, Assists, | 8 | AssistContext, AssistId, AssistKind, Assists, |
11 | }; | 9 | }; |
12 | 10 | ||
@@ -28,7 +26,6 @@ use crate::{ | |||
28 | // impl<T: Clone> Ctx<T> { | 26 | // impl<T: Clone> Ctx<T> { |
29 | // fn $0new(data: T) -> Self { Self { data } } | 27 | // fn $0new(data: T) -> Self { Self { data } } |
30 | // } | 28 | // } |
31 | // | ||
32 | // ``` | 29 | // ``` |
33 | pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | 30 | pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { |
34 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | 31 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; |
@@ -40,7 +37,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
40 | }; | 37 | }; |
41 | 38 | ||
42 | // Return early if we've found an existing new fn | 39 | // Return early if we've found an existing new fn |
43 | let impl_def = find_struct_impl(&ctx, &ast::Adt::Struct(strukt.clone()), "new")?; | 40 | let impl_def = find_struct_impl(&ctx, &Adt::Struct(strukt.clone()), "new")?; |
44 | 41 | ||
45 | let target = strukt.syntax().text_range(); | 42 | let target = strukt.syntax().text_range(); |
46 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { | 43 | acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| { |
@@ -63,7 +60,7 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
63 | let start_offset = impl_def | 60 | let start_offset = impl_def |
64 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) | 61 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) |
65 | .unwrap_or_else(|| { | 62 | .unwrap_or_else(|| { |
66 | buf = generate_impl_text(&strukt, &buf); | 63 | buf = generate_impl_text(&Adt::Struct(strukt.clone()), &buf); |
67 | strukt.syntax().text_range().end() | 64 | strukt.syntax().text_range().end() |
68 | }); | 65 | }); |
69 | 66 | ||
@@ -77,32 +74,6 @@ pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> | |||
77 | }) | 74 | }) |
78 | } | 75 | } |
79 | 76 | ||
80 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
81 | // parameters | ||
82 | fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String { | ||
83 | let type_params = strukt.generic_param_list(); | ||
84 | let mut buf = String::with_capacity(code.len()); | ||
85 | buf.push_str("\n\nimpl"); | ||
86 | if let Some(type_params) = &type_params { | ||
87 | format_to!(buf, "{}", type_params.syntax()); | ||
88 | } | ||
89 | buf.push(' '); | ||
90 | buf.push_str(strukt.name().unwrap().text()); | ||
91 | if let Some(type_params) = type_params { | ||
92 | let lifetime_params = type_params | ||
93 | .lifetime_params() | ||
94 | .filter_map(|it| it.lifetime()) | ||
95 | .map(|it| SmolStr::from(it.text())); | ||
96 | let type_params = | ||
97 | type_params.type_params().filter_map(|it| it.name()).map(|it| SmolStr::from(it.text())); | ||
98 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) | ||
99 | } | ||
100 | |||
101 | format_to!(buf, " {{\n{}\n}}\n", code); | ||
102 | |||
103 | buf | ||
104 | } | ||
105 | |||
106 | #[cfg(test)] | 77 | #[cfg(test)] |
107 | mod tests { | 78 | mod tests { |
108 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; | 79 | use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target}; |
@@ -120,8 +91,7 @@ mod tests { | |||
120 | 91 | ||
121 | impl Foo { | 92 | impl Foo { |
122 | fn $0new() -> Self { Self { } } | 93 | fn $0new() -> Self { Self { } } |
123 | } | 94 | }", |
124 | ", | ||
125 | ); | 95 | ); |
126 | check_assist( | 96 | check_assist( |
127 | generate_new, | 97 | generate_new, |
@@ -130,8 +100,7 @@ impl Foo { | |||
130 | 100 | ||
131 | impl<T: Clone> Foo<T> { | 101 | impl<T: Clone> Foo<T> { |
132 | fn $0new() -> Self { Self { } } | 102 | fn $0new() -> Self { Self { } } |
133 | } | 103 | }", |
134 | ", | ||
135 | ); | 104 | ); |
136 | check_assist( | 105 | check_assist( |
137 | generate_new, | 106 | generate_new, |
@@ -140,8 +109,7 @@ impl<T: Clone> Foo<T> { | |||
140 | 109 | ||
141 | impl<'a, T: Foo<'a>> Foo<'a, T> { | 110 | impl<'a, T: Foo<'a>> Foo<'a, T> { |
142 | fn $0new() -> Self { Self { } } | 111 | fn $0new() -> Self { Self { } } |
143 | } | 112 | }", |
144 | ", | ||
145 | ); | 113 | ); |
146 | check_assist( | 114 | check_assist( |
147 | generate_new, | 115 | generate_new, |
@@ -150,8 +118,7 @@ impl<'a, T: Foo<'a>> Foo<'a, T> { | |||
150 | 118 | ||
151 | impl Foo { | 119 | impl Foo { |
152 | fn $0new(baz: String) -> Self { Self { baz } } | 120 | fn $0new(baz: String) -> Self { Self { baz } } |
153 | } | 121 | }", |
154 | ", | ||
155 | ); | 122 | ); |
156 | check_assist( | 123 | check_assist( |
157 | generate_new, | 124 | generate_new, |
@@ -160,8 +127,7 @@ impl Foo { | |||
160 | 127 | ||
161 | impl Foo { | 128 | impl Foo { |
162 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } | 129 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } |
163 | } | 130 | }", |
164 | ", | ||
165 | ); | 131 | ); |
166 | 132 | ||
167 | // Check that visibility modifiers don't get brought in for fields | 133 | // Check that visibility modifiers don't get brought in for fields |
@@ -172,8 +138,7 @@ impl Foo { | |||
172 | 138 | ||
173 | impl Foo { | 139 | impl Foo { |
174 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } | 140 | fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } } |
175 | } | 141 | }", |
176 | ", | ||
177 | ); | 142 | ); |
178 | 143 | ||
179 | // Check that it reuses existing impls | 144 | // Check that it reuses existing impls |
@@ -240,8 +205,7 @@ impl Foo { | |||
240 | 205 | ||
241 | impl Foo { | 206 | impl Foo { |
242 | pub fn $0new() -> Self { Self { } } | 207 | pub fn $0new() -> Self { Self { } } |
243 | } | 208 | }", |
244 | ", | ||
245 | ); | 209 | ); |
246 | check_assist( | 210 | check_assist( |
247 | generate_new, | 211 | generate_new, |
@@ -250,8 +214,7 @@ impl Foo { | |||
250 | 214 | ||
251 | impl Foo { | 215 | impl Foo { |
252 | pub(crate) fn $0new() -> Self { Self { } } | 216 | pub(crate) fn $0new() -> Self { Self { } } |
253 | } | 217 | }", |
254 | ", | ||
255 | ); | 218 | ); |
256 | } | 219 | } |
257 | 220 | ||
@@ -322,8 +285,7 @@ impl<T> Source<T> { | |||
322 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | 285 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { |
323 | Source { file_id: self.file_id, ast: f(self.ast) } | 286 | Source { file_id: self.file_id, ast: f(self.ast) } |
324 | } | 287 | } |
325 | } | 288 | }"##, |
326 | "##, | ||
327 | r##" | 289 | r##" |
328 | pub struct AstId<N: AstNode> { | 290 | pub struct AstId<N: AstNode> { |
329 | file_id: HirFileId, | 291 | file_id: HirFileId, |
@@ -347,8 +309,7 @@ impl<T> Source<T> { | |||
347 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { | 309 | pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> { |
348 | Source { file_id: self.file_id, ast: f(self.ast) } | 310 | Source { file_id: self.file_id, ast: f(self.ast) } |
349 | } | 311 | } |
350 | } | 312 | }"##, |
351 | "##, | ||
352 | ); | 313 | ); |
353 | } | 314 | } |
354 | } | 315 | } |
diff --git a/crates/assists/src/handlers/generate_setter.rs b/crates/assists/src/handlers/generate_setter.rs new file mode 100644 index 000000000..c9043a162 --- /dev/null +++ b/crates/assists/src/handlers/generate_setter.rs | |||
@@ -0,0 +1,162 @@ | |||
1 | use stdx::{format_to, to_lower_snake_case}; | ||
2 | use syntax::ast::VisibilityOwner; | ||
3 | use syntax::ast::{self, AstNode, NameOwner}; | ||
4 | |||
5 | use crate::{ | ||
6 | utils::{find_impl_block, find_struct_impl, generate_impl_text}, | ||
7 | AssistContext, AssistId, AssistKind, Assists, | ||
8 | }; | ||
9 | |||
10 | // Assist: generate_setter | ||
11 | // | ||
12 | // Generate a setter method. | ||
13 | // | ||
14 | // ``` | ||
15 | // struct Person { | ||
16 | // nam$0e: String, | ||
17 | // } | ||
18 | // ``` | ||
19 | // -> | ||
20 | // ``` | ||
21 | // struct Person { | ||
22 | // name: String, | ||
23 | // } | ||
24 | // | ||
25 | // impl Person { | ||
26 | // /// Set the person's name. | ||
27 | // fn set_name(&mut self, name: String) { | ||
28 | // self.name = name; | ||
29 | // } | ||
30 | // } | ||
31 | // ``` | ||
32 | pub(crate) fn generate_setter(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { | ||
33 | let strukt = ctx.find_node_at_offset::<ast::Struct>()?; | ||
34 | let field = ctx.find_node_at_offset::<ast::RecordField>()?; | ||
35 | |||
36 | let strukt_name = strukt.name()?; | ||
37 | let field_name = field.name()?; | ||
38 | let field_ty = field.ty()?; | ||
39 | |||
40 | // Return early if we've found an existing fn | ||
41 | let fn_name = to_lower_snake_case(&field_name.to_string()); | ||
42 | let impl_def = find_struct_impl( | ||
43 | &ctx, | ||
44 | &ast::Adt::Struct(strukt.clone()), | ||
45 | format!("set_{}", fn_name).as_str(), | ||
46 | )?; | ||
47 | |||
48 | let target = field.syntax().text_range(); | ||
49 | acc.add( | ||
50 | AssistId("generate_setter", AssistKind::Generate), | ||
51 | "Generate a setter method", | ||
52 | target, | ||
53 | |builder| { | ||
54 | let mut buf = String::with_capacity(512); | ||
55 | |||
56 | let fn_name_spaced = fn_name.replace('_', " "); | ||
57 | let strukt_name_spaced = | ||
58 | to_lower_snake_case(&strukt_name.to_string()).replace('_', " "); | ||
59 | |||
60 | if impl_def.is_some() { | ||
61 | buf.push('\n'); | ||
62 | } | ||
63 | |||
64 | let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v)); | ||
65 | format_to!( | ||
66 | buf, | ||
67 | " /// Set the {}'s {}. | ||
68 | {}fn set_{}(&mut self, {}: {}) {{ | ||
69 | self.{} = {}; | ||
70 | }}", | ||
71 | strukt_name_spaced, | ||
72 | fn_name_spaced, | ||
73 | vis, | ||
74 | fn_name, | ||
75 | fn_name, | ||
76 | field_ty, | ||
77 | fn_name, | ||
78 | fn_name, | ||
79 | ); | ||
80 | |||
81 | let start_offset = impl_def | ||
82 | .and_then(|impl_def| find_impl_block(impl_def, &mut buf)) | ||
83 | .unwrap_or_else(|| { | ||
84 | buf = generate_impl_text(&ast::Adt::Struct(strukt.clone()), &buf); | ||
85 | strukt.syntax().text_range().end() | ||
86 | }); | ||
87 | |||
88 | builder.insert(start_offset, buf); | ||
89 | }, | ||
90 | ) | ||
91 | } | ||
92 | |||
93 | #[cfg(test)] | ||
94 | mod tests { | ||
95 | use crate::tests::{check_assist, check_assist_not_applicable}; | ||
96 | |||
97 | use super::*; | ||
98 | |||
99 | fn check_not_applicable(ra_fixture: &str) { | ||
100 | check_assist_not_applicable(generate_setter, ra_fixture) | ||
101 | } | ||
102 | |||
103 | #[test] | ||
104 | fn test_generate_setter_from_field() { | ||
105 | check_assist( | ||
106 | generate_setter, | ||
107 | r#" | ||
108 | struct Person<T: Clone> { | ||
109 | dat$0a: T, | ||
110 | }"#, | ||
111 | r#" | ||
112 | struct Person<T: Clone> { | ||
113 | data: T, | ||
114 | } | ||
115 | |||
116 | impl<T: Clone> Person<T> { | ||
117 | /// Set the person's data. | ||
118 | fn set_data(&mut self, data: T) { | ||
119 | self.data = data; | ||
120 | } | ||
121 | }"#, | ||
122 | ); | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn test_generate_setter_already_implemented() { | ||
127 | check_not_applicable( | ||
128 | r#" | ||
129 | struct Person<T: Clone> { | ||
130 | dat$0a: T, | ||
131 | } | ||
132 | |||
133 | impl<T: Clone> Person<T> { | ||
134 | fn set_data(&mut self, data: T) { | ||
135 | self.data = data; | ||
136 | } | ||
137 | }"#, | ||
138 | ); | ||
139 | } | ||
140 | |||
141 | #[test] | ||
142 | fn test_generate_setter_from_field_with_visibility_marker() { | ||
143 | check_assist( | ||
144 | generate_setter, | ||
145 | r#" | ||
146 | pub(crate) struct Person<T: Clone> { | ||
147 | dat$0a: T, | ||
148 | }"#, | ||
149 | r#" | ||
150 | pub(crate) struct Person<T: Clone> { | ||
151 | data: T, | ||
152 | } | ||
153 | |||
154 | impl<T: Clone> Person<T> { | ||
155 | /// Set the person's data. | ||
156 | pub(crate) fn set_data(&mut self, data: T) { | ||
157 | self.data = data; | ||
158 | } | ||
159 | }"#, | ||
160 | ); | ||
161 | } | ||
162 | } | ||
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs index 83fbf6986..957efa6b9 100644 --- a/crates/assists/src/lib.rs +++ b/crates/assists/src/lib.rs | |||
@@ -130,8 +130,11 @@ mod handlers { | |||
130 | mod generate_enum_match_method; | 130 | mod generate_enum_match_method; |
131 | mod generate_from_impl_for_enum; | 131 | mod generate_from_impl_for_enum; |
132 | mod generate_function; | 132 | mod generate_function; |
133 | mod generate_getter; | ||
134 | mod generate_getter_mut; | ||
133 | mod generate_impl; | 135 | mod generate_impl; |
134 | mod generate_new; | 136 | mod generate_new; |
137 | mod generate_setter; | ||
135 | mod infer_function_return_type; | 138 | mod infer_function_return_type; |
136 | mod inline_function; | 139 | mod inline_function; |
137 | mod inline_local_variable; | 140 | mod inline_local_variable; |
@@ -189,8 +192,11 @@ mod handlers { | |||
189 | generate_enum_match_method::generate_enum_match_method, | 192 | generate_enum_match_method::generate_enum_match_method, |
190 | generate_from_impl_for_enum::generate_from_impl_for_enum, | 193 | generate_from_impl_for_enum::generate_from_impl_for_enum, |
191 | generate_function::generate_function, | 194 | generate_function::generate_function, |
195 | generate_getter::generate_getter, | ||
196 | generate_getter_mut::generate_getter_mut, | ||
192 | generate_impl::generate_impl, | 197 | generate_impl::generate_impl, |
193 | generate_new::generate_new, | 198 | generate_new::generate_new, |
199 | generate_setter::generate_setter, | ||
194 | infer_function_return_type::infer_function_return_type, | 200 | infer_function_return_type::infer_function_return_type, |
195 | inline_function::inline_function, | 201 | inline_function::inline_function, |
196 | inline_local_variable::inline_local_variable, | 202 | inline_local_variable::inline_local_variable, |
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs index b27f6bf75..5b9992f15 100644 --- a/crates/assists/src/tests.rs +++ b/crates/assists/src/tests.rs | |||
@@ -173,6 +173,9 @@ fn assist_order_field_struct() { | |||
173 | let mut assists = assists.iter(); | 173 | let mut assists = assists.iter(); |
174 | 174 | ||
175 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); | 175 | assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)"); |
176 | assert_eq!(assists.next().expect("expected assist").label, "Generate a getter method"); | ||
177 | assert_eq!(assists.next().expect("expected assist").label, "Generate a mut getter method"); | ||
178 | assert_eq!(assists.next().expect("expected assist").label, "Generate a setter method"); | ||
176 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); | 179 | assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); |
177 | } | 180 | } |
178 | 181 | ||
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs index 0dbb05f2a..6f2b22bc2 100644 --- a/crates/assists/src/tests/generated.rs +++ b/crates/assists/src/tests/generated.rs | |||
@@ -534,6 +534,54 @@ fn bar(arg: &str, baz: Baz) ${0:-> ()} { | |||
534 | } | 534 | } |
535 | 535 | ||
536 | #[test] | 536 | #[test] |
537 | fn doctest_generate_getter() { | ||
538 | check_doc_test( | ||
539 | "generate_getter", | ||
540 | r#####" | ||
541 | struct Person { | ||
542 | nam$0e: String, | ||
543 | } | ||
544 | "#####, | ||
545 | r#####" | ||
546 | struct Person { | ||
547 | name: String, | ||
548 | } | ||
549 | |||
550 | impl Person { | ||
551 | /// Get a reference to the person's name. | ||
552 | fn name(&self) -> &String { | ||
553 | &self.name | ||
554 | } | ||
555 | } | ||
556 | "#####, | ||
557 | ) | ||
558 | } | ||
559 | |||
560 | #[test] | ||
561 | fn doctest_generate_getter_mut() { | ||
562 | check_doc_test( | ||
563 | "generate_getter_mut", | ||
564 | r#####" | ||
565 | struct Person { | ||
566 | nam$0e: String, | ||
567 | } | ||
568 | "#####, | ||
569 | r#####" | ||
570 | struct Person { | ||
571 | name: String, | ||
572 | } | ||
573 | |||
574 | impl Person { | ||
575 | /// Get a mutable reference to the person's name. | ||
576 | fn name_mut(&mut self) -> &mut String { | ||
577 | &mut self.name | ||
578 | } | ||
579 | } | ||
580 | "#####, | ||
581 | ) | ||
582 | } | ||
583 | |||
584 | #[test] | ||
537 | fn doctest_generate_impl() { | 585 | fn doctest_generate_impl() { |
538 | check_doc_test( | 586 | check_doc_test( |
539 | "generate_impl", | 587 | "generate_impl", |
@@ -571,7 +619,30 @@ struct Ctx<T: Clone> { | |||
571 | impl<T: Clone> Ctx<T> { | 619 | impl<T: Clone> Ctx<T> { |
572 | fn $0new(data: T) -> Self { Self { data } } | 620 | fn $0new(data: T) -> Self { Self { data } } |
573 | } | 621 | } |
622 | "#####, | ||
623 | ) | ||
624 | } | ||
574 | 625 | ||
626 | #[test] | ||
627 | fn doctest_generate_setter() { | ||
628 | check_doc_test( | ||
629 | "generate_setter", | ||
630 | r#####" | ||
631 | struct Person { | ||
632 | nam$0e: String, | ||
633 | } | ||
634 | "#####, | ||
635 | r#####" | ||
636 | struct Person { | ||
637 | name: String, | ||
638 | } | ||
639 | |||
640 | impl Person { | ||
641 | /// Set the person's name. | ||
642 | fn set_name(&mut self, name: String) { | ||
643 | self.name = name; | ||
644 | } | ||
645 | } | ||
575 | "#####, | 646 | "#####, |
576 | ) | 647 | ) |
577 | } | 648 | } |
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs index cd80c2958..643dade23 100644 --- a/crates/assists/src/utils.rs +++ b/crates/assists/src/utils.rs | |||
@@ -5,12 +5,13 @@ use std::ops; | |||
5 | use hir::{Adt, HasSource}; | 5 | use hir::{Adt, HasSource}; |
6 | use ide_db::{helpers::SnippetCap, RootDatabase}; | 6 | use ide_db::{helpers::SnippetCap, RootDatabase}; |
7 | use itertools::Itertools; | 7 | use itertools::Itertools; |
8 | use stdx::format_to; | ||
8 | use syntax::{ | 9 | use syntax::{ |
9 | ast::edit::AstNodeEdit, | 10 | ast::edit::AstNodeEdit, |
10 | ast::AttrsOwner, | 11 | ast::AttrsOwner, |
11 | ast::NameOwner, | 12 | ast::NameOwner, |
12 | ast::{self, edit, make, ArgListOwner}, | 13 | ast::{self, edit, make, ArgListOwner, GenericParamsOwner}, |
13 | AstNode, Direction, | 14 | AstNode, Direction, SmolStr, |
14 | SyntaxKind::*, | 15 | SyntaxKind::*, |
15 | SyntaxNode, TextSize, T, | 16 | SyntaxNode, TextSize, T, |
16 | }; | 17 | }; |
@@ -354,3 +355,29 @@ pub(crate) fn find_impl_block(impl_def: ast::Impl, buf: &mut String) -> Option<T | |||
354 | .end(); | 355 | .end(); |
355 | Some(start) | 356 | Some(start) |
356 | } | 357 | } |
358 | |||
359 | // Generates the surrounding `impl Type { <code> }` including type and lifetime | ||
360 | // parameters | ||
361 | pub(crate) fn generate_impl_text(adt: &ast::Adt, code: &str) -> String { | ||
362 | let type_params = adt.generic_param_list(); | ||
363 | let mut buf = String::with_capacity(code.len()); | ||
364 | buf.push_str("\n\nimpl"); | ||
365 | if let Some(type_params) = &type_params { | ||
366 | format_to!(buf, "{}", type_params.syntax()); | ||
367 | } | ||
368 | buf.push(' '); | ||
369 | buf.push_str(adt.name().unwrap().text()); | ||
370 | if let Some(type_params) = type_params { | ||
371 | let lifetime_params = type_params | ||
372 | .lifetime_params() | ||
373 | .filter_map(|it| it.lifetime()) | ||
374 | .map(|it| SmolStr::from(it.text())); | ||
375 | let type_params = | ||
376 | type_params.type_params().filter_map(|it| it.name()).map(|it| SmolStr::from(it.text())); | ||
377 | format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", ")) | ||
378 | } | ||
379 | |||
380 | format_to!(buf, " {{\n{}\n}}", code); | ||
381 | |||
382 | buf | ||
383 | } | ||