aboutsummaryrefslogtreecommitdiff
path: root/crates/ide
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide')
-rw-r--r--crates/ide/src/diagnostics.rs83
-rw-r--r--crates/ide/src/diagnostics/field_shorthand.rs8
-rw-r--r--crates/ide/src/diagnostics/fixes.rs42
-rw-r--r--crates/ide/src/diagnostics/unlinked_file.rs14
-rw-r--r--crates/ide/src/lib.rs2
5 files changed, 83 insertions, 66 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 0ace80a1e..4f0b4a62e 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -25,7 +25,7 @@ use syntax::{
25use text_edit::TextEdit; 25use text_edit::TextEdit;
26use unlinked_file::UnlinkedFile; 26use unlinked_file::UnlinkedFile;
27 27
28use crate::{FileId, Label, SourceChange}; 28use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange};
29 29
30use self::fixes::DiagnosticWithFix; 30use self::fixes::DiagnosticWithFix;
31 31
@@ -35,7 +35,7 @@ pub struct Diagnostic {
35 pub message: String, 35 pub message: String,
36 pub range: TextRange, 36 pub range: TextRange,
37 pub severity: Severity, 37 pub severity: Severity,
38 pub fix: Option<Fix>, 38 pub fix: Option<Assist>,
39 pub unused: bool, 39 pub unused: bool,
40 pub code: Option<DiagnosticCode>, 40 pub code: Option<DiagnosticCode>,
41} 41}
@@ -56,7 +56,7 @@ impl Diagnostic {
56 } 56 }
57 } 57 }
58 58
59 fn with_fix(self, fix: Option<Fix>) -> Self { 59 fn with_fix(self, fix: Option<Assist>) -> Self {
60 Self { fix, ..self } 60 Self { fix, ..self }
61 } 61 }
62 62
@@ -69,21 +69,6 @@ impl Diagnostic {
69 } 69 }
70} 70}
71 71
72#[derive(Debug)]
73pub struct Fix {
74 pub label: Label,
75 pub source_change: SourceChange,
76 /// Allows to trigger the fix only when the caret is in the range given
77 pub fix_trigger_range: TextRange,
78}
79
80impl Fix {
81 fn new(label: &str, source_change: SourceChange, fix_trigger_range: TextRange) -> Self {
82 let label = Label::new(label);
83 Self { label, source_change, fix_trigger_range }
84 }
85}
86
87#[derive(Debug, Copy, Clone)] 72#[derive(Debug, Copy, Clone)]
88pub enum Severity { 73pub enum Severity {
89 Error, 74 Error,
@@ -261,7 +246,8 @@ fn check_unnecessary_braces_in_use_statement(
261 246
262 acc.push( 247 acc.push(
263 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) 248 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
264 .with_fix(Some(Fix::new( 249 .with_fix(Some(fix(
250 "remove_braces",
265 "Remove unnecessary braces", 251 "Remove unnecessary braces",
266 SourceChange::from_text_edit(file_id, edit), 252 SourceChange::from_text_edit(file_id, edit),
267 use_range, 253 use_range,
@@ -284,6 +270,17 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
284 None 270 None
285} 271}
286 272
273fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist {
274 assert!(!id.contains(' '));
275 Assist {
276 id: AssistId(id, AssistKind::QuickFix),
277 label: Label::new(label),
278 group: None,
279 target,
280 source_change: Some(source_change),
281 }
282}
283
287#[cfg(test)] 284#[cfg(test)]
288mod tests { 285mod tests {
289 use expect_test::{expect, Expect}; 286 use expect_test::{expect, Expect};
@@ -308,10 +305,11 @@ mod tests {
308 .unwrap(); 305 .unwrap();
309 let fix = diagnostic.fix.unwrap(); 306 let fix = diagnostic.fix.unwrap();
310 let actual = { 307 let actual = {
311 let file_id = *fix.source_change.source_file_edits.keys().next().unwrap(); 308 let source_change = fix.source_change.unwrap();
309 let file_id = *source_change.source_file_edits.keys().next().unwrap();
312 let mut actual = analysis.file_text(file_id).unwrap().to_string(); 310 let mut actual = analysis.file_text(file_id).unwrap().to_string();
313 311
314 for edit in fix.source_change.source_file_edits.values() { 312 for edit in source_change.source_file_edits.values() {
315 edit.apply(&mut actual); 313 edit.apply(&mut actual);
316 } 314 }
317 actual 315 actual
@@ -319,9 +317,9 @@ mod tests {
319 317
320 assert_eq_text!(&after, &actual); 318 assert_eq_text!(&after, &actual);
321 assert!( 319 assert!(
322 fix.fix_trigger_range.contains_inclusive(file_position.offset), 320 fix.target.contains_inclusive(file_position.offset),
323 "diagnostic fix range {:?} does not touch cursor position {:?}", 321 "diagnostic fix range {:?} does not touch cursor position {:?}",
324 fix.fix_trigger_range, 322 fix.target,
325 file_position.offset 323 file_position.offset
326 ); 324 );
327 } 325 }
@@ -665,24 +663,31 @@ fn test_fn() {
665 range: 0..8, 663 range: 0..8,
666 severity: Error, 664 severity: Error,
667 fix: Some( 665 fix: Some(
668 Fix { 666 Assist {
667 id: AssistId(
668 "create_module",
669 QuickFix,
670 ),
669 label: "Create module", 671 label: "Create module",
670 source_change: SourceChange { 672 group: None,
671 source_file_edits: {}, 673 target: 0..8,
672 file_system_edits: [ 674 source_change: Some(
673 CreateFile { 675 SourceChange {
674 dst: AnchoredPathBuf { 676 source_file_edits: {},
675 anchor: FileId( 677 file_system_edits: [
676 0, 678 CreateFile {
677 ), 679 dst: AnchoredPathBuf {
678 path: "foo.rs", 680 anchor: FileId(
681 0,
682 ),
683 path: "foo.rs",
684 },
685 initial_contents: "",
679 }, 686 },
680 initial_contents: "", 687 ],
681 }, 688 is_snippet: false,
682 ], 689 },
683 is_snippet: false, 690 ),
684 },
685 fix_trigger_range: 0..8,
686 }, 691 },
687 ), 692 ),
688 unused: false, 693 unused: false,
diff --git a/crates/ide/src/diagnostics/field_shorthand.rs b/crates/ide/src/diagnostics/field_shorthand.rs
index 5c89e2170..2b1787f9b 100644
--- a/crates/ide/src/diagnostics/field_shorthand.rs
+++ b/crates/ide/src/diagnostics/field_shorthand.rs
@@ -5,7 +5,7 @@ use ide_db::{base_db::FileId, source_change::SourceChange};
5use syntax::{ast, match_ast, AstNode, SyntaxNode}; 5use syntax::{ast, match_ast, AstNode, SyntaxNode};
6use text_edit::TextEdit; 6use text_edit::TextEdit;
7 7
8use crate::{Diagnostic, Fix}; 8use crate::{diagnostics::fix, Diagnostic};
9 9
10pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) { 10pub(super) fn check(acc: &mut Vec<Diagnostic>, file_id: FileId, node: &SyntaxNode) {
11 match_ast! { 11 match_ast! {
@@ -47,7 +47,8 @@ fn check_expr_field_shorthand(
47 let field_range = record_field.syntax().text_range(); 47 let field_range = record_field.syntax().text_range();
48 acc.push( 48 acc.push(
49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix( 49 Diagnostic::hint(field_range, "Shorthand struct initialization".to_string()).with_fix(
50 Some(Fix::new( 50 Some(fix(
51 "use_expr_field_shorthand",
51 "Use struct shorthand initialization", 52 "Use struct shorthand initialization",
52 SourceChange::from_text_edit(file_id, edit), 53 SourceChange::from_text_edit(file_id, edit),
53 field_range, 54 field_range,
@@ -86,7 +87,8 @@ fn check_pat_field_shorthand(
86 87
87 let field_range = record_pat_field.syntax().text_range(); 88 let field_range = record_pat_field.syntax().text_range();
88 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix( 89 acc.push(Diagnostic::hint(field_range, "Shorthand struct pattern".to_string()).with_fix(
89 Some(Fix::new( 90 Some(fix(
91 "use_pat_field_shorthand",
90 "Use struct field shorthand", 92 "Use struct field shorthand",
91 SourceChange::from_text_edit(file_id, edit), 93 SourceChange::from_text_edit(file_id, edit),
92 field_range, 94 field_range,
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs
index 5fb3e2d91..69cf5288c 100644
--- a/crates/ide/src/diagnostics/fixes.rs
+++ b/crates/ide/src/diagnostics/fixes.rs
@@ -20,20 +20,21 @@ use syntax::{
20}; 20};
21use text_edit::TextEdit; 21use text_edit::TextEdit;
22 22
23use crate::{diagnostics::Fix, references::rename::rename_with_semantics, FilePosition}; 23use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, FilePosition};
24 24
25/// A [Diagnostic] that potentially has a fix available. 25/// A [Diagnostic] that potentially has a fix available.
26/// 26///
27/// [Diagnostic]: hir::diagnostics::Diagnostic 27/// [Diagnostic]: hir::diagnostics::Diagnostic
28pub(crate) trait DiagnosticWithFix: Diagnostic { 28pub(crate) trait DiagnosticWithFix: Diagnostic {
29 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix>; 29 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist>;
30} 30}
31 31
32impl DiagnosticWithFix for UnresolvedModule { 32impl DiagnosticWithFix for UnresolvedModule {
33 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 33 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
34 let root = sema.db.parse_or_expand(self.file)?; 34 let root = sema.db.parse_or_expand(self.file)?;
35 let unresolved_module = self.decl.to_node(&root); 35 let unresolved_module = self.decl.to_node(&root);
36 Some(Fix::new( 36 Some(fix(
37 "create_module",
37 "Create module", 38 "Create module",
38 FileSystemEdit::CreateFile { 39 FileSystemEdit::CreateFile {
39 dst: AnchoredPathBuf { 40 dst: AnchoredPathBuf {
@@ -49,7 +50,7 @@ impl DiagnosticWithFix for UnresolvedModule {
49} 50}
50 51
51impl DiagnosticWithFix for NoSuchField { 52impl DiagnosticWithFix for NoSuchField {
52 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 53 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
53 let root = sema.db.parse_or_expand(self.file)?; 54 let root = sema.db.parse_or_expand(self.file)?;
54 missing_record_expr_field_fix( 55 missing_record_expr_field_fix(
55 &sema, 56 &sema,
@@ -60,7 +61,7 @@ impl DiagnosticWithFix for NoSuchField {
60} 61}
61 62
62impl DiagnosticWithFix for MissingFields { 63impl DiagnosticWithFix for MissingFields {
63 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 64 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
64 // Note that although we could add a diagnostics to 65 // Note that although we could add a diagnostics to
65 // fill the missing tuple field, e.g : 66 // fill the missing tuple field, e.g :
66 // `struct A(usize);` 67 // `struct A(usize);`
@@ -86,7 +87,8 @@ impl DiagnosticWithFix for MissingFields {
86 .into_text_edit(&mut builder); 87 .into_text_edit(&mut builder);
87 builder.finish() 88 builder.finish()
88 }; 89 };
89 Some(Fix::new( 90 Some(fix(
91 "fill_missing_fields",
90 "Fill struct fields", 92 "Fill struct fields",
91 SourceChange::from_text_edit(self.file.original_file(sema.db), edit), 93 SourceChange::from_text_edit(self.file.original_file(sema.db), edit),
92 sema.original_range(&field_list_parent.syntax()).range, 94 sema.original_range(&field_list_parent.syntax()).range,
@@ -95,7 +97,7 @@ impl DiagnosticWithFix for MissingFields {
95} 97}
96 98
97impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { 99impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
98 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 100 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
99 let root = sema.db.parse_or_expand(self.file)?; 101 let root = sema.db.parse_or_expand(self.file)?;
100 let tail_expr = self.expr.to_node(&root); 102 let tail_expr = self.expr.to_node(&root);
101 let tail_expr_range = tail_expr.syntax().text_range(); 103 let tail_expr_range = tail_expr.syntax().text_range();
@@ -103,12 +105,12 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr {
103 let edit = TextEdit::replace(tail_expr_range, replacement); 105 let edit = TextEdit::replace(tail_expr_range, replacement);
104 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 106 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
105 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" }; 107 let name = if self.required == "Ok" { "Wrap with Ok" } else { "Wrap with Some" };
106 Some(Fix::new(name, source_change, tail_expr_range)) 108 Some(fix("wrap_tail_expr", name, source_change, tail_expr_range))
107 } 109 }
108} 110}
109 111
110impl DiagnosticWithFix for RemoveThisSemicolon { 112impl DiagnosticWithFix for RemoveThisSemicolon {
111 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 113 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
112 let root = sema.db.parse_or_expand(self.file)?; 114 let root = sema.db.parse_or_expand(self.file)?;
113 115
114 let semicolon = self 116 let semicolon = self
@@ -123,12 +125,12 @@ impl DiagnosticWithFix for RemoveThisSemicolon {
123 let edit = TextEdit::delete(semicolon); 125 let edit = TextEdit::delete(semicolon);
124 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 126 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
125 127
126 Some(Fix::new("Remove this semicolon", source_change, semicolon)) 128 Some(fix("remove_semicolon", "Remove this semicolon", source_change, semicolon))
127 } 129 }
128} 130}
129 131
130impl DiagnosticWithFix for IncorrectCase { 132impl DiagnosticWithFix for IncorrectCase {
131 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 133 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
132 let root = sema.db.parse_or_expand(self.file)?; 134 let root = sema.db.parse_or_expand(self.file)?;
133 let name_node = self.ident.to_node(&root); 135 let name_node = self.ident.to_node(&root);
134 136
@@ -140,12 +142,12 @@ impl DiagnosticWithFix for IncorrectCase {
140 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; 142 rename_with_semantics(sema, file_position, &self.suggested_text).ok()?;
141 143
142 let label = format!("Rename to {}", self.suggested_text); 144 let label = format!("Rename to {}", self.suggested_text);
143 Some(Fix::new(&label, rename_changes, frange.range)) 145 Some(fix("change_case", &label, rename_changes, frange.range))
144 } 146 }
145} 147}
146 148
147impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { 149impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
148 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Fix> { 150 fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> {
149 let root = sema.db.parse_or_expand(self.file)?; 151 let root = sema.db.parse_or_expand(self.file)?;
150 let next_expr = self.next_expr.to_node(&root); 152 let next_expr = self.next_expr.to_node(&root);
151 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; 153 let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?;
@@ -163,7 +165,8 @@ impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap {
163 165
164 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit); 166 let source_change = SourceChange::from_text_edit(self.file.original_file(sema.db), edit);
165 167
166 Some(Fix::new( 168 Some(fix(
169 "replace_with_find_map",
167 "Replace filter_map(..).next() with find_map()", 170 "Replace filter_map(..).next() with find_map()",
168 source_change, 171 source_change,
169 trigger_range, 172 trigger_range,
@@ -175,7 +178,7 @@ fn missing_record_expr_field_fix(
175 sema: &Semantics<RootDatabase>, 178 sema: &Semantics<RootDatabase>,
176 usage_file_id: FileId, 179 usage_file_id: FileId,
177 record_expr_field: &ast::RecordExprField, 180 record_expr_field: &ast::RecordExprField,
178) -> Option<Fix> { 181) -> Option<Assist> {
179 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?; 182 let record_lit = ast::RecordExpr::cast(record_expr_field.syntax().parent()?.parent()?)?;
180 let def_id = sema.resolve_variant(record_lit)?; 183 let def_id = sema.resolve_variant(record_lit)?;
181 let module; 184 let module;
@@ -233,7 +236,12 @@ fn missing_record_expr_field_fix(
233 def_file_id, 236 def_file_id,
234 TextEdit::insert(last_field_syntax.text_range().end(), new_field), 237 TextEdit::insert(last_field_syntax.text_range().end(), new_field),
235 ); 238 );
236 return Some(Fix::new("Create field", source_change, record_expr_field.syntax().text_range())); 239 return Some(fix(
240 "create_field",
241 "Create field",
242 source_change,
243 record_expr_field.syntax().text_range(),
244 ));
237 245
238 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> { 246 fn record_field_list(field_def_list: ast::FieldList) -> Option<ast::RecordFieldList> {
239 match field_def_list { 247 match field_def_list {
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs
index e174fb767..5482b7287 100644
--- a/crates/ide/src/diagnostics/unlinked_file.rs
+++ b/crates/ide/src/diagnostics/unlinked_file.rs
@@ -16,9 +16,10 @@ use syntax::{
16}; 16};
17use text_edit::TextEdit; 17use text_edit::TextEdit;
18 18
19use crate::Fix; 19use crate::{
20 20 diagnostics::{fix, fixes::DiagnosticWithFix},
21use super::fixes::DiagnosticWithFix; 21 Assist,
22};
22 23
23// Diagnostic: unlinked-file 24// Diagnostic: unlinked-file
24// 25//
@@ -49,7 +50,7 @@ impl Diagnostic for UnlinkedFile {
49} 50}
50 51
51impl DiagnosticWithFix for UnlinkedFile { 52impl DiagnosticWithFix for UnlinkedFile {
52 fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Fix> { 53 fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Assist> {
53 // If there's an existing module that could add a `mod` item to include the unlinked file, 54 // If there's an existing module that could add a `mod` item to include the unlinked file,
54 // suggest that as a fix. 55 // suggest that as a fix.
55 56
@@ -100,7 +101,7 @@ fn make_fix(
100 parent_file_id: FileId, 101 parent_file_id: FileId,
101 new_mod_name: &str, 102 new_mod_name: &str,
102 added_file_id: FileId, 103 added_file_id: FileId,
103) -> Option<Fix> { 104) -> Option<Assist> {
104 fn is_outline_mod(item: &ast::Item) -> bool { 105 fn is_outline_mod(item: &ast::Item) -> bool {
105 matches!(item, ast::Item::Module(m) if m.item_list().is_none()) 106 matches!(item, ast::Item::Module(m) if m.item_list().is_none())
106 } 107 }
@@ -152,7 +153,8 @@ fn make_fix(
152 153
153 let edit = builder.finish(); 154 let edit = builder.finish();
154 let trigger_range = db.parse(added_file_id).tree().syntax().text_range(); 155 let trigger_range = db.parse(added_file_id).tree().syntax().text_range();
155 Some(Fix::new( 156 Some(fix(
157 "add_mod_declaration",
156 &format!("Insert `{}`", mod_decl), 158 &format!("Insert `{}`", mod_decl),
157 SourceChange::from_text_edit(parent_file_id, edit), 159 SourceChange::from_text_edit(parent_file_id, edit),
158 trigger_range, 160 trigger_range,
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs
index 3f73c0632..0615b26d3 100644
--- a/crates/ide/src/lib.rs
+++ b/crates/ide/src/lib.rs
@@ -69,7 +69,7 @@ use crate::display::ToNav;
69pub use crate::{ 69pub use crate::{
70 annotations::{Annotation, AnnotationConfig, AnnotationKind}, 70 annotations::{Annotation, AnnotationConfig, AnnotationKind},
71 call_hierarchy::CallItem, 71 call_hierarchy::CallItem,
72 diagnostics::{Diagnostic, DiagnosticsConfig, Fix, Severity}, 72 diagnostics::{Diagnostic, DiagnosticsConfig, Severity},
73 display::navigation_target::NavigationTarget, 73 display::navigation_target::NavigationTarget,
74 expand_macro::ExpandedMacro, 74 expand_macro::ExpandedMacro,
75 file_structure::{StructureNode, StructureNodeKind}, 75 file_structure::{StructureNode, StructureNodeKind},