ide_diagnostics/handlers/
field_shorthand.rs

1//! Suggests shortening `Foo { field: field }` to `Foo { field }` in both
2//! expressions and patterns.
3
4use 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}