aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-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.rs7
-rw-r--r--crates/assists/src/tests/generated.rs71
-rw-r--r--crates/assists/src/utils.rs31
9 files changed, 607 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 32bd8698b..5b9992f15 100644
--- a/crates/assists/src/tests.rs
+++ b/crates/assists/src/tests.rs
@@ -49,14 +49,17 @@ pub(crate) fn check_assist_by_label(
49// FIXME: instead of having a separate function here, maybe use 49// FIXME: instead of having a separate function here, maybe use
50// `extract_ranges` and mark the target as `<target> </target>` in the 50// `extract_ranges` and mark the target as `<target> </target>` in the
51// fixture? 51// fixture?
52#[track_caller]
52pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { 53pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
53 check(assist, ra_fixture, ExpectedResult::Target(target), None); 54 check(assist, ra_fixture, ExpectedResult::Target(target), None);
54} 55}
55 56
57#[track_caller]
56pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { 58pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
57 check(assist, ra_fixture, ExpectedResult::NotApplicable, None); 59 check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
58} 60}
59 61
62#[track_caller]
60fn check_doc_test(assist_id: &str, before: &str, after: &str) { 63fn check_doc_test(assist_id: &str, before: &str, after: &str) {
61 let after = trim_indent(after); 64 let after = trim_indent(after);
62 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before); 65 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
@@ -95,6 +98,7 @@ enum ExpectedResult<'a> {
95 Target(&'a str), 98 Target(&'a str),
96} 99}
97 100
101#[track_caller]
98fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) { 102fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) {
99 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before); 103 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
100 let text_without_caret = db.file_text(file_with_caret_id).to_string(); 104 let text_without_caret = db.file_text(file_with_caret_id).to_string();
@@ -169,6 +173,9 @@ fn assist_order_field_struct() {
169 let mut assists = assists.iter(); 173 let mut assists = assists.iter();
170 174
171 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");
172 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`"); 179 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
173} 180}
174 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}