ide_diagnostics/handlers/
field_shorthand.rs1use ide_db::RootDatabase;
5use ide_db::text_edit::TextEdit;
6use ide_db::{EditionedFileId, FileRange, source_change::SourceChange};
7use syntax::{AstNode, SyntaxNode, ast, match_ast};
8
9use crate::{Diagnostic, DiagnosticCode, fix};
10
11pub(crate) fn field_shorthand(
12 db: &RootDatabase,
13 acc: &mut Vec<Diagnostic>,
14 file_id: EditionedFileId,
15 node: &SyntaxNode,
16) {
17 match_ast! {
18 match node {
19 ast::RecordExpr(it) => check_expr_field_shorthand(db, acc, file_id, it),
20 ast::RecordPat(it) => check_pat_field_shorthand(db, acc, file_id, it),
21 _ => ()
22 }
23 };
24}
25
26fn check_expr_field_shorthand(
27 db: &RootDatabase,
28 acc: &mut Vec<Diagnostic>,
29 file_id: EditionedFileId,
30 record_expr: ast::RecordExpr,
31) {
32 let record_field_list = match record_expr.record_expr_field_list() {
33 Some(it) => it,
34 None => return,
35 };
36 for record_field in record_field_list.fields() {
37 let (name_ref, expr) = match record_field.name_ref().zip(record_field.expr()) {
38 Some(it) => it,
39 None => continue,
40 };
41
42 let field_name = name_ref.syntax().text().to_string();
43 let field_expr = expr.syntax().text().to_string();
44 let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
45 if field_name != field_expr || field_name_is_tup_index {
46 continue;
47 }
48
49 let mut edit_builder = TextEdit::builder();
50 edit_builder.delete(record_field.syntax().text_range());
51 edit_builder.insert(record_field.syntax().text_range().start(), field_name);
52 let edit = edit_builder.finish();
53
54 let field_range = record_field.syntax().text_range();
55 let vfs_file_id = file_id.file_id(db);
56 acc.push(
57 Diagnostic::new(
58 DiagnosticCode::Clippy("redundant_field_names"),
59 "Shorthand struct initialization",
60 FileRange { file_id: vfs_file_id, range: field_range },
61 )
62 .with_fixes(Some(vec![fix(
63 "use_expr_field_shorthand",
64 "Use struct shorthand initialization",
65 SourceChange::from_text_edit(vfs_file_id, edit),
66 field_range,
67 )])),
68 );
69 }
70}
71
72fn check_pat_field_shorthand(
73 db: &RootDatabase,
74 acc: &mut Vec<Diagnostic>,
75 file_id: EditionedFileId,
76 record_pat: ast::RecordPat,
77) {
78 let record_pat_field_list = match record_pat.record_pat_field_list() {
79 Some(it) => it,
80 None => return,
81 };
82 for record_pat_field in record_pat_field_list.fields() {
83 let (name_ref, pat) = match record_pat_field.name_ref().zip(record_pat_field.pat()) {
84 Some(it) => it,
85 None => continue,
86 };
87
88 let field_name = name_ref.syntax().text().to_string();
89 let field_pat = pat.syntax().text().to_string();
90 let field_name_is_tup_index = name_ref.as_tuple_field().is_some();
91 if field_name != field_pat || field_name_is_tup_index {
92 continue;
93 }
94
95 let mut edit_builder = TextEdit::builder();
96 edit_builder.delete(record_pat_field.syntax().text_range());
97 edit_builder.insert(record_pat_field.syntax().text_range().start(), field_name);
98 let edit = edit_builder.finish();
99
100 let field_range = record_pat_field.syntax().text_range();
101 let vfs_file_id = file_id.file_id(db);
102 acc.push(
103 Diagnostic::new(
104 DiagnosticCode::Clippy("redundant_field_names"),
105 "Shorthand struct pattern",
106 FileRange { file_id: vfs_file_id, range: field_range },
107 )
108 .with_fixes(Some(vec![fix(
109 "use_pat_field_shorthand",
110 "Use struct field shorthand",
111 SourceChange::from_text_edit(vfs_file_id, edit),
112 field_range,
113 )])),
114 );
115 }
116}
117
118#[cfg(test)]
119mod tests {
120 use crate::tests::{check_diagnostics, check_fix};
121
122 #[test]
123 fn test_check_expr_field_shorthand() {
124 check_diagnostics(
125 r#"
126struct A { a: &'static str }
127fn main() { A { a: "hello" }; }
128"#,
129 );
130 check_diagnostics(
131 r#"
132struct A(usize);
133fn main() { A { 0: 0 }; }
134"#,
135 );
136
137 check_fix(
138 r#"
139struct A { a: &'static str }
140fn main() {
141 let a = "haha";
142 A { a$0: a };
143}
144"#,
145 r#"
146struct A { a: &'static str }
147fn main() {
148 let a = "haha";
149 A { a };
150}
151"#,
152 );
153
154 check_fix(
155 r#"
156struct A { a: &'static str, b: &'static str }
157fn main() {
158 let a = "haha";
159 let b = "bb";
160 A { a$0: a, b };
161}
162"#,
163 r#"
164struct A { a: &'static str, b: &'static str }
165fn main() {
166 let a = "haha";
167 let b = "bb";
168 A { a, b };
169}
170"#,
171 );
172 }
173
174 #[test]
175 fn test_check_pat_field_shorthand() {
176 check_diagnostics(
177 r#"
178struct A { a: &'static str }
179fn f(a: A) { let A { a: _hello } = a; }
180"#,
181 );
182 check_diagnostics(
183 r#"
184struct A(usize);
185fn f(a: A) { let A { 0: 0 } = a; }
186"#,
187 );
188
189 check_fix(
190 r#"
191struct A { a: &'static str }
192fn f(a: A) {
193 let A { a$0: a } = a;
194 _ = a;
195}
196"#,
197 r#"
198struct A { a: &'static str }
199fn f(a: A) {
200 let A { a } = a;
201 _ = a;
202}
203"#,
204 );
205
206 check_fix(
207 r#"
208struct A { a: &'static str, b: &'static str }
209fn f(a: A) {
210 let A { a$0: a, b } = a;
211 _ = (a, b);
212}
213"#,
214 r#"
215struct A { a: &'static str, b: &'static str }
216fn f(a: A) {
217 let A { a, b } = a;
218 _ = (a, b);
219}
220"#,
221 );
222 }
223}