aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics/missing_fields.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/diagnostics/missing_fields.rs')
-rw-r--r--crates/ide/src/diagnostics/missing_fields.rs256
1 files changed, 256 insertions, 0 deletions
diff --git a/crates/ide/src/diagnostics/missing_fields.rs b/crates/ide/src/diagnostics/missing_fields.rs
new file mode 100644
index 000000000..66575f713
--- /dev/null
+++ b/crates/ide/src/diagnostics/missing_fields.rs
@@ -0,0 +1,256 @@
1use either::Either;
2use hir::{db::AstDatabase, InFile};
3use ide_assists::Assist;
4use ide_db::source_change::SourceChange;
5use stdx::format_to;
6use syntax::{algo, ast::make, AstNode, SyntaxNodePtr};
7use text_edit::TextEdit;
8
9use crate::diagnostics::{fix, Diagnostic, DiagnosticsContext};
10
11// Diagnostic: missing-fields
12//
13// This diagnostic is triggered if record lacks some fields that exist in the corresponding structure.
14//
15// Example:
16//
17// ```rust
18// struct A { a: u8, b: u8 }
19//
20// let a = A { a: 10 };
21// ```
22pub(super) fn missing_fields(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Diagnostic {
23 let mut message = String::from("Missing structure fields:\n");
24 for field in &d.missed_fields {
25 format_to!(message, "- {}\n", field);
26 }
27
28 let ptr = InFile::new(
29 d.file,
30 d.field_list_parent_path
31 .clone()
32 .map(SyntaxNodePtr::from)
33 .unwrap_or_else(|| d.field_list_parent.clone().either(|it| it.into(), |it| it.into())),
34 );
35
36 Diagnostic::new("missing-fields", message, ctx.sema.diagnostics_display_range(ptr).range)
37 .with_fixes(fixes(ctx, d))
38}
39
40fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::MissingFields) -> Option<Vec<Assist>> {
41 // Note that although we could add a diagnostics to
42 // fill the missing tuple field, e.g :
43 // `struct A(usize);`
44 // `let a = A { 0: () }`
45 // but it is uncommon usage and it should not be encouraged.
46 if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
47 return None;
48 }
49
50 let root = ctx.sema.db.parse_or_expand(d.file)?;
51 let field_list_parent = match &d.field_list_parent {
52 Either::Left(record_expr) => record_expr.to_node(&root),
53 // FIXE: patterns should be fixable as well.
54 Either::Right(_) => return None,
55 };
56 let old_field_list = field_list_parent.record_expr_field_list()?;
57 let new_field_list = old_field_list.clone_for_update();
58 for f in d.missed_fields.iter() {
59 let field =
60 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()))
61 .clone_for_update();
62 new_field_list.add_field(field);
63 }
64
65 let edit = {
66 let mut builder = TextEdit::builder();
67 algo::diff(old_field_list.syntax(), new_field_list.syntax()).into_text_edit(&mut builder);
68 builder.finish()
69 };
70 Some(vec![fix(
71 "fill_missing_fields",
72 "Fill struct fields",
73 SourceChange::from_text_edit(d.file.original_file(ctx.sema.db), edit),
74 ctx.sema.original_range(field_list_parent.syntax()).range,
75 )])
76}
77
78#[cfg(test)]
79mod tests {
80 use crate::diagnostics::tests::{check_diagnostics, check_fix, check_no_diagnostics};
81
82 #[test]
83 fn missing_record_pat_field_diagnostic() {
84 check_diagnostics(
85 r#"
86struct S { foo: i32, bar: () }
87fn baz(s: S) {
88 let S { foo: _ } = s;
89 //^ Missing structure fields:
90 //| - bar
91}
92"#,
93 );
94 }
95
96 #[test]
97 fn test_fill_struct_fields_empty() {
98 check_fix(
99 r#"
100struct TestStruct { one: i32, two: i64 }
101
102fn test_fn() {
103 let s = TestStruct {$0};
104}
105"#,
106 r#"
107struct TestStruct { one: i32, two: i64 }
108
109fn test_fn() {
110 let s = TestStruct { one: (), two: () };
111}
112"#,
113 );
114 }
115
116 #[test]
117 fn test_fill_struct_fields_self() {
118 check_fix(
119 r#"
120struct TestStruct { one: i32 }
121
122impl TestStruct {
123 fn test_fn() { let s = Self {$0}; }
124}
125"#,
126 r#"
127struct TestStruct { one: i32 }
128
129impl TestStruct {
130 fn test_fn() { let s = Self { one: () }; }
131}
132"#,
133 );
134 }
135
136 #[test]
137 fn test_fill_struct_fields_enum() {
138 check_fix(
139 r#"
140enum Expr {
141 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
142}
143
144impl Expr {
145 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
146 Expr::Bin {$0 }
147 }
148}
149"#,
150 r#"
151enum Expr {
152 Bin { lhs: Box<Expr>, rhs: Box<Expr> }
153}
154
155impl Expr {
156 fn new_bin(lhs: Box<Expr>, rhs: Box<Expr>) -> Expr {
157 Expr::Bin { lhs: (), rhs: () }
158 }
159}
160"#,
161 );
162 }
163
164 #[test]
165 fn test_fill_struct_fields_partial() {
166 check_fix(
167 r#"
168struct TestStruct { one: i32, two: i64 }
169
170fn test_fn() {
171 let s = TestStruct{ two: 2$0 };
172}
173"#,
174 r"
175struct TestStruct { one: i32, two: i64 }
176
177fn test_fn() {
178 let s = TestStruct{ two: 2, one: () };
179}
180",
181 );
182 }
183
184 #[test]
185 fn test_fill_struct_fields_raw_ident() {
186 check_fix(
187 r#"
188struct TestStruct { r#type: u8 }
189
190fn test_fn() {
191 TestStruct { $0 };
192}
193"#,
194 r"
195struct TestStruct { r#type: u8 }
196
197fn test_fn() {
198 TestStruct { r#type: () };
199}
200",
201 );
202 }
203
204 #[test]
205 fn test_fill_struct_fields_no_diagnostic() {
206 check_no_diagnostics(
207 r#"
208struct TestStruct { one: i32, two: i64 }
209
210fn test_fn() {
211 let one = 1;
212 let s = TestStruct{ one, two: 2 };
213}
214 "#,
215 );
216 }
217
218 #[test]
219 fn test_fill_struct_fields_no_diagnostic_on_spread() {
220 check_no_diagnostics(
221 r#"
222struct TestStruct { one: i32, two: i64 }
223
224fn test_fn() {
225 let one = 1;
226 let s = TestStruct{ ..a };
227}
228"#,
229 );
230 }
231
232 #[test]
233 fn test_fill_struct_fields_blank_line() {
234 check_fix(
235 r#"
236struct S { a: (), b: () }
237
238fn f() {
239 S {
240 $0
241 };
242}
243"#,
244 r#"
245struct S { a: (), b: () }
246
247fn f() {
248 S {
249 a: (),
250 b: (),
251 };
252}
253"#,
254 );
255 }
256}