aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-10-15 16:11:36 +0100
committerGitHub <[email protected]>2020-10-15 16:11:36 +0100
commit7fadc78ebb43b0eb9b6ccf314e29bdd23717542f (patch)
treea8ed4d8e9ff783b8a0ed6a2355580716851dd211
parent8090799aa2f2a83746780ebdd7837e0e280c2d2c (diff)
parent3086fb2ef4064ab7daa8194a8d57c742849ca900 (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.rs95
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs206
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
7mod fixes; 7mod fixes;
8mod field_shorthand;
8 9
9use std::cell::RefCell; 10use 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
191fn 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)]
228mod tests { 193mod 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#"
726struct A { a: &'static str }
727fn main() { A { a: "hello" } }
728"#,
729 );
730 check_no_diagnostics(
731 r#"
732struct A(usize);
733fn main() { A { 0: 0 } }
734"#,
735 );
736
737 check_fix(
738 r#"
739struct A { a: &'static str }
740fn main() {
741 let a = "haha";
742 A { a<|>: a }
743}
744"#,
745 r#"
746struct A { a: &'static str }
747fn main() {
748 let a = "haha";
749 A { a }
750}
751"#,
752 );
753
754 check_fix(
755 r#"
756struct A { a: &'static str, b: &'static str }
757fn main() {
758 let a = "haha";
759 let b = "bb";
760 A { a<|>: a, b }
761}
762"#,
763 r#"
764struct A { a: &'static str, b: &'static str }
765fn 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
4use base_db::FileId;
5use ide_db::source_change::SourceFileEdit;
6use syntax::{ast, match_ast, AstNode, SyntaxNode};
7use text_edit::TextEdit;
8
9use crate::{Diagnostic, Fix, Severity};
10
11pub(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
21fn 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
63fn 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)]
106mod 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#"
113struct A { a: &'static str }
114fn main() { A { a: "hello" } }
115"#,
116 );
117 check_no_diagnostics(
118 r#"
119struct A(usize);
120fn main() { A { 0: 0 } }
121"#,
122 );
123
124 check_fix(
125 r#"
126struct A { a: &'static str }
127fn main() {
128 let a = "haha";
129 A { a<|>: a }
130}
131"#,
132 r#"
133struct A { a: &'static str }
134fn main() {
135 let a = "haha";
136 A { a }
137}
138"#,
139 );
140
141 check_fix(
142 r#"
143struct A { a: &'static str, b: &'static str }
144fn main() {
145 let a = "haha";
146 let b = "bb";
147 A { a<|>: a, b }
148}
149"#,
150 r#"
151struct A { a: &'static str, b: &'static str }
152fn 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#"
165struct A { a: &'static str }
166fn f(a: A) { let A { a: hello } = a; }
167"#,
168 );
169 check_no_diagnostics(
170 r#"
171struct A(usize);
172fn f(a: A) { let A { 0: 0 } = a; }
173"#,
174 );
175
176 check_fix(
177 r#"
178struct A { a: &'static str }
179fn f(a: A) {
180 let A { a<|>: a } = a;
181}
182"#,
183 r#"
184struct A { a: &'static str }
185fn f(a: A) {
186 let A { a } = a;
187}
188"#,
189 );
190
191 check_fix(
192 r#"
193struct A { a: &'static str, b: &'static str }
194fn f(a: A) {
195 let A { a<|>: a, b } = a;
196}
197"#,
198 r#"
199struct A { a: &'static str, b: &'static str }
200fn f(a: A) {
201 let A { a, b } = a;
202}
203"#,
204 );
205 }
206}