diff options
-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 | 7 | ||||
-rw-r--r-- | crates/assists/src/tests/generated.rs | 71 | ||||
-rw-r--r-- | crates/assists/src/utils.rs | 31 | ||||
-rw-r--r-- | crates/hir/src/source_analyzer.rs | 17 | ||||
-rw-r--r-- | crates/ide/src/references.rs | 23 | ||||
-rw-r--r-- | docs/dev/architecture.md | 2 | ||||
-rw-r--r-- | editors/code/.eslintrc.js | 5 | ||||
-rw-r--r-- | editors/code/src/client.ts | 4 | ||||
-rw-r--r-- | editors/code/src/commands.ts | 12 | ||||
-rw-r--r-- | editors/code/src/debug.ts | 12 | ||||
-rw-r--r-- | editors/code/src/inlay_hints.ts | 2 | ||||
-rw-r--r-- | editors/code/src/main.ts | 13 | ||||
-rw-r--r-- | editors/code/src/run.ts | 7 | ||||
-rw-r--r-- | editors/code/src/snippets.ts | 4 |
20 files changed, 675 insertions, 101 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 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] | ||
52 | pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) { | 53 | pub(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] | ||
56 | pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) { | 58 | pub(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] | ||
60 | fn check_doc_test(assist_id: &str, before: &str, after: &str) { | 63 | fn 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] | ||
98 | fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) { | 102 | fn 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] |
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 | } | ||
diff --git a/crates/hir/src/source_analyzer.rs b/crates/hir/src/source_analyzer.rs index 626c3078a..bed3fa50f 100644 --- a/crates/hir/src/source_analyzer.rs +++ b/crates/hir/src/source_analyzer.rs | |||
@@ -222,8 +222,9 @@ impl SourceAnalyzer { | |||
222 | db: &dyn HirDatabase, | 222 | db: &dyn HirDatabase, |
223 | path: &ast::Path, | 223 | path: &ast::Path, |
224 | ) -> Option<PathResolution> { | 224 | ) -> Option<PathResolution> { |
225 | let parent = || path.syntax().parent(); | ||
225 | let mut prefer_value_ns = false; | 226 | let mut prefer_value_ns = false; |
226 | if let Some(path_expr) = path.syntax().parent().and_then(ast::PathExpr::cast) { | 227 | if let Some(path_expr) = parent().and_then(ast::PathExpr::cast) { |
227 | let expr_id = self.expr_id(db, &path_expr.into())?; | 228 | let expr_id = self.expr_id(db, &path_expr.into())?; |
228 | let infer = self.infer.as_ref()?; | 229 | let infer = self.infer.as_ref()?; |
229 | if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) { | 230 | if let Some(assoc) = infer.assoc_resolutions_for_expr(expr_id) { |
@@ -237,7 +238,7 @@ impl SourceAnalyzer { | |||
237 | prefer_value_ns = true; | 238 | prefer_value_ns = true; |
238 | } | 239 | } |
239 | 240 | ||
240 | if let Some(path_pat) = path.syntax().parent().and_then(ast::PathPat::cast) { | 241 | if let Some(path_pat) = parent().and_then(ast::PathPat::cast) { |
241 | let pat_id = self.pat_id(&path_pat.into())?; | 242 | let pat_id = self.pat_id(&path_pat.into())?; |
242 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { | 243 | if let Some(assoc) = self.infer.as_ref()?.assoc_resolutions_for_pat(pat_id) { |
243 | return Some(PathResolution::AssocItem(assoc.into())); | 244 | return Some(PathResolution::AssocItem(assoc.into())); |
@@ -249,7 +250,7 @@ impl SourceAnalyzer { | |||
249 | } | 250 | } |
250 | } | 251 | } |
251 | 252 | ||
252 | if let Some(rec_lit) = path.syntax().parent().and_then(ast::RecordExpr::cast) { | 253 | if let Some(rec_lit) = parent().and_then(ast::RecordExpr::cast) { |
253 | let expr_id = self.expr_id(db, &rec_lit.into())?; | 254 | let expr_id = self.expr_id(db, &rec_lit.into())?; |
254 | if let Some(VariantId::EnumVariantId(variant)) = | 255 | if let Some(VariantId::EnumVariantId(variant)) = |
255 | self.infer.as_ref()?.variant_resolution_for_expr(expr_id) | 256 | self.infer.as_ref()?.variant_resolution_for_expr(expr_id) |
@@ -258,8 +259,12 @@ impl SourceAnalyzer { | |||
258 | } | 259 | } |
259 | } | 260 | } |
260 | 261 | ||
261 | if let Some(rec_pat) = path.syntax().parent().and_then(ast::RecordPat::cast) { | 262 | if let Some(pat) = parent() |
262 | let pat_id = self.pat_id(&rec_pat.into())?; | 263 | .and_then(ast::RecordPat::cast) |
264 | .map(ast::Pat::from) | ||
265 | .or_else(|| parent().and_then(ast::TupleStructPat::cast).map(ast::Pat::from)) | ||
266 | { | ||
267 | let pat_id = self.pat_id(&pat)?; | ||
263 | if let Some(VariantId::EnumVariantId(variant)) = | 268 | if let Some(VariantId::EnumVariantId(variant)) = |
264 | self.infer.as_ref()?.variant_resolution_for_pat(pat_id) | 269 | self.infer.as_ref()?.variant_resolution_for_pat(pat_id) |
265 | { | 270 | { |
@@ -272,7 +277,7 @@ impl SourceAnalyzer { | |||
272 | 277 | ||
273 | // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we | 278 | // Case where path is a qualifier of another path, e.g. foo::bar::Baz where we |
274 | // trying to resolve foo::bar. | 279 | // trying to resolve foo::bar. |
275 | if let Some(outer_path) = path.syntax().parent().and_then(ast::Path::cast) { | 280 | if let Some(outer_path) = parent().and_then(ast::Path::cast) { |
276 | if let Some(qualifier) = outer_path.qualifier() { | 281 | if let Some(qualifier) = outer_path.qualifier() { |
277 | if path == &qualifier { | 282 | if path == &qualifier { |
278 | return resolve_hir_path_qualifier(db, &self.resolver, &hir_path); | 283 | return resolve_hir_path_qualifier(db, &self.resolver, &hir_path); |
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs index 40d9487eb..6999dacee 100644 --- a/crates/ide/src/references.rs +++ b/crates/ide/src/references.rs | |||
@@ -1114,4 +1114,27 @@ trait Foo { | |||
1114 | "#]], | 1114 | "#]], |
1115 | ); | 1115 | ); |
1116 | } | 1116 | } |
1117 | |||
1118 | #[test] | ||
1119 | fn test_self_variant_with_payload() { | ||
1120 | check( | ||
1121 | r#" | ||
1122 | enum Foo { Bar() } | ||
1123 | |||
1124 | impl Foo { | ||
1125 | fn foo(self) { | ||
1126 | match self { | ||
1127 | Self::Bar$0() => (), | ||
1128 | } | ||
1129 | } | ||
1130 | } | ||
1131 | |||
1132 | "#, | ||
1133 | expect![[r#" | ||
1134 | Bar Variant FileId(0) 11..16 11..14 Other | ||
1135 | |||
1136 | FileId(0) 89..92 Other | ||
1137 | "#]], | ||
1138 | ); | ||
1139 | } | ||
1117 | } | 1140 | } |
diff --git a/docs/dev/architecture.md b/docs/dev/architecture.md index 01063824f..081ee5b9d 100644 --- a/docs/dev/architecture.md +++ b/docs/dev/architecture.md | |||
@@ -372,11 +372,11 @@ Tests which directly call various API functions are a liability, because they ma | |||
372 | So most of the tests look like this: | 372 | So most of the tests look like this: |
373 | 373 | ||
374 | ```rust | 374 | ```rust |
375 | #[track_caller] | ||
375 | fn check(input: &str, expect: expect_test::Expect) { | 376 | fn check(input: &str, expect: expect_test::Expect) { |
376 | // The single place that actually exercises a particular API | 377 | // The single place that actually exercises a particular API |
377 | } | 378 | } |
378 | 379 | ||
379 | |||
380 | #[test] | 380 | #[test] |
381 | fn foo() { | 381 | fn foo() { |
382 | check("foo", expect![["bar"]]); | 382 | check("foo", expect![["bar"]]); |
diff --git a/editors/code/.eslintrc.js b/editors/code/.eslintrc.js index c6bf410f4..b145330a0 100644 --- a/editors/code/.eslintrc.js +++ b/editors/code/.eslintrc.js | |||
@@ -14,7 +14,7 @@ module.exports = { | |||
14 | "rules": { | 14 | "rules": { |
15 | "camelcase": ["error"], | 15 | "camelcase": ["error"], |
16 | "eqeqeq": ["error", "always", { "null": "ignore" }], | 16 | "eqeqeq": ["error", "always", { "null": "ignore" }], |
17 | "no-console": ["error"], | 17 | "no-console": ["error", { allow: ["warn", "error"] }], |
18 | "prefer-const": "error", | 18 | "prefer-const": "error", |
19 | "@typescript-eslint/member-delimiter-style": [ | 19 | "@typescript-eslint/member-delimiter-style": [ |
20 | "error", | 20 | "error", |
@@ -33,6 +33,7 @@ module.exports = { | |||
33 | "error", | 33 | "error", |
34 | "always" | 34 | "always" |
35 | ], | 35 | ], |
36 | "@typescript-eslint/no-unnecessary-type-assertion": "error" | 36 | "@typescript-eslint/no-unnecessary-type-assertion": "error", |
37 | "@typescript-eslint/no-floating-promises": "error" | ||
37 | } | 38 | } |
38 | }; | 39 | }; |
diff --git a/editors/code/src/client.ts b/editors/code/src/client.ts index 539e487ec..aec5c70c0 100644 --- a/editors/code/src/client.ts +++ b/editors/code/src/client.ts | |||
@@ -11,7 +11,7 @@ export interface Env { | |||
11 | } | 11 | } |
12 | 12 | ||
13 | function renderCommand(cmd: ra.CommandLink) { | 13 | function renderCommand(cmd: ra.CommandLink) { |
14 | return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip!}')`; | 14 | return `[${cmd.title}](command:${cmd.command}?${encodeURIComponent(JSON.stringify(cmd.arguments))} '${cmd.tooltip}')`; |
15 | } | 15 | } |
16 | 16 | ||
17 | function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { | 17 | function renderHoverActions(actions: ra.CommandLinkGroup[]): vscode.MarkdownString { |
@@ -138,7 +138,7 @@ export function createClient(serverPath: string, cwd: string, extraEnv: Env): lc | |||
138 | command: "rust-analyzer.applyActionGroup", | 138 | command: "rust-analyzer.applyActionGroup", |
139 | title: "", | 139 | title: "", |
140 | arguments: [items.map((item) => { | 140 | arguments: [items.map((item) => { |
141 | return { label: item.title, arguments: item.command!!.arguments!![0] }; | 141 | return { label: item.title, arguments: item.command!.arguments![0] }; |
142 | })], | 142 | })], |
143 | }; | 143 | }; |
144 | 144 | ||
diff --git a/editors/code/src/commands.ts b/editors/code/src/commands.ts index e3a23c0d2..283b9a160 100644 --- a/editors/code/src/commands.ts +++ b/editors/code/src/commands.ts | |||
@@ -125,7 +125,7 @@ export function joinLines(ctx: Ctx): Cmd { | |||
125 | ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), | 125 | ranges: editor.selections.map((it) => client.code2ProtocolConverter.asRange(it)), |
126 | textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), | 126 | textDocument: ctx.client.code2ProtocolConverter.asTextDocumentIdentifier(editor.document), |
127 | }); | 127 | }); |
128 | editor.edit((builder) => { | 128 | await editor.edit((builder) => { |
129 | client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => { | 129 | client.protocol2CodeConverter.asTextEdits(items).forEach((edit: any) => { |
130 | builder.replace(edit.range, edit.newText); | 130 | builder.replace(edit.range, edit.newText); |
131 | }); | 131 | }); |
@@ -236,7 +236,7 @@ export function ssr(ctx: Ctx): Cmd { | |||
236 | const request = await vscode.window.showInputBox(options); | 236 | const request = await vscode.window.showInputBox(options); |
237 | if (!request) return; | 237 | if (!request) return; |
238 | 238 | ||
239 | vscode.window.withProgress({ | 239 | await vscode.window.withProgress({ |
240 | location: vscode.ProgressLocation.Notification, | 240 | location: vscode.ProgressLocation.Notification, |
241 | title: "Structured search replace in progress...", | 241 | title: "Structured search replace in progress...", |
242 | cancellable: false, | 242 | cancellable: false, |
@@ -457,10 +457,10 @@ export function reloadWorkspace(ctx: Ctx): Cmd { | |||
457 | } | 457 | } |
458 | 458 | ||
459 | export function showReferences(ctx: Ctx): Cmd { | 459 | export function showReferences(ctx: Ctx): Cmd { |
460 | return (uri: string, position: lc.Position, locations: lc.Location[]) => { | 460 | return async (uri: string, position: lc.Position, locations: lc.Location[]) => { |
461 | const client = ctx.client; | 461 | const client = ctx.client; |
462 | if (client) { | 462 | if (client) { |
463 | vscode.commands.executeCommand( | 463 | await vscode.commands.executeCommand( |
464 | 'editor.action.showReferences', | 464 | 'editor.action.showReferences', |
465 | vscode.Uri.parse(uri), | 465 | vscode.Uri.parse(uri), |
466 | client.protocol2CodeConverter.asPosition(position), | 466 | client.protocol2CodeConverter.asPosition(position), |
@@ -474,7 +474,7 @@ export function applyActionGroup(_ctx: Ctx): Cmd { | |||
474 | return async (actions: { label: string; arguments: lc.CodeAction }[]) => { | 474 | return async (actions: { label: string; arguments: lc.CodeAction }[]) => { |
475 | const selectedAction = await vscode.window.showQuickPick(actions); | 475 | const selectedAction = await vscode.window.showQuickPick(actions); |
476 | if (!selectedAction) return; | 476 | if (!selectedAction) return; |
477 | vscode.commands.executeCommand( | 477 | await vscode.commands.executeCommand( |
478 | 'rust-analyzer.resolveCodeAction', | 478 | 'rust-analyzer.resolveCodeAction', |
479 | selectedAction.arguments, | 479 | selectedAction.arguments, |
480 | ); | 480 | ); |
@@ -510,7 +510,7 @@ export function openDocs(ctx: Ctx): Cmd { | |||
510 | const doclink = await client.sendRequest(ra.openDocs, { position, textDocument }); | 510 | const doclink = await client.sendRequest(ra.openDocs, { position, textDocument }); |
511 | 511 | ||
512 | if (doclink != null) { | 512 | if (doclink != null) { |
513 | vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink)); | 513 | await vscode.commands.executeCommand("vscode.open", vscode.Uri.parse(doclink)); |
514 | } | 514 | } |
515 | }; | 515 | }; |
516 | 516 | ||
diff --git a/editors/code/src/debug.ts b/editors/code/src/debug.ts index 925126a16..3889a2773 100644 --- a/editors/code/src/debug.ts +++ b/editors/code/src/debug.ts | |||
@@ -77,7 +77,7 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v | |||
77 | } | 77 | } |
78 | 78 | ||
79 | if (!debugEngine) { | 79 | if (!debugEngine) { |
80 | vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` | 80 | await vscode.window.showErrorMessage(`Install [CodeLLDB](https://marketplace.visualstudio.com/items?itemName=vadimcn.vscode-lldb)` |
81 | + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); | 81 | + ` or [MS C++ tools](https://marketplace.visualstudio.com/items?itemName=ms-vscode.cpptools) extension for debugging.`); |
82 | return; | 82 | return; |
83 | } | 83 | } |
@@ -86,12 +86,14 @@ async function getDebugConfiguration(ctx: Ctx, runnable: ra.Runnable): Promise<v | |||
86 | if (ctx.config.debug.openDebugPane) { | 86 | if (ctx.config.debug.openDebugPane) { |
87 | debugOutput.show(true); | 87 | debugOutput.show(true); |
88 | } | 88 | } |
89 | 89 | // folder exists or RA is not active. | |
90 | const isMultiFolderWorkspace = vscode.workspace.workspaceFolders!.length > 1; | 90 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion |
91 | const firstWorkspace = vscode.workspace.workspaceFolders![0]; // folder exists or RA is not active. | 91 | const workspaceFolders = vscode.workspace.workspaceFolders!; |
92 | const isMultiFolderWorkspace = workspaceFolders.length > 1; | ||
93 | const firstWorkspace = workspaceFolders[0]; | ||
92 | const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ? | 94 | const workspace = !isMultiFolderWorkspace || !runnable.args.workspaceRoot ? |
93 | firstWorkspace : | 95 | firstWorkspace : |
94 | vscode.workspace.workspaceFolders!.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace; | 96 | workspaceFolders.find(w => runnable.args.workspaceRoot?.includes(w.uri.fsPath)) || firstWorkspace; |
95 | 97 | ||
96 | const wsFolder = path.normalize(workspace.uri.fsPath); | 98 | const wsFolder = path.normalize(workspace.uri.fsPath); |
97 | const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ''; | 99 | const workspaceQualifier = isMultiFolderWorkspace ? `:${workspace.name}` : ''; |
diff --git a/editors/code/src/inlay_hints.ts b/editors/code/src/inlay_hints.ts index 38eb1c15b..61db6b8d0 100644 --- a/editors/code/src/inlay_hints.ts +++ b/editors/code/src/inlay_hints.ts | |||
@@ -36,7 +36,7 @@ export function activateInlayHints(ctx: Ctx) { | |||
36 | maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions | 36 | maybeUpdater.onConfigChange, maybeUpdater, ctx.subscriptions |
37 | ); | 37 | ); |
38 | 38 | ||
39 | maybeUpdater.onConfigChange(); | 39 | maybeUpdater.onConfigChange().catch(console.error); |
40 | } | 40 | } |
41 | 41 | ||
42 | const typeHints = createHintStyle("type"); | 42 | const typeHints = createHintStyle("type"); |
diff --git a/editors/code/src/main.ts b/editors/code/src/main.ts index 43cae5c7f..d18d6c8a9 100644 --- a/editors/code/src/main.ts +++ b/editors/code/src/main.ts | |||
@@ -76,7 +76,7 @@ async function tryActivate(context: vscode.ExtensionContext) { | |||
76 | // This a horribly, horribly wrong way to deal with this problem. | 76 | // This a horribly, horribly wrong way to deal with this problem. |
77 | ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); | 77 | ctx = await Ctx.create(config, context, serverPath, workspaceFolder.uri.fsPath); |
78 | 78 | ||
79 | setContextValue(RUST_PROJECT_CONTEXT_NAME, true); | 79 | await setContextValue(RUST_PROJECT_CONTEXT_NAME, true); |
80 | 80 | ||
81 | // Commands which invokes manually via command palette, shortcut, etc. | 81 | // Commands which invokes manually via command palette, shortcut, etc. |
82 | 82 | ||
@@ -143,7 +143,7 @@ async function tryActivate(context: vscode.ExtensionContext) { | |||
143 | } | 143 | } |
144 | 144 | ||
145 | export async function deactivate() { | 145 | export async function deactivate() { |
146 | setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined); | 146 | await setContextValue(RUST_PROJECT_CONTEXT_NAME, undefined); |
147 | await ctx?.client.stop(); | 147 | await ctx?.client.stop(); |
148 | ctx = undefined; | 148 | ctx = undefined; |
149 | } | 149 | } |
@@ -184,10 +184,10 @@ async function bootstrapExtension(config: Config, state: PersistentState): Promi | |||
184 | 184 | ||
185 | const release = await downloadWithRetryDialog(state, async () => { | 185 | const release = await downloadWithRetryDialog(state, async () => { |
186 | return await fetchRelease("nightly", state.githubToken); | 186 | return await fetchRelease("nightly", state.githubToken); |
187 | }).catch((e) => { | 187 | }).catch(async (e) => { |
188 | log.error(e); | 188 | log.error(e); |
189 | if (state.releaseId === undefined) { // Show error only for the initial download | 189 | if (state.releaseId === undefined) { // Show error only for the initial download |
190 | vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); | 190 | await vscode.window.showErrorMessage(`Failed to download rust-analyzer nightly ${e}`); |
191 | } | 191 | } |
192 | return undefined; | 192 | return undefined; |
193 | }); | 193 | }); |
@@ -299,7 +299,7 @@ async function getServer(config: Config, state: PersistentState): Promise<string | |||
299 | }; | 299 | }; |
300 | const platform = platforms[`${process.arch} ${process.platform}`]; | 300 | const platform = platforms[`${process.arch} ${process.platform}`]; |
301 | if (platform === undefined) { | 301 | if (platform === undefined) { |
302 | vscode.window.showErrorMessage( | 302 | await vscode.window.showErrorMessage( |
303 | "Unfortunately we don't ship binaries for your platform yet. " + | 303 | "Unfortunately we don't ship binaries for your platform yet. " + |
304 | "You need to manually clone rust-analyzer repository and " + | 304 | "You need to manually clone rust-analyzer repository and " + |
305 | "run `cargo xtask install --server` to build the language server from sources. " + | 305 | "run `cargo xtask install --server` to build the language server from sources. " + |
@@ -434,6 +434,7 @@ function warnAboutExtensionConflicts() { | |||
434 | vscode.window.showWarningMessage( | 434 | vscode.window.showWarningMessage( |
435 | `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` + | 435 | `You have both the ${fst[0]} (${fst[1]}) and ${sec[0]} (${sec[1]}) ` + |
436 | "plugins enabled. These are known to conflict and cause various functions of " + | 436 | "plugins enabled. These are known to conflict and cause various functions of " + |
437 | "both plugins to not work correctly. You should disable one of them.", "Got it"); | 437 | "both plugins to not work correctly. You should disable one of them.", "Got it") |
438 | .then(() => { }, console.error); | ||
438 | }; | 439 | }; |
439 | } | 440 | } |
diff --git a/editors/code/src/run.ts b/editors/code/src/run.ts index f42baed16..138e3f686 100644 --- a/editors/code/src/run.ts +++ b/editors/code/src/run.ts | |||
@@ -45,7 +45,7 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, | |||
45 | if (items.length === 0) { | 45 | if (items.length === 0) { |
46 | // it is the debug case, run always has at least 'cargo check ...' | 46 | // it is the debug case, run always has at least 'cargo check ...' |
47 | // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables | 47 | // see crates\rust-analyzer\src\main_loop\handlers.rs, handle_runnables |
48 | vscode.window.showErrorMessage("There's no debug target!"); | 48 | await vscode.window.showErrorMessage("There's no debug target!"); |
49 | return; | 49 | return; |
50 | } | 50 | } |
51 | 51 | ||
@@ -65,8 +65,8 @@ export async function selectRunnable(ctx: Ctx, prevRunnable?: RunnableQuickPick, | |||
65 | disposables.push( | 65 | disposables.push( |
66 | quickPick.onDidHide(() => close()), | 66 | quickPick.onDidHide(() => close()), |
67 | quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), | 67 | quickPick.onDidAccept(() => close(quickPick.selectedItems[0])), |
68 | quickPick.onDidTriggerButton((_button) => { | 68 | quickPick.onDidTriggerButton(async (_button) => { |
69 | (async () => await makeDebugConfig(ctx, quickPick.activeItems[0].runnable))(); | 69 | await makeDebugConfig(ctx, quickPick.activeItems[0].runnable); |
70 | close(); | 70 | close(); |
71 | }), | 71 | }), |
72 | quickPick.onDidChangeActive((active) => { | 72 | quickPick.onDidChangeActive((active) => { |
@@ -139,6 +139,7 @@ export async function createTask(runnable: ra.Runnable, config: Config): Promise | |||
139 | overrideCargo: runnable.args.overrideCargo, | 139 | overrideCargo: runnable.args.overrideCargo, |
140 | }; | 140 | }; |
141 | 141 | ||
142 | // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion | ||
142 | const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() | 143 | const target = vscode.workspace.workspaceFolders![0]; // safe, see main activate() |
143 | const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); | 144 | const cargoTask = await tasks.buildCargoTask(target, definition, runnable.label, args, config.cargoRunner, true); |
144 | cargoTask.presentationOptions.clear = true; | 145 | cargoTask.presentationOptions.clear = true; |
diff --git a/editors/code/src/snippets.ts b/editors/code/src/snippets.ts index fee736e7d..dc53ebe2e 100644 --- a/editors/code/src/snippets.ts +++ b/editors/code/src/snippets.ts | |||
@@ -62,7 +62,9 @@ function parseSnippet(snip: string): [string, [number, number]] | undefined { | |||
62 | const m = snip.match(/\$(0|\{0:([^}]*)\})/); | 62 | const m = snip.match(/\$(0|\{0:([^}]*)\})/); |
63 | if (!m) return undefined; | 63 | if (!m) return undefined; |
64 | const placeholder = m[2] ?? ""; | 64 | const placeholder = m[2] ?? ""; |
65 | const range: [number, number] = [m.index!!, placeholder.length]; | 65 | if (m.index == null) |
66 | return undefined; | ||
67 | const range: [number, number] = [m.index, placeholder.length]; | ||
66 | const insert = snip.replace(m[0], placeholder); | 68 | const insert = snip.replace(m[0], placeholder); |
67 | return [insert, range]; | 69 | return [insert, range]; |
68 | } | 70 | } |