diff options
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 68 | ||||
-rw-r--r-- | crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | 101 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 12 | ||||
-rw-r--r-- | crates/rust-analyzer/src/handlers.rs | 7 |
4 files changed, 92 insertions, 96 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index 165ff5249..757b76fd4 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -6,10 +6,7 @@ | |||
6 | 6 | ||
7 | use std::cell::RefCell; | 7 | use std::cell::RefCell; |
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{diagnostics::DiagnosticSinkBuilder, Semantics}; |
10 | diagnostics::{Diagnostic as HirDiagnostics, DiagnosticSinkBuilder}, | ||
11 | Semantics, | ||
12 | }; | ||
13 | use itertools::Itertools; | 10 | use itertools::Itertools; |
14 | use ra_db::SourceDatabase; | 11 | use ra_db::SourceDatabase; |
15 | use ra_ide_db::RootDatabase; | 12 | use ra_ide_db::RootDatabase; |
@@ -73,7 +70,7 @@ pub(crate) fn diagnostics( | |||
73 | .build(|d| { | 70 | .build(|d| { |
74 | res.borrow_mut().push(Diagnostic { | 71 | res.borrow_mut().push(Diagnostic { |
75 | message: d.message(), | 72 | message: d.message(), |
76 | range: sema.diagnostics_presentation_range(d).range, | 73 | range: sema.diagnostics_display_range(d).range, |
77 | severity: Severity::Error, | 74 | severity: Severity::Error, |
78 | fix: None, | 75 | fix: None, |
79 | }) | 76 | }) |
@@ -86,12 +83,9 @@ pub(crate) fn diagnostics( | |||
86 | res.into_inner() | 83 | res.into_inner() |
87 | } | 84 | } |
88 | 85 | ||
89 | fn diagnostic_with_fix<D: HirDiagnostics + DiagnosticWithFix>( | 86 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { |
90 | d: &D, | ||
91 | sema: &Semantics<RootDatabase>, | ||
92 | ) -> Diagnostic { | ||
93 | Diagnostic { | 87 | Diagnostic { |
94 | range: sema.diagnostics_presentation_range(d).range, | 88 | range: sema.diagnostics_display_range(d).range, |
95 | message: d.message(), | 89 | message: d.message(), |
96 | severity: Severity::Error, | 90 | severity: Severity::Error, |
97 | fix: d.fix(&sema), | 91 | fix: d.fix(&sema), |
@@ -120,8 +114,9 @@ fn check_unnecessary_braces_in_use_statement( | |||
120 | range: use_range, | 114 | range: use_range, |
121 | message: "Unnecessary braces in use statement".to_string(), | 115 | message: "Unnecessary braces in use statement".to_string(), |
122 | severity: Severity::WeakWarning, | 116 | severity: Severity::WeakWarning, |
123 | fix: Some(( | 117 | fix: Some(Fix::new( |
124 | Fix::new("Remove unnecessary braces", SourceFileEdit { file_id, edit }.into()), | 118 | "Remove unnecessary braces", |
119 | SourceFileEdit { file_id, edit }.into(), | ||
125 | use_range, | 120 | use_range, |
126 | )), | 121 | )), |
127 | }); | 122 | }); |
@@ -165,11 +160,9 @@ fn check_struct_shorthand_initialization( | |||
165 | range: field_range, | 160 | range: field_range, |
166 | message: "Shorthand struct initialization".to_string(), | 161 | message: "Shorthand struct initialization".to_string(), |
167 | severity: Severity::WeakWarning, | 162 | severity: Severity::WeakWarning, |
168 | fix: Some(( | 163 | fix: Some(Fix::new( |
169 | Fix::new( | 164 | "Use struct shorthand initialization", |
170 | "Use struct shorthand initialization", | 165 | SourceFileEdit { file_id, edit }.into(), |
171 | SourceFileEdit { file_id, edit }.into(), | ||
172 | ), | ||
173 | field_range, | 166 | field_range, |
174 | )), | 167 | )), |
175 | }); | 168 | }); |
@@ -197,7 +190,7 @@ mod tests { | |||
197 | 190 | ||
198 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); | 191 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); |
199 | let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap(); | 192 | let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap(); |
200 | let (mut fix, fix_range) = diagnostic.fix.unwrap(); | 193 | let mut fix = diagnostic.fix.unwrap(); |
201 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | 194 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
202 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 195 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
203 | let actual = { | 196 | let actual = { |
@@ -208,9 +201,10 @@ mod tests { | |||
208 | 201 | ||
209 | assert_eq_text!(&after, &actual); | 202 | assert_eq_text!(&after, &actual); |
210 | assert!( | 203 | assert!( |
211 | fix_range.start() <= file_position.offset && fix_range.end() >= file_position.offset, | 204 | fix.fix_trigger_range.start() <= file_position.offset |
205 | && fix.fix_trigger_range.end() >= file_position.offset, | ||
212 | "diagnostic fix range {:?} does not touch cursor position {:?}", | 206 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
213 | fix_range, | 207 | fix.fix_trigger_range, |
214 | file_position.offset | 208 | file_position.offset |
215 | ); | 209 | ); |
216 | } | 210 | } |
@@ -222,7 +216,7 @@ mod tests { | |||
222 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); | 216 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); |
223 | let current_file_id = file_pos.file_id; | 217 | let current_file_id = file_pos.file_id; |
224 | let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap(); | 218 | let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap(); |
225 | let mut fix = diagnostic.fix.unwrap().0; | 219 | let mut fix = diagnostic.fix.unwrap(); |
226 | let edit = fix.source_change.source_file_edits.pop().unwrap(); | 220 | let edit = fix.source_change.source_file_edits.pop().unwrap(); |
227 | let changed_file_id = edit.file_id; | 221 | let changed_file_id = edit.file_id; |
228 | let before = analysis.file_text(changed_file_id).unwrap(); | 222 | let before = analysis.file_text(changed_file_id).unwrap(); |
@@ -513,24 +507,22 @@ fn test_fn() { | |||
513 | range: 0..8, | 507 | range: 0..8, |
514 | severity: Error, | 508 | severity: Error, |
515 | fix: Some( | 509 | fix: Some( |
516 | ( | 510 | Fix { |
517 | Fix { | 511 | label: "Create module", |
518 | label: "Create module", | 512 | source_change: SourceChange { |
519 | source_change: SourceChange { | 513 | source_file_edits: [], |
520 | source_file_edits: [], | 514 | file_system_edits: [ |
521 | file_system_edits: [ | 515 | CreateFile { |
522 | CreateFile { | 516 | anchor: FileId( |
523 | anchor: FileId( | 517 | 1, |
524 | 1, | 518 | ), |
525 | ), | 519 | dst: "foo.rs", |
526 | dst: "foo.rs", | 520 | }, |
527 | }, | 521 | ], |
528 | ], | 522 | is_snippet: false, |
529 | is_snippet: false, | ||
530 | }, | ||
531 | }, | 523 | }, |
532 | 0..8, | 524 | fix_trigger_range: 0..8, |
533 | ), | 525 | }, |
534 | ), | 526 | ), |
535 | }, | 527 | }, |
536 | ] | 528 | ] |
diff --git a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs index 1955e1521..57b54a61e 100644 --- a/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs +++ b/crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs | |||
@@ -1,9 +1,9 @@ | |||
1 | //! Provides a way to derive fixes based on the diagnostic data. | 1 | //! Provides a way to attach fix actions to the |
2 | use crate::Fix; | 2 | use crate::Fix; |
3 | use ast::{edit::IndentLevel, make}; | 3 | use ast::{edit::IndentLevel, make}; |
4 | use hir::{ | 4 | use hir::{ |
5 | db::AstDatabase, | 5 | db::AstDatabase, |
6 | diagnostics::{MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, | 6 | diagnostics::{Diagnostic, MissingFields, MissingOkInTailExpr, NoSuchField, UnresolvedModule}, |
7 | HasSource, HirDisplay, Semantics, VariantDef, | 7 | HasSource, HirDisplay, Semantics, VariantDef, |
8 | }; | 8 | }; |
9 | use ra_db::FileId; | 9 | use ra_db::FileId; |
@@ -11,94 +11,90 @@ use ra_ide_db::{ | |||
11 | source_change::{FileSystemEdit, SourceFileEdit}, | 11 | source_change::{FileSystemEdit, SourceFileEdit}, |
12 | RootDatabase, | 12 | RootDatabase, |
13 | }; | 13 | }; |
14 | use ra_syntax::{algo, ast, AstNode, TextRange}; | 14 | use ra_syntax::{algo, ast, AstNode}; |
15 | use ra_text_edit::{TextEdit, TextEditBuilder}; | 15 | use ra_text_edit::{TextEdit, TextEditBuilder}; |
16 | 16 | ||
17 | /// A trait to implement fot the Diagnostic that has a fix available. | 17 | /// A [Diagnostic] that potentially has a fix available. |
18 | pub trait DiagnosticWithFix { | 18 | /// |
19 | /// Provides a fix with the fix range, if applicable in the current semantics. | 19 | /// [Diagnostic]: hir::diagnostics::Diagnostic |
20 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)>; | 20 | pub trait DiagnosticWithFix: Diagnostic { |
21 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>; | ||
21 | } | 22 | } |
22 | 23 | ||
23 | impl DiagnosticWithFix for UnresolvedModule { | 24 | impl DiagnosticWithFix for UnresolvedModule { |
24 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 25 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
25 | let fix = Fix::new( | 26 | let root = sema.db.parse_or_expand(self.file)?; |
27 | let unresolved_module = self.decl.to_node(&root); | ||
28 | Some(Fix::new( | ||
26 | "Create module", | 29 | "Create module", |
27 | FileSystemEdit::CreateFile { | 30 | FileSystemEdit::CreateFile { |
28 | anchor: self.file.original_file(sema.db), | 31 | anchor: self.file.original_file(sema.db), |
29 | dst: self.candidate.clone(), | 32 | dst: self.candidate.clone(), |
30 | } | 33 | } |
31 | .into(), | 34 | .into(), |
32 | ); | 35 | unresolved_module.syntax().text_range(), |
33 | 36 | )) | |
34 | let root = sema.db.parse_or_expand(self.file)?; | ||
35 | let unresolved_module = self.decl.to_node(&root); | ||
36 | Some((fix, unresolved_module.syntax().text_range())) | ||
37 | } | 37 | } |
38 | } | 38 | } |
39 | 39 | ||
40 | impl DiagnosticWithFix for NoSuchField { | 40 | impl DiagnosticWithFix for NoSuchField { |
41 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 41 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
42 | let root = sema.db.parse_or_expand(self.file)?; | 42 | let root = sema.db.parse_or_expand(self.file)?; |
43 | let record_expr_field = self.field.to_node(&root); | 43 | missing_record_expr_field_fix( |
44 | let fix = | 44 | &sema, |
45 | missing_struct_field_fix(&sema, self.file.original_file(sema.db), &record_expr_field)?; | 45 | self.file.original_file(sema.db), |
46 | Some((fix, record_expr_field.syntax().text_range())) | 46 | &self.field.to_node(&root), |
47 | ) | ||
47 | } | 48 | } |
48 | } | 49 | } |
49 | 50 | ||
50 | impl DiagnosticWithFix for MissingFields { | 51 | impl DiagnosticWithFix for MissingFields { |
51 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 52 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
52 | // Note that although we could add a diagnostics to | 53 | // Note that although we could add a diagnostics to |
53 | // fill the missing tuple field, e.g : | 54 | // fill the missing tuple field, e.g : |
54 | // `struct A(usize);` | 55 | // `struct A(usize);` |
55 | // `let a = A { 0: () }` | 56 | // `let a = A { 0: () }` |
56 | // but it is uncommon usage and it should not be encouraged. | 57 | // but it is uncommon usage and it should not be encouraged. |
57 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | 58 | if self.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { |
58 | None | 59 | return None; |
59 | } else { | 60 | } |
60 | let root = sema.db.parse_or_expand(self.file)?; | ||
61 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; | ||
62 | let mut new_field_list = old_field_list.clone(); | ||
63 | for f in self.missed_fields.iter() { | ||
64 | let field = make::record_expr_field( | ||
65 | make::name_ref(&f.to_string()), | ||
66 | Some(make::expr_unit()), | ||
67 | ); | ||
68 | new_field_list = new_field_list.append_field(&field); | ||
69 | } | ||
70 | 61 | ||
71 | let edit = { | 62 | let root = sema.db.parse_or_expand(self.file)?; |
72 | let mut builder = TextEditBuilder::default(); | 63 | let old_field_list = self.field_list_parent.to_node(&root).record_expr_field_list()?; |
73 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | 64 | let mut new_field_list = old_field_list.clone(); |
74 | .into_text_edit(&mut builder); | 65 | for f in self.missed_fields.iter() { |
75 | builder.finish() | 66 | let field = |
76 | }; | 67 | make::record_expr_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); |
77 | Some(( | 68 | new_field_list = new_field_list.append_field(&field); |
78 | Fix::new( | ||
79 | "Fill struct fields", | ||
80 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
81 | ), | ||
82 | sema.original_range(&old_field_list.syntax()).range, | ||
83 | // old_field_list.syntax().text_range(), | ||
84 | )) | ||
85 | } | 69 | } |
70 | |||
71 | let edit = { | ||
72 | let mut builder = TextEditBuilder::default(); | ||
73 | algo::diff(&old_field_list.syntax(), &new_field_list.syntax()) | ||
74 | .into_text_edit(&mut builder); | ||
75 | builder.finish() | ||
76 | }; | ||
77 | Some(Fix::new( | ||
78 | "Fill struct fields", | ||
79 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(), | ||
80 | sema.original_range(&old_field_list.syntax()).range, | ||
81 | )) | ||
86 | } | 82 | } |
87 | } | 83 | } |
88 | 84 | ||
89 | impl DiagnosticWithFix for MissingOkInTailExpr { | 85 | impl DiagnosticWithFix for MissingOkInTailExpr { |
90 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<(Fix, TextRange)> { | 86 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { |
91 | let root = sema.db.parse_or_expand(self.file)?; | 87 | let root = sema.db.parse_or_expand(self.file)?; |
92 | let tail_expr = self.expr.to_node(&root); | 88 | let tail_expr = self.expr.to_node(&root); |
93 | let tail_expr_range = tail_expr.syntax().text_range(); | 89 | let tail_expr_range = tail_expr.syntax().text_range(); |
94 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); | 90 | let edit = TextEdit::replace(tail_expr_range, format!("Ok({})", tail_expr.syntax())); |
95 | let source_change = | 91 | let source_change = |
96 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); | 92 | SourceFileEdit { file_id: self.file.original_file(sema.db), edit }.into(); |
97 | Some((Fix::new("Wrap with ok", source_change), tail_expr_range)) | 93 | Some(Fix::new("Wrap with ok", source_change, tail_expr_range)) |
98 | } | 94 | } |
99 | } | 95 | } |
100 | 96 | ||
101 | fn missing_struct_field_fix( | 97 | fn missing_record_expr_field_fix( |
102 | sema: &Semantics<RootDatabase>, | 98 | sema: &Semantics<RootDatabase>, |
103 | usage_file_id: FileId, | 99 | usage_file_id: FileId, |
104 | record_expr_field: &ast::RecordExprField, | 100 | record_expr_field: &ast::RecordExprField, |
@@ -159,8 +155,11 @@ fn missing_struct_field_fix( | |||
159 | file_id: def_file_id, | 155 | file_id: def_file_id, |
160 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), | 156 | edit: TextEdit::insert(last_field_syntax.text_range().end(), new_field), |
161 | }; | 157 | }; |
162 | let fix = Fix::new("Create field", source_change.into()); | 158 | return Some(Fix::new( |
163 | return Some(fix); | 159 | "Create field", |
160 | source_change.into(), | ||
161 | record_expr_field.syntax().text_range(), | ||
162 | )); | ||
164 | 163 | ||
165 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { | 164 | fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { |
166 | match field_def_list { | 165 | match field_def_list { |
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index 45a4b2421..89fcb6f17 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -105,20 +105,26 @@ pub struct Diagnostic { | |||
105 | pub message: String, | 105 | pub message: String, |
106 | pub range: TextRange, | 106 | pub range: TextRange, |
107 | pub severity: Severity, | 107 | pub severity: Severity, |
108 | pub fix: Option<(Fix, TextRange)>, | 108 | pub fix: Option<Fix>, |
109 | } | 109 | } |
110 | 110 | ||
111 | #[derive(Debug)] | 111 | #[derive(Debug)] |
112 | pub struct Fix { | 112 | pub struct Fix { |
113 | pub label: String, | 113 | pub label: String, |
114 | pub source_change: SourceChange, | 114 | pub source_change: SourceChange, |
115 | /// Allows to trigger the fix only when the caret is in the range given | ||
116 | pub fix_trigger_range: TextRange, | ||
115 | } | 117 | } |
116 | 118 | ||
117 | impl Fix { | 119 | impl Fix { |
118 | pub fn new(label: impl Into<String>, source_change: SourceChange) -> Self { | 120 | pub fn new( |
121 | label: impl Into<String>, | ||
122 | source_change: SourceChange, | ||
123 | fix_trigger_range: TextRange, | ||
124 | ) -> Self { | ||
119 | let label = label.into(); | 125 | let label = label.into(); |
120 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); | 126 | assert!(label.starts_with(char::is_uppercase) && !label.ends_with('.')); |
121 | Self { label, source_change } | 127 | Self { label, source_change, fix_trigger_range } |
122 | } | 128 | } |
123 | } | 129 | } |
124 | 130 | ||
diff --git a/crates/rust-analyzer/src/handlers.rs b/crates/rust-analyzer/src/handlers.rs index 144c641b2..785dd2a26 100644 --- a/crates/rust-analyzer/src/handlers.rs +++ b/crates/rust-analyzer/src/handlers.rs | |||
@@ -773,12 +773,11 @@ fn handle_fixes( | |||
773 | 773 | ||
774 | let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?; | 774 | let diagnostics = snap.analysis.diagnostics(file_id, snap.config.experimental_diagnostics)?; |
775 | 775 | ||
776 | let fixes_from_diagnostics = diagnostics | 776 | for fix in diagnostics |
777 | .into_iter() | 777 | .into_iter() |
778 | .filter_map(|d| d.fix) | 778 | .filter_map(|d| d.fix) |
779 | .filter(|(_fix, fix_range)| fix_range.intersect(range).is_some()) | 779 | .filter(|fix| fix.fix_trigger_range.intersect(range).is_some()) |
780 | .map(|(fix, _range)| fix); | 780 | { |
781 | for fix in fixes_from_diagnostics { | ||
782 | let title = fix.label; | 781 | let title = fix.label; |
783 | let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; | 782 | let edit = to_proto::snippet_workspace_edit(&snap, fix.source_change)?; |
784 | let action = lsp_ext::CodeAction { | 783 | let action = lsp_ext::CodeAction { |