aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/diagnostics.rs68
-rw-r--r--crates/ra_ide/src/diagnostics/diagnostics_with_fix.rs101
-rw-r--r--crates/ra_ide/src/lib.rs12
-rw-r--r--crates/rust-analyzer/src/handlers.rs7
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
7use std::cell::RefCell; 7use std::cell::RefCell;
8 8
9use hir::{ 9use hir::{diagnostics::DiagnosticSinkBuilder, Semantics};
10 diagnostics::{Diagnostic as HirDiagnostics, DiagnosticSinkBuilder},
11 Semantics,
12};
13use itertools::Itertools; 10use itertools::Itertools;
14use ra_db::SourceDatabase; 11use ra_db::SourceDatabase;
15use ra_ide_db::RootDatabase; 12use 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
89fn diagnostic_with_fix<D: HirDiagnostics + DiagnosticWithFix>( 86fn 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
2use crate::Fix; 2use crate::Fix;
3use ast::{edit::IndentLevel, make}; 3use ast::{edit::IndentLevel, make};
4use hir::{ 4use 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};
9use ra_db::FileId; 9use 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};
14use ra_syntax::{algo, ast, AstNode, TextRange}; 14use ra_syntax::{algo, ast, AstNode};
15use ra_text_edit::{TextEdit, TextEditBuilder}; 15use 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.
18pub 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)>; 20pub trait DiagnosticWithFix: Diagnostic {
21 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>;
21} 22}
22 23
23impl DiagnosticWithFix for UnresolvedModule { 24impl 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
40impl DiagnosticWithFix for NoSuchField { 40impl 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
50impl DiagnosticWithFix for MissingFields { 51impl 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
89impl DiagnosticWithFix for MissingOkInTailExpr { 85impl 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
101fn missing_struct_field_fix( 97fn 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)]
112pub struct Fix { 112pub 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
117impl Fix { 119impl 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 {