aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics
diff options
context:
space:
mode:
authorAleksey Kladov <[email protected]>2021-06-13 17:45:16 +0100
committerAleksey Kladov <[email protected]>2021-06-13 17:45:16 +0100
commit7166e8549bad95b05f66acd508d07a6cd97d3dc0 (patch)
treecf85d27f8eda6afdf4f9a92bcf646a7e1ce77397 /crates/ide/src/diagnostics
parentd3621eeb02652038a8185f60d78fb4791a732dc6 (diff)
internal: refactor NoSuchField diagnostic
Diffstat (limited to 'crates/ide/src/diagnostics')
-rw-r--r--crates/ide/src/diagnostics/fixes/create_field.rs155
-rw-r--r--crates/ide/src/diagnostics/no_such_field.rs286
2 files changed, 286 insertions, 155 deletions
diff --git a/crates/ide/src/diagnostics/fixes/create_field.rs b/crates/ide/src/diagnostics/fixes/create_field.rs
index f6e45967a..8b1378917 100644
--- a/crates/ide/src/diagnostics/fixes/create_field.rs
+++ b/crates/ide/src/diagnostics/fixes/create_field.rs
@@ -1,156 +1 @@
1use hir::{db::AstDatabase, diagnostics::NoSuchField, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, DiagnosticWithFixes},
11 Assist, AssistResolveStrategy,
12};
13impl DiagnosticWithFixes for NoSuchField {
14 fn fixes(
15 &self,
16 sema: &Semantics<RootDatabase>,
17 _resolve: &AssistResolveStrategy,
18 ) -> Option<Vec<Assist>> {
19 let root = sema.db.parse_or_expand(self.file)?;
20 missing_record_expr_field_fixes(
21 sema,
22 self.file.original_file(sema.db),
23 &self.field.to_node(&root),
24 )
25 }
26}
27
28fn missing_record_expr_field_fixes(
29 sema: &Semantics<RootDatabase>,
30 usage_file_id: FileId,
31 record_expr_field: &ast::RecordExprField,
32) -> Option<Vec<Assist>> {
33 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
34 let def_id = sema.resolve_variant(record_lit)?;
35 let module;
36 let def_file_id;
37 let record_fields = match def_id {
38 hir::VariantDef::Struct(s) => {
39 module = s.module(sema.db);
40 let source = s.source(sema.db)?;
41 def_file_id = source.file_id;
42 let fields = source.value.field_list()?;
43 record_field_list(fields)?
44 }
45 hir::VariantDef::Union(u) => {
46 module = u.module(sema.db);
47 let source = u.source(sema.db)?;
48 def_file_id = source.file_id;
49 source.value.record_field_list()?
50 }
51 hir::VariantDef::Variant(e) => {
52 module = e.module(sema.db);
53 let source = e.source(sema.db)?;
54 def_file_id = source.file_id;
55 let fields = source.value.field_list()?;
56 record_field_list(fields)?
57 }
58 };
59 let def_file_id = def_file_id.original_file(sema.db);
60
61 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
62 if new_field_type.is_unknown() {
63 return None;
64 }
65 let new_field = make::record_field(
66 None,
67 make::name(&record_expr_field.field_name()?.text()),
68 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
69 );
70
71 let last_field = record_fields.fields().last()?;
72 let last_field_syntax = last_field.syntax();
73 let indent = IndentLevel::from_node(last_field_syntax);
74
75 let mut new_field = new_field.to_string();
76 if usage_file_id != def_file_id {
77 new_field = format!("pub(crate) {}", new_field);
78 }
79 new_field = format!("\n{}{}", indent, new_field);
80
81 let needs_comma = !last_field_syntax.to_string().ends_with(',');
82 if needs_comma {
83 new_field = format!(",{}", new_field);
84 }
85
86 let source_change = SourceChange::from_text_edit(
87 def_file_id,
88 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
89 );
90
91 return Some(vec![fix(
92 "create_field",
93 "Create field",
94 source_change,
95 record_expr_field.syntax().text_range(),
96 )]);
97
98 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
99 match field_def_list {
100 ast::FieldList::RecordFieldList(it) => Some(it),
101 ast::FieldList::TupleFieldList(_) => None,
102 }
103 }
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::diagnostics::tests::check_fix;
109
110 #[test]
111 fn test_add_field_from_usage() {
112 check_fix(
113 r"
114fn main() {
115 Foo { bar: 3, baz$0: false};
116}
117struct Foo {
118 bar: i32
119}
120",
121 r"
122fn main() {
123 Foo { bar: 3, baz: false};
124}
125struct Foo {
126 bar: i32,
127 baz: bool
128}
129",
130 )
131 }
132
133 #[test]
134 fn test_add_field_in_other_file_from_usage() {
135 check_fix(
136 r#"
137//- /main.rs
138mod foo;
139
140fn main() {
141 foo::Foo { bar: 3, $0baz: false};
142}
143//- /foo.rs
144struct Foo {
145 bar: i32
146}
147"#,
148 r#"
149struct Foo {
150 bar: i32,
151 pub(crate) baz: bool
152}
153"#,
154 )
155 }
156}
diff --git a/crates/ide/src/diagnostics/no_such_field.rs b/crates/ide/src/diagnostics/no_such_field.rs
new file mode 100644
index 000000000..61962de28
--- /dev/null
+++ b/crates/ide/src/diagnostics/no_such_field.rs
@@ -0,0 +1,286 @@
1use hir::{db::AstDatabase, HasSource, HirDisplay, Semantics};
2use ide_db::{base_db::FileId, source_change::SourceChange, RootDatabase};
3use syntax::{
4 ast::{self, edit::IndentLevel, make},
5 AstNode,
6};
7use text_edit::TextEdit;
8
9use crate::{
10 diagnostics::{fix, Diagnostic, DiagnosticsContext},
11 Assist,
12};
13
14// Diagnostic: no-such-field
15//
16// This diagnostic is triggered if created structure does not have field provided in record.
17pub(super) fn no_such_field(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Diagnostic {
18 Diagnostic::new(
19 "no-such-field",
20 "no such field".to_string(),
21 ctx.sema.diagnostics_display_range(d.field.clone().map(|it| it.into())).range,
22 )
23 .with_fixes(fixes(ctx, d))
24}
25
26fn fixes(ctx: &DiagnosticsContext<'_>, d: &hir::NoSuchField) -> Option<Vec<Assist>> {
27 let root = ctx.sema.db.parse_or_expand(d.field.file_id)?;
28 missing_record_expr_field_fixes(
29 &ctx.sema,
30 d.field.file_id.original_file(ctx.sema.db),
31 &d.field.value.to_node(&root),
32 )
33}
34
35fn missing_record_expr_field_fixes(
36 sema: &Semantics<RootDatabase>,
37 usage_file_id: FileId,
38 record_expr_field: &ast::RecordExprField,
39) -> Option<Vec<Assist>> {
40 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
41 let def_id = sema.resolve_variant(record_lit)?;
42 let module;
43 let def_file_id;
44 let record_fields = match def_id {
45 hir::VariantDef::Struct(s) => {
46 module = s.module(sema.db);
47 let source = s.source(sema.db)?;
48 def_file_id = source.file_id;
49 let fields = source.value.field_list()?;
50 record_field_list(fields)?
51 }
52 hir::VariantDef::Union(u) => {
53 module = u.module(sema.db);
54 let source = u.source(sema.db)?;
55 def_file_id = source.file_id;
56 source.value.record_field_list()?
57 }
58 hir::VariantDef::Variant(e) => {
59 module = e.module(sema.db);
60 let source = e.source(sema.db)?;
61 def_file_id = source.file_id;
62 let fields = source.value.field_list()?;
63 record_field_list(fields)?
64 }
65 };
66 let def_file_id = def_file_id.original_file(sema.db);
67
68 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
69 if new_field_type.is_unknown() {
70 return None;
71 }
72 let new_field = make::record_field(
73 None,
74 make::name(&record_expr_field.field_name()?.text()),
75 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
76 );
77
78 let last_field = record_fields.fields().last()?;
79 let last_field_syntax = last_field.syntax();
80 let indent = IndentLevel::from_node(last_field_syntax);
81
82 let mut new_field = new_field.to_string();
83 if usage_file_id != def_file_id {
84 new_field = format!("pub(crate) {}", new_field);
85 }
86 new_field = format!("\n{}{}", indent, new_field);
87
88 let needs_comma = !last_field_syntax.to_string().ends_with(',');
89 if needs_comma {
90 new_field = format!(",{}", new_field);
91 }
92
93 let source_change = SourceChange::from_text_edit(
94 def_file_id,
95 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
96 );
97
98 return Some(vec![fix(
99 "create_field",
100 "Create field",
101 source_change,
102 record_expr_field.syntax().text_range(),
103 )]);
104
105 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
106 match field_def_list {
107 ast::FieldList::RecordFieldList(it) => Some(it),
108 ast::FieldList::TupleFieldList(_) => None,
109 }
110 }
111}
112
113#[cfg(test)]
114mod tests {
115 use crate::diagnostics::tests::{check_diagnostics, check_fix};
116
117 #[test]
118 fn no_such_field_diagnostics() {
119 check_diagnostics(
120 r#"
121struct S { foo: i32, bar: () }
122impl S {
123 fn new() -> S {
124 S {
125 //^ Missing structure fields:
126 //| - bar
127 foo: 92,
128 baz: 62,
129 //^^^^^^^ no such field
130 }
131 }
132}
133"#,
134 );
135 }
136 #[test]
137 fn no_such_field_with_feature_flag_diagnostics() {
138 check_diagnostics(
139 r#"
140//- /lib.rs crate:foo cfg:feature=foo
141struct MyStruct {
142 my_val: usize,
143 #[cfg(feature = "foo")]
144 bar: bool,
145}
146
147impl MyStruct {
148 #[cfg(feature = "foo")]
149 pub(crate) fn new(my_val: usize, bar: bool) -> Self {
150 Self { my_val, bar }
151 }
152 #[cfg(not(feature = "foo"))]
153 pub(crate) fn new(my_val: usize, _bar: bool) -> Self {
154 Self { my_val }
155 }
156}
157"#,
158 );
159 }
160
161 #[test]
162 fn no_such_field_enum_with_feature_flag_diagnostics() {
163 check_diagnostics(
164 r#"
165//- /lib.rs crate:foo cfg:feature=foo
166enum Foo {
167 #[cfg(not(feature = "foo"))]
168 Buz,
169 #[cfg(feature = "foo")]
170 Bar,
171 Baz
172}
173
174fn test_fn(f: Foo) {
175 match f {
176 Foo::Bar => {},
177 Foo::Baz => {},
178 }
179}
180"#,
181 );
182 }
183
184 #[test]
185 fn no_such_field_with_feature_flag_diagnostics_on_struct_lit() {
186 check_diagnostics(
187 r#"
188//- /lib.rs crate:foo cfg:feature=foo
189struct S {
190 #[cfg(feature = "foo")]
191 foo: u32,
192 #[cfg(not(feature = "foo"))]
193 bar: u32,
194}
195
196impl S {
197 #[cfg(feature = "foo")]
198 fn new(foo: u32) -> Self {
199 Self { foo }
200 }
201 #[cfg(not(feature = "foo"))]
202 fn new(bar: u32) -> Self {
203 Self { bar }
204 }
205 fn new2(bar: u32) -> Self {
206 #[cfg(feature = "foo")]
207 { Self { foo: bar } }
208 #[cfg(not(feature = "foo"))]
209 { Self { bar } }
210 }
211 fn new2(val: u32) -> Self {
212 Self {
213 #[cfg(feature = "foo")]
214 foo: val,
215 #[cfg(not(feature = "foo"))]
216 bar: val,
217 }
218 }
219}
220"#,
221 );
222 }
223
224 #[test]
225 fn no_such_field_with_type_macro() {
226 check_diagnostics(
227 r#"
228macro_rules! Type { () => { u32 }; }
229struct Foo { bar: Type![] }
230
231impl Foo {
232 fn new() -> Self {
233 Foo { bar: 0 }
234 }
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn test_add_field_from_usage() {
242 check_fix(
243 r"
244fn main() {
245 Foo { bar: 3, baz$0: false};
246}
247struct Foo {
248 bar: i32
249}
250",
251 r"
252fn main() {
253 Foo { bar: 3, baz: false};
254}
255struct Foo {
256 bar: i32,
257 baz: bool
258}
259",
260 )
261 }
262
263 #[test]
264 fn test_add_field_in_other_file_from_usage() {
265 check_fix(
266 r#"
267//- /main.rs
268mod foo;
269
270fn main() {
271 foo::Foo { bar: 3, $0baz: false};
272}
273//- /foo.rs
274struct Foo {
275 bar: i32
276}
277"#,
278 r#"
279struct Foo {
280 bar: i32,
281 pub(crate) baz: bool
282}
283"#,
284 )
285 }
286}