aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2020-08-12 14:44:13 +0100
committerGitHub <[email protected]>2020-08-12 14:44:13 +0100
commit5b8fdfe23100b88e4fd8e210ccf6b852f5c9bf2a (patch)
treee321c900fe4997ec5ffe20cbb09946502745849c /crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs
parent11de7ac2fb6514484076217acb8d93eb36468681 (diff)
parentdb12ccee96bf37367b39ad99638d06da7123c088 (diff)
Merge #5553
5553: Add fix ranges for diagnostics r=matklad a=SomeoneToIgnore A follow-up of https://rust-lang.zulipchat.com/#narrow/stream/185405-t-compiler.2Fwg-rls-2.2E0/topic/Less.20red.20in.20the.20code Now diagnostics can apply fixes in a range that's different from the range used to highlight the diagnostics. Previous logic did not consider the fix range, having both ranges equal, which could cause a lot of red noise in the editor. Now, the fix range gets used with the fix, the diagnostics range is used for everything else which allows to improve the error highlighting. before: <img width="191" alt="image" src="https://user-images.githubusercontent.com/2690773/88590727-df9a6a00-d063-11ea-97ed-9809c1c5e6e6.png"> after: <img width="222" alt="image" src="https://user-images.githubusercontent.com/2690773/88590734-e1fcc400-d063-11ea-9b7c-25701cbd5352.png"> `MissingFields` and `MissingPatFields` diagnostics now have the fix range as `ast::RecordFieldList` of the expression with an error (as it was before this PR), and the diagnostics range as a `ast::Path` of the expression, if it's present (do you have any example of `ast::Expr::RecordLit` that has no path btw?). The rest of the diagnostics have both ranges equal, same as it was before this PR. Co-authored-by: Kirill Bulatov <[email protected]>
Diffstat (limited to 'crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs')
-rw-r--r--crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs171
1 files changed, 171 insertions, 0 deletions
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs
new file mode 100644
index 000000000..f7c73773f
--- /dev/null
+++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs
@@ -0,0 +1,171 @@
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 crate::Fix;
4use ast::{edit::IndentLevel, make};
5use hir::{
6 db::AstDatabase,
7 diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule},
8 HasSource, HirDisplay, Semantics, VariantDef,
9};
10use ra_db::FileId;
11use ra_ide_db::{
12 source_change::{FileSystemEdit, SourceFileEdit},
13 RootDatabase,
14};
15use ra_syntax::{algo, ast, AstNode};
16use ra_text_edit::{TextEdit, TextEditBuilder};
17
18/// A [Diagnostic] that potentially has a fix available.
19///
20/// [Diagnostic]: hir::diagnostics::Diagnostic
21pub trait DiagnosticWithFix: Diagnostic {
22 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
23}
24
25impl DiagnosticWithFix for UnresolvedModule {
26 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
27 let root = sema.db.parse_or_expand(self.file)?;
28 let unresolved_module = self.decl.to_node(&root);
29 Some(Fix::new(
30 "Create module",
31 FileSystemEdit::CreateFile {
32 anchor: self.file.original_file(sema.db),
33 dst: self.candidate.clone(),
34 }
35 .into(),
36 unresolved_module.syntax().text_range(),
37 ))
38 }
39}
40
41impl DiagnosticWithFix for NoSuchField {
42 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
43 let root = sema.db.parse_or_expand(self.file)?;
44 missing_record_expr_field_fix(
45 &sema,
46 self.file.original_file(sema.db),
47 &self.field.to_node(&root),
48 )
49 }
50}
51
52impl DiagnosticWithFix for MissingFields {
53 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
54 // Note that although we could add a diagnostics to
55 // fill the missing tuple field, e.g :
56 // `struct A(usize);`
57 // `let a = A { 0: () }`
58 // but it is uncommon usage and it should not be encouraged.
59 if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) {
60 return None;
61 }
62
63 let root = sema.db.parse_or_expand(self.file)?;
64 let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?;
65 let mut new_field_list = old_field_list.clone();
66 for f in self.missed_fields.iter() {
67 let field =
68 make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit()));
69 new_field_list = new_field_list.append_field(&field);
70 }
71
72 let edit = {
73 let mut builder = TextEditBuilder::default();
74 algo::diff(&old_field_list.syntax(), &new_field_list.syntax())
75 .into_text_edit(&mut builder);
76 builder.finish()
77 };
78 Some(Fix::new(
79 "Fill struct fields",
80 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(),
81 sema.original_range(&old_field_list.syntax()).range,
82 ))
83 }
84}
85
86impl DiagnosticWithFix for MissingOkInTailExpr {
87 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> {
88 let root = sema.db.parse_or_expand(self.file)?;
89 let tail_expr = self.expr.to_node(&root);
90 let tail_expr_range = tail_expr.syntax().text_range();
91 let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax()));
92 let source_change =
93 SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into();
94 Some(Fix::new("Wrap with ok", source_change, tail_expr_range))
95 }
96}
97
98fn missing_record_expr_field_fix(
99 sema: &Semantics<RootDatabase>,
100 usage_file_id: FileId,
101 record_expr_field: &ast::RecordExprField,
102) -> Option<Fix> {
103 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
104 let def_id = sema.resolve_variant(record_lit)?;
105 let module;
106 let def_file_id;
107 let record_fields = match VariantDef::from(def_id) {
108 VariantDef::Struct(s) => {
109 module = s.module(sema.db);
110 let source = s.source(sema.db);
111 def_file_id = source.file_id;
112 let fields = source.value.field_list()?;
113 record_field_list(fields)?
114 }
115 VariantDef::Union(u) => {
116 module = u.module(sema.db);
117 let source = u.source(sema.db);
118 def_file_id = source.file_id;
119 source.value.record_field_list()?
120 }
121 VariantDef::EnumVariant(e) => {
122 module = e.module(sema.db);
123 let source = e.source(sema.db);
124 def_file_id = source.file_id;
125 let fields = source.value.field_list()?;
126 record_field_list(fields)?
127 }
128 };
129 let def_file_id = def_file_id.original_file(sema.db);
130
131 let new_field_type = sema.type_of_expr(&record_expr_field.expr()?)?;
132 if new_field_type.is_unknown() {
133 return None;
134 }
135 let new_field = make::record_field(
136 record_expr_field.field_name()?,
137 make::ty(&new_field_type.display_source_code(sema.db, module.into()).ok()?),
138 );
139
140 let last_field = record_fields.fields().last()?;
141 let last_field_syntax = last_field.syntax();
142 let indent = IndentLevel::from_node(last_field_syntax);
143
144 let mut new_field = new_field.to_string();
145 if usage_file_id != def_file_id {
146 new_field = format!("pub(crate) {}", new_field);
147 }
148 new_field = format!("\n{}{}", indent, new_field);
149
150 let needs_comma = !last_field_syntax.to_string().ends_with(',');
151 if needs_comma {
152 new_field = format!(",{}", new_field);
153 }
154
155 let source_change = SourceFileEdit {
156 file_id: def_file_id,
157 edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field),
158 };
159 return Some(Fix::new(
160 "Create field",
161 source_change.into(),
162 record_expr_field.syntax().text_range(),
163 ));
164
165 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
166 match field_def_list {
167 ast::FieldList::RecordFieldList(it) => Some(it),
168 ast::FieldList::TupleFieldList(_) => None,
169 }
170 }
171}