aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics/fixes.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/diagnostics/fixes.rs')
-rw-r--r--crates/ide/src/diagnostics/fixes.rs175
1 files changed, 175 insertions, 0 deletions
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
new file mode 100644
index 000000000..68ae1c239
--- /dev/null
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -0,0 +1,175 @@
1//! Provides a way to attach fixes to the diagnostics.
2//! The same module also has all curret custom fixes for the diagnostics implemented.
3use base_db::FileId;
4use hir::{
5 db::AstDatabase,
6 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
7 HasSource, HirDisplay, Semantics, VariantDef,
8};
9use ide_db::{
10 source_change::{FileSystemEdit, SourceFileEdit},
11 RootDatabase,
12};
13use syntax::{
14 algo,
15 ast::{self, edit::IndentLevel, make},
16 AstNode,
17};
18use text_edit::TextEdit;
19
20use crate::diagnostics::Fix;
21
22/// A [Diagnostic] that potentially has a fix available.
23///
24/// [Diagnostic]: hir::diagnostics::Diagnostic
25pub trait DiagnosticWithFix: Diagnostic {
26 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
27}
28
29impl DiagnosticWithFix for UnresolvedModule {
30 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
31 let root = sema.db.parse_or_expand(self.file)?;
32 let unresolved_module = self.decl.to_node(&root);
33 Some(Fix::new(
34 "Create module",
35 FileSystemEdit::CreateFile {
36 anchor: self.file.original_file(sema.db),
37 dst: self.candidate.clone(),
38 }
39 .into(),
40 unresolved_module.syntax().text_range(),
41 ))
42 }
43}
44
45impl DiagnosticWithFix for NoSuchField {
46 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
47 let root = sema.db.parse_or_expand(self.file)?;
48 missing_record_expr_field_fix(
49 &sema,
50 self.file.original_file(sema.db),
51 &self.field.to_node(&root),
52 )
53 }
54}
55
56impl DiagnosticWithFix for MissingFields {
57 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
58 // Note that although we could add a diagnostics to
59 // fill the missing tuple field, e.g :
60 // `struct A(usize);`
61 // `let a = A { 0: () }`
62 // but it is uncommon usage and it should not be encouraged.
63 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
64 return None;
65 }
66
67 let root = sema.db.parse_or_expand(self.file)?;
68 let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?;
69 let mut new_field_list = old_field_list.clone();
70 for f in self.missed_fields.iter() {
71 let field =
72 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
73 new_field_list = new_field_list.append_field(&field);
74 }
75
76 let edit = {
77 let mut builder = TextEdit::builder();
78 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
79 .into_text_edit(&mut builder);
80 builder.finish()
81 };
82 Some(Fix::new(
83 "Fill struct fields",
84 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(),
85 sema.original_range(&old_field_list.syntax()).range,
86 ))
87 }
88}
89
90impl DiagnosticWithFix for MissingOkInTailExpr {
91 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
92 let root = sema.db.parse_or_expand(self.file)?;
93 let tail_expr = self.expr.to_node(&root);
94 let tail_expr_range = tail_expr.syntax().text_range();
95 let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
96 let source_change =
97 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
98 Some(Fix::new("Wrap with ok", source_change, tail_expr_range))
99 }
100}
101
102fn missing_record_expr_field_fix(
103 sema: &Semantics<RootDatabase>,
104 usage_file_id: FileId,
105 record_expr_field: &ast::RecordExprField,
106) -> Option<Fix> {
107 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
108 let def_id = sema.resolve_variant(record_lit)?;
109 let module;
110 let def_file_id;
111 let record_fields = match VariantDef::from(def_id) {
112 VariantDef::Struct(s) => {
113 module = s.module(sema.db);
114 let source = s.source(sema.db);
115 def_file_id = source.file_id;
116 let fields = source.value.field_list()?;
117 record_field_list(fields)?
118 }
119 VariantDef::Union(u) => {
120 module = u.module(sema.db);
121 let source = u.source(sema.db);
122 def_file_id = source.file_id;
123 source.value.record_field_list()?
124 }
125 VariantDef::EnumVariant(e) => {
126 module = e.module(sema.db);
127 let source = e.source(sema.db);
128 def_file_id = source.file_id;
129 let fields = source.value.field_list()?;
130 record_field_list(fields)?
131 }
132 };
133 let def_file_id = def_file_id.original_file(sema.db);
134
135 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
136 if new_field_type.is_unknown() {
137 return None;
138 }
139 let new_field = make::record_field(
140 record_expr_field.field_name()?,
141 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
142 );
143
144 let last_field = record_fields.fields().last()?;
145 let last_field_syntax = last_field.syntax();
146 let indent = IndentLevel::from_node(last_field_syntax);
147
148 let mut new_field = new_field.to_string();
149 if usage_file_id != def_file_id {
150 new_field = format!("pub(crate) {}", new_field);
151 }
152 new_field = format!("\n{}{}", indent, new_field);
153
154 let needs_comma = !last_field_syntax.to_string().ends_with(',');
155 if needs_comma {
156 new_field = format!(",{}", new_field);
157 }
158
159 let source_change = SourceFileEdit {
160 file_id: def_file_id,
161 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
162 };
163 return Some(Fix::new(
164 "Create field",
165 source_change.into(),
166 record_expr_field.syntax().text_range(),
167 ));
168
169 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
170 match field_def_list {
171 ast::FieldList::RecordFieldList(it) => Some(it),
172 ast::FieldList::TupleFieldList(_) => None,
173 }
174 }
175}