aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2021-02-10 10:32:36 +0000
committerGitHub <[email protected]>2021-02-10 10:32:36 +0000
commit5e39d7a68032ba40d3ed76d41d0f01ad628f5c95 (patch)
tree2cee8cecf4214092f42dbee3432afa944e219671
parentff5ef2830c4cc6bf4116b99b440885bf0c94b459 (diff)
parente8d7bcc35507425f384cff25feb564ac41a5c5a7 (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]>
-rw-r--r--crates/assists/src/handlers/generate_enum_match_method.rs14
-rw-r--r--crates/assists/src/handlers/generate_getter.rs156
-rw-r--r--crates/assists/src/handlers/generate_getter_mut.rs159
-rw-r--r--crates/assists/src/handlers/generate_new.rs69
-rw-r--r--crates/assists/src/handlers/generate_setter.rs162
-rw-r--r--crates/assists/src/lib.rs6
-rw-r--r--crates/assists/src/tests.rs3
-rw-r--r--crates/assists/src/tests/generated.rs71
-rw-r--r--crates/assists/src/utils.rs31
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};
4use test_utils::mark; 4use test_utils::mark;
5 5
6use crate::{ 6use 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
96fn 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)]
105mod tests { 95mod 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 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4
5use 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// ```
32pub(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)]
88mod 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#"
102struct Context<T: Clone> {
103 dat$0a: T,
104}"#,
105 r#"
106struct Context<T: Clone> {
107 data: T,
108}
109
110impl<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#"
123struct Context<T: Clone> {
124 dat$0a: T,
125}
126
127impl<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#"
140pub(crate) struct Context<T: Clone> {
141 dat$0a: T,
142}"#,
143 r#"
144pub(crate) struct Context<T: Clone> {
145 data: T,
146}
147
148impl<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 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4
5use 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// ```
32pub(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)]
91mod 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#"
105struct Context<T: Clone> {
106 dat$0a: T,
107}"#,
108 r#"
109struct Context<T: Clone> {
110 data: T,
111}
112
113impl<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#"
126struct Context<T: Clone> {
127 dat$0a: T,
128}
129
130impl<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#"
143pub(crate) struct Context<T: Clone> {
144 dat$0a: T,
145}"#,
146 r#"
147pub(crate) struct Context<T: Clone> {
148 data: T,
149}
150
151impl<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 @@
1use ast::Adt;
1use itertools::Itertools; 2use itertools::Itertools;
2use stdx::format_to; 3use stdx::format_to;
3use syntax::{ 4use syntax::ast::{self, AstNode, NameOwner, StructKind, VisibilityOwner};
4 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
5 SmolStr,
6};
7 5
8use crate::{ 6use 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// ```
33pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { 30pub(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
82fn 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)]
107mod tests { 78mod 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
121impl Foo { 92impl 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
131impl<T: Clone> Foo<T> { 101impl<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
141impl<'a, T: Foo<'a>> Foo<'a, T> { 110impl<'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
151impl Foo { 119impl 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
161impl Foo { 128impl 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
173impl Foo { 139impl 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
241impl Foo { 206impl 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
251impl Foo { 215impl 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##"
328pub struct AstId<N: AstNode> { 290pub 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 @@
1use stdx::{format_to, to_lower_snake_case};
2use syntax::ast::VisibilityOwner;
3use syntax::ast::{self, AstNode, NameOwner};
4
5use 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// ```
32pub(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)]
94mod 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#"
108struct Person<T: Clone> {
109 dat$0a: T,
110}"#,
111 r#"
112struct Person<T: Clone> {
113 data: T,
114}
115
116impl<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#"
129struct Person<T: Clone> {
130 dat$0a: T,
131}
132
133impl<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#"
146pub(crate) struct Person<T: Clone> {
147 dat$0a: T,
148}"#,
149 r#"
150pub(crate) struct Person<T: Clone> {
151 data: T,
152}
153
154impl<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]
537fn doctest_generate_getter() {
538 check_doc_test(
539 "generate_getter",
540 r#####"
541struct Person {
542 nam$0e: String,
543}
544"#####,
545 r#####"
546struct Person {
547 name: String,
548}
549
550impl 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]
561fn doctest_generate_getter_mut() {
562 check_doc_test(
563 "generate_getter_mut",
564 r#####"
565struct Person {
566 nam$0e: String,
567}
568"#####,
569 r#####"
570struct Person {
571 name: String,
572}
573
574impl 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]
537fn doctest_generate_impl() { 585fn 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> {
571impl<T: Clone> Ctx<T> { 619impl<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]
627fn doctest_generate_setter() {
628 check_doc_test(
629 "generate_setter",
630 r#####"
631struct Person {
632 nam$0e: String,
633}
634"#####,
635 r#####"
636struct Person {
637 name: String,
638}
639
640impl 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;
5use hir::{Adt, HasSource}; 5use hir::{Adt, HasSource};
6use ide_db::{helpers::SnippetCap, RootDatabase}; 6use ide_db::{helpers::SnippetCap, RootDatabase};
7use itertools::Itertools; 7use itertools::Itertools;
8use stdx::format_to;
8use syntax::{ 9use 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
361pub(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}