diff options
author | bors[bot] <26634292+bors[bot]@users.noreply.github.com> | 2020-10-15 16:11:36 +0100 |
---|---|---|
committer | GitHub <[email protected]> | 2020-10-15 16:11:36 +0100 |
commit | 7fadc78ebb43b0eb9b6ccf314e29bdd23717542f (patch) | |
tree | a8ed4d8e9ff783b8a0ed6a2355580716851dd211 | |
parent | 8090799aa2f2a83746780ebdd7837e0e280c2d2c (diff) | |
parent | 3086fb2ef4064ab7daa8194a8d57c742849ca900 (diff) |
Merge #6242
6242: Diagnost shorthand in patterns r=matklad a=matklad
bors r+
🤖
Co-authored-by: Aleksey Kladov <[email protected]>
-rw-r--r-- | crates/ide/src/diagnostics.rs | 95 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/field_shorthand.rs | 206 |
2 files changed, 210 insertions, 91 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index b30cdb6ed..1e5ea4617 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -5,6 +5,7 @@ | |||
5 | //! original files. So we need to map the ranges. | 5 | //! original files. So we need to map the ranges. |
6 | 6 | ||
7 | mod fixes; | 7 | mod fixes; |
8 | mod field_shorthand; | ||
8 | 9 | ||
9 | use std::cell::RefCell; | 10 | use std::cell::RefCell; |
10 | 11 | ||
@@ -80,7 +81,7 @@ pub(crate) fn diagnostics( | |||
80 | 81 | ||
81 | for node in parse.tree().syntax().descendants() { | 82 | for node in parse.tree().syntax().descendants() { |
82 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); | 83 | check_unnecessary_braces_in_use_statement(&mut res, file_id, &node); |
83 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 84 | field_shorthand::check(&mut res, file_id, &node); |
84 | } | 85 | } |
85 | let res = RefCell::new(res); | 86 | let res = RefCell::new(res); |
86 | let sink_builder = DiagnosticSinkBuilder::new() | 87 | let sink_builder = DiagnosticSinkBuilder::new() |
@@ -188,42 +189,6 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
188 | None | 189 | None |
189 | } | 190 | } |
190 | 191 | ||
191 | fn check_struct_shorthand_initialization( | ||
192 | acc: &mut Vec<Diagnostic>, | ||
193 | file_id: FileId, | ||
194 | node: &SyntaxNode, | ||
195 | ) -> Option<()> { | ||
196 | let record_lit = ast::RecordExpr::cast(node.clone())?; | ||
197 | let record_field_list = record_lit.record_expr_field_list()?; | ||
198 | for record_field in record_field_list.fields() { | ||
199 | if let (Some(name_ref), Some(expr)) = (record_field.name_ref(), record_field.expr()) { | ||
200 | let field_name = name_ref.syntax().text().to_string(); | ||
201 | let field_expr = expr.syntax().text().to_string(); | ||
202 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
203 | if field_name == field_expr && !field_name_is_tup_index { | ||
204 | let mut edit_builder = TextEdit::builder(); | ||
205 | edit_builder.delete(record_field.syntax().text_range()); | ||
206 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
207 | let edit = edit_builder.finish(); | ||
208 | |||
209 | let field_range = record_field.syntax().text_range(); | ||
210 | acc.push(Diagnostic { | ||
211 | // name: None, | ||
212 | range: field_range, | ||
213 | message: "Shorthand struct initialization".to_string(), | ||
214 | severity: Severity::WeakWarning, | ||
215 | fix: Some(Fix::new( | ||
216 | "Use struct shorthand initialization", | ||
217 | SourceFileEdit { file_id, edit }.into(), | ||
218 | field_range, | ||
219 | )), | ||
220 | }); | ||
221 | } | ||
222 | } | ||
223 | } | ||
224 | Some(()) | ||
225 | } | ||
226 | |||
227 | #[cfg(test)] | 192 | #[cfg(test)] |
228 | mod tests { | 193 | mod tests { |
229 | use expect_test::{expect, Expect}; | 194 | use expect_test::{expect, Expect}; |
@@ -237,7 +202,7 @@ mod tests { | |||
237 | /// * a diagnostic is produced | 202 | /// * a diagnostic is produced |
238 | /// * this diagnostic fix trigger range touches the input cursor position | 203 | /// * this diagnostic fix trigger range touches the input cursor position |
239 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied | 204 | /// * that the contents of the file containing the cursor match `after` after the diagnostic fix is applied |
240 | fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { | 205 | pub(super) fn check_fix(ra_fixture_before: &str, ra_fixture_after: &str) { |
241 | let after = trim_indent(ra_fixture_after); | 206 | let after = trim_indent(ra_fixture_after); |
242 | 207 | ||
243 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 208 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
@@ -319,7 +284,7 @@ mod tests { | |||
319 | 284 | ||
320 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics | 285 | /// Takes a multi-file input fixture with annotated cursor position and checks that no diagnostics |
321 | /// apply to the file containing the cursor. | 286 | /// apply to the file containing the cursor. |
322 | fn check_no_diagnostics(ra_fixture: &str) { | 287 | pub(crate) fn check_no_diagnostics(ra_fixture: &str) { |
323 | let (analysis, files) = fixture::files(ra_fixture); | 288 | let (analysis, files) = fixture::files(ra_fixture); |
324 | let diagnostics = files | 289 | let diagnostics = files |
325 | .into_iter() | 290 | .into_iter() |
@@ -720,58 +685,6 @@ mod a { | |||
720 | } | 685 | } |
721 | 686 | ||
722 | #[test] | 687 | #[test] |
723 | fn test_check_struct_shorthand_initialization() { | ||
724 | check_no_diagnostics( | ||
725 | r#" | ||
726 | struct A { a: &'static str } | ||
727 | fn main() { A { a: "hello" } } | ||
728 | "#, | ||
729 | ); | ||
730 | check_no_diagnostics( | ||
731 | r#" | ||
732 | struct A(usize); | ||
733 | fn main() { A { 0: 0 } } | ||
734 | "#, | ||
735 | ); | ||
736 | |||
737 | check_fix( | ||
738 | r#" | ||
739 | struct A { a: &'static str } | ||
740 | fn main() { | ||
741 | let a = "haha"; | ||
742 | A { a<|>: a } | ||
743 | } | ||
744 | "#, | ||
745 | r#" | ||
746 | struct A { a: &'static str } | ||
747 | fn main() { | ||
748 | let a = "haha"; | ||
749 | A { a } | ||
750 | } | ||
751 | "#, | ||
752 | ); | ||
753 | |||
754 | check_fix( | ||
755 | r#" | ||
756 | struct A { a: &'static str, b: &'static str } | ||
757 | fn main() { | ||
758 | let a = "haha"; | ||
759 | let b = "bb"; | ||
760 | A { a<|>: a, b } | ||
761 | } | ||
762 | "#, | ||
763 | r#" | ||
764 | struct A { a: &'static str, b: &'static str } | ||
765 | fn main() { | ||
766 | let a = "haha"; | ||
767 | let b = "bb"; | ||
768 | A { a, b } | ||
769 | } | ||
770 | "#, | ||
771 | ); | ||
772 | } | ||
773 | |||
774 | #[test] | ||
775 | fn test_add_field_from_usage() { | 688 | fn test_add_field_from_usage() { |
776 | check_fix( | 689 | check_fix( |
777 | r" | 690 | r" |
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs new file mode 100644 index 000000000..2c4acd783 --- /dev/null +++ b/crates/ide/src/diagnostics/field_shorthand.rs | |||
@@ -0,0 +1,206 @@ | |||
1 | //! Suggests shortening `Foo { field: field }` to `Foo { field }` in both | ||
2 | //! expressions and patterns. | ||
3 | |||
4 | use base_db::FileId; | ||
5 | use ide_db::source_change::SourceFileEdit; | ||
6 | use syntax::{ast, match_ast, AstNode, SyntaxNode}; | ||
7 | use text_edit::TextEdit; | ||
8 | |||
9 | use crate::{Diagnostic, Fix, Severity}; | ||
10 | |||
11 | pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { | ||
12 | match_ast! { | ||
13 | match node { | ||
14 | ast::RecordExpr(it) => check_expr_field_shorthand(acc, file_id, it), | ||
15 | ast::RecordPat(it) => check_pat_field_shorthand(acc, file_id, it), | ||
16 | _ => () | ||
17 | } | ||
18 | }; | ||
19 | } | ||
20 | |||
21 | fn check_expr_field_shorthand( | ||
22 | acc: &mut Vec<Diagnostic>, | ||
23 | file_id: FileId, | ||
24 | record_expr: ast::RecordExpr, | ||
25 | ) { | ||
26 | let record_field_list = match record_expr.record_expr_field_list() { | ||
27 | Some(it) => it, | ||
28 | None => return, | ||
29 | }; | ||
30 | for record_field in record_field_list.fields() { | ||
31 | let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) { | ||
32 | Some(it) => it, | ||
33 | None => continue, | ||
34 | }; | ||
35 | |||
36 | let field_name = name_ref.syntax().text().to_string(); | ||
37 | let field_expr = expr.syntax().text().to_string(); | ||
38 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
39 | if field_name != field_expr || field_name_is_tup_index { | ||
40 | continue; | ||
41 | } | ||
42 | |||
43 | let mut edit_builder = TextEdit::builder(); | ||
44 | edit_builder.delete(record_field.syntax().text_range()); | ||
45 | edit_builder.insert(record_field.syntax().text_range().start(), field_name); | ||
46 | let edit = edit_builder.finish(); | ||
47 | |||
48 | let field_range = record_field.syntax().text_range(); | ||
49 | acc.push(Diagnostic { | ||
50 | // name: None, | ||
51 | range: field_range, | ||
52 | message: "Shorthand struct initialization".to_string(), | ||
53 | severity: Severity::WeakWarning, | ||
54 | fix: Some(Fix::new( | ||
55 | "Use struct shorthand initialization", | ||
56 | SourceFileEdit { file_id, edit }.into(), | ||
57 | field_range, | ||
58 | )), | ||
59 | }); | ||
60 | } | ||
61 | } | ||
62 | |||
63 | fn check_pat_field_shorthand( | ||
64 | acc: &mut Vec<Diagnostic>, | ||
65 | file_id: FileId, | ||
66 | record_pat: ast::RecordPat, | ||
67 | ) { | ||
68 | let record_pat_field_list = match record_pat.record_pat_field_list() { | ||
69 | Some(it) => it, | ||
70 | None => return, | ||
71 | }; | ||
72 | for record_pat_field in record_pat_field_list.fields() { | ||
73 | let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) { | ||
74 | Some(it) => it, | ||
75 | None => continue, | ||
76 | }; | ||
77 | |||
78 | let field_name = name_ref.syntax().text().to_string(); | ||
79 | let field_pat = pat.syntax().text().to_string(); | ||
80 | let field_name_is_tup_index = name_ref.as_tuple_field().is_some(); | ||
81 | if field_name != field_pat || field_name_is_tup_index { | ||
82 | continue; | ||
83 | } | ||
84 | |||
85 | let mut edit_builder = TextEdit::builder(); | ||
86 | edit_builder.delete(record_pat_field.syntax().text_range()); | ||
87 | edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name); | ||
88 | let edit = edit_builder.finish(); | ||
89 | |||
90 | let field_range = record_pat_field.syntax().text_range(); | ||
91 | acc.push(Diagnostic { | ||
92 | // name: None, | ||
93 | range: field_range, | ||
94 | message: "Shorthand struct pattern".to_string(), | ||
95 | severity: Severity::WeakWarning, | ||
96 | fix: Some(Fix::new( | ||
97 | "Use struct field shorthand", | ||
98 | SourceFileEdit { file_id, edit }.into(), | ||
99 | field_range, | ||
100 | )), | ||
101 | }); | ||
102 | } | ||
103 | } | ||
104 | |||
105 | #[cfg(test)] | ||
106 | mod tests { | ||
107 | use crate::diagnostics::tests::{check_fix, check_no_diagnostics}; | ||
108 | |||
109 | #[test] | ||
110 | fn test_check_expr_field_shorthand() { | ||
111 | check_no_diagnostics( | ||
112 | r#" | ||
113 | struct A { a: &'static str } | ||
114 | fn main() { A { a: "hello" } } | ||
115 | "#, | ||
116 | ); | ||
117 | check_no_diagnostics( | ||
118 | r#" | ||
119 | struct A(usize); | ||
120 | fn main() { A { 0: 0 } } | ||
121 | "#, | ||
122 | ); | ||
123 | |||
124 | check_fix( | ||
125 | r#" | ||
126 | struct A { a: &'static str } | ||
127 | fn main() { | ||
128 | let a = "haha"; | ||
129 | A { a<|>: a } | ||
130 | } | ||
131 | "#, | ||
132 | r#" | ||
133 | struct A { a: &'static str } | ||
134 | fn main() { | ||
135 | let a = "haha"; | ||
136 | A { a } | ||
137 | } | ||
138 | "#, | ||
139 | ); | ||
140 | |||
141 | check_fix( | ||
142 | r#" | ||
143 | struct A { a: &'static str, b: &'static str } | ||
144 | fn main() { | ||
145 | let a = "haha"; | ||
146 | let b = "bb"; | ||
147 | A { a<|>: a, b } | ||
148 | } | ||
149 | "#, | ||
150 | r#" | ||
151 | struct A { a: &'static str, b: &'static str } | ||
152 | fn main() { | ||
153 | let a = "haha"; | ||
154 | let b = "bb"; | ||
155 | A { a, b } | ||
156 | } | ||
157 | "#, | ||
158 | ); | ||
159 | } | ||
160 | |||
161 | #[test] | ||
162 | fn test_check_pat_field_shorthand() { | ||
163 | check_no_diagnostics( | ||
164 | r#" | ||
165 | struct A { a: &'static str } | ||
166 | fn f(a: A) { let A { a: hello } = a; } | ||
167 | "#, | ||
168 | ); | ||
169 | check_no_diagnostics( | ||
170 | r#" | ||
171 | struct A(usize); | ||
172 | fn f(a: A) { let A { 0: 0 } = a; } | ||
173 | "#, | ||
174 | ); | ||
175 | |||
176 | check_fix( | ||
177 | r#" | ||
178 | struct A { a: &'static str } | ||
179 | fn f(a: A) { | ||
180 | let A { a<|>: a } = a; | ||
181 | } | ||
182 | "#, | ||
183 | r#" | ||
184 | struct A { a: &'static str } | ||
185 | fn f(a: A) { | ||
186 | let A { a } = a; | ||
187 | } | ||
188 | "#, | ||
189 | ); | ||
190 | |||
191 | check_fix( | ||
192 | r#" | ||
193 | struct A { a: &'static str, b: &'static str } | ||
194 | fn f(a: A) { | ||
195 | let A { a<|>: a, b } = a; | ||
196 | } | ||
197 | "#, | ||
198 | r#" | ||
199 | struct A { a: &'static str, b: &'static str } | ||
200 | fn f(a: A) { | ||
201 | let A { a, b } = a; | ||
202 | } | ||
203 | "#, | ||
204 | ); | ||
205 | } | ||
206 | } | ||