aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers
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 /crates/assists/src/handlers
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]>
Diffstat (limited to 'crates/assists/src/handlers')
-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
5 files changed, 494 insertions, 66 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}