diff options
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 143 |
1 files changed, 83 insertions, 60 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 0ace80a1e..1c911a8b2 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -25,7 +25,7 @@ use syntax::{ | |||
25 | use text_edit::TextEdit; | 25 | use text_edit::TextEdit; |
26 | use unlinked_file::UnlinkedFile; | 26 | use unlinked_file::UnlinkedFile; |
27 | 27 | ||
28 | use crate::{FileId, Label, SourceChange}; | 28 | use crate::{Assist, AssistId, AssistKind, FileId, Label, SourceChange}; |
29 | 29 | ||
30 | use self::fixes::DiagnosticWithFix; | 30 | use 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)] | ||
73 | pub 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 | |||
80 | impl 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)] |
88 | pub enum Severity { | 73 | pub enum Severity { |
89 | Error, | 74 | Error, |
@@ -99,6 +84,7 @@ pub struct DiagnosticsConfig { | |||
99 | pub(crate) fn diagnostics( | 84 | pub(crate) fn diagnostics( |
100 | db: &RootDatabase, | 85 | db: &RootDatabase, |
101 | config: &DiagnosticsConfig, | 86 | config: &DiagnosticsConfig, |
87 | resolve: bool, | ||
102 | file_id: FileId, | 88 | file_id: FileId, |
103 | ) -> Vec<Diagnostic> { | 89 | ) -> Vec<Diagnostic> { |
104 | let _p = profile::span("diagnostics"); | 90 | let _p = profile::span("diagnostics"); |
@@ -122,25 +108,25 @@ pub(crate) fn diagnostics( | |||
122 | let res = RefCell::new(res); | 108 | let res = RefCell::new(res); |
123 | let sink_builder = DiagnosticSinkBuilder::new() | 109 | let sink_builder = DiagnosticSinkBuilder::new() |
124 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 110 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
125 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 111 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
126 | }) | 112 | }) |
127 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 113 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
128 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 114 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
129 | }) | 115 | }) |
130 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { | 116 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { |
131 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 117 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
132 | }) | 118 | }) |
133 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 119 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
134 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 120 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
135 | }) | 121 | }) |
136 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { | 122 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { |
137 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 123 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
138 | }) | 124 | }) |
139 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | 125 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { |
140 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 126 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
141 | }) | 127 | }) |
142 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | 128 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { |
143 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 129 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
144 | }) | 130 | }) |
145 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | 131 | .on::<hir::diagnostics::InactiveCode, _>(|d| { |
146 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. | 132 | // If there's inactive code somewhere in a macro, don't propagate to the call-site. |
@@ -167,7 +153,7 @@ pub(crate) fn diagnostics( | |||
167 | // Override severity and mark as unused. | 153 | // Override severity and mark as unused. |
168 | res.borrow_mut().push( | 154 | res.borrow_mut().push( |
169 | Diagnostic::hint(range, d.message()) | 155 | Diagnostic::hint(range, d.message()) |
170 | .with_fix(d.fix(&sema)) | 156 | .with_fix(d.fix(&sema, resolve)) |
171 | .with_code(Some(d.code())), | 157 | .with_code(Some(d.code())), |
172 | ); | 158 | ); |
173 | }) | 159 | }) |
@@ -223,15 +209,23 @@ pub(crate) fn diagnostics( | |||
223 | res.into_inner() | 209 | res.into_inner() |
224 | } | 210 | } |
225 | 211 | ||
226 | fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 212 | fn diagnostic_with_fix<D: DiagnosticWithFix>( |
213 | d: &D, | ||
214 | sema: &Semantics<RootDatabase>, | ||
215 | resolve: bool, | ||
216 | ) -> Diagnostic { | ||
227 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 217 | Diagnostic::error(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
228 | .with_fix(d.fix(&sema)) | 218 | .with_fix(d.fix(&sema, resolve)) |
229 | .with_code(Some(d.code())) | 219 | .with_code(Some(d.code())) |
230 | } | 220 | } |
231 | 221 | ||
232 | fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { | 222 | fn warning_with_fix<D: DiagnosticWithFix>( |
223 | d: &D, | ||
224 | sema: &Semantics<RootDatabase>, | ||
225 | resolve: bool, | ||
226 | ) -> Diagnostic { | ||
233 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) | 227 | Diagnostic::hint(sema.diagnostics_display_range(d.display_source()).range, d.message()) |
234 | .with_fix(d.fix(&sema)) | 228 | .with_fix(d.fix(&sema, resolve)) |
235 | .with_code(Some(d.code())) | 229 | .with_code(Some(d.code())) |
236 | } | 230 | } |
237 | 231 | ||
@@ -261,7 +255,8 @@ fn check_unnecessary_braces_in_use_statement( | |||
261 | 255 | ||
262 | acc.push( | 256 | acc.push( |
263 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) | 257 | Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) |
264 | .with_fix(Some(Fix::new( | 258 | .with_fix(Some(fix( |
259 | "remove_braces", | ||
265 | "Remove unnecessary braces", | 260 | "Remove unnecessary braces", |
266 | SourceChange::from_text_edit(file_id, edit), | 261 | SourceChange::from_text_edit(file_id, edit), |
267 | use_range, | 262 | use_range, |
@@ -284,6 +279,23 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
284 | None | 279 | None |
285 | } | 280 | } |
286 | 281 | ||
282 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | ||
283 | let mut res = unresolved_fix(id, label, target); | ||
284 | res.source_change = Some(source_change); | ||
285 | res | ||
286 | } | ||
287 | |||
288 | fn unresolved_fix(id: &'static str, label: &str, target: TextRange) -> Assist { | ||
289 | assert!(!id.contains(' ')); | ||
290 | Assist { | ||
291 | id: AssistId(id, AssistKind::QuickFix), | ||
292 | label: Label::new(label), | ||
293 | group: None, | ||
294 | target, | ||
295 | source_change: None, | ||
296 | } | ||
297 | } | ||
298 | |||
287 | #[cfg(test)] | 299 | #[cfg(test)] |
288 | mod tests { | 300 | mod tests { |
289 | use expect_test::{expect, Expect}; | 301 | use expect_test::{expect, Expect}; |
@@ -302,16 +314,17 @@ mod tests { | |||
302 | 314 | ||
303 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 315 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
304 | let diagnostic = analysis | 316 | let diagnostic = analysis |
305 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
306 | .unwrap() | 318 | .unwrap() |
307 | .pop() | 319 | .pop() |
308 | .unwrap(); | 320 | .unwrap(); |
309 | let fix = diagnostic.fix.unwrap(); | 321 | let fix = diagnostic.fix.unwrap(); |
310 | let actual = { | 322 | let actual = { |
311 | let file_id = *fix.source_change.source_file_edits.keys().next().unwrap(); | 323 | let source_change = fix.source_change.unwrap(); |
324 | let file_id = *source_change.source_file_edits.keys().next().unwrap(); | ||
312 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); | 325 | let mut actual = analysis.file_text(file_id).unwrap().to_string(); |
313 | 326 | ||
314 | for edit in fix.source_change.source_file_edits.values() { | 327 | for edit in source_change.source_file_edits.values() { |
315 | edit.apply(&mut actual); | 328 | edit.apply(&mut actual); |
316 | } | 329 | } |
317 | actual | 330 | actual |
@@ -319,9 +332,9 @@ mod tests { | |||
319 | 332 | ||
320 | assert_eq_text!(&after, &actual); | 333 | assert_eq_text!(&after, &actual); |
321 | assert!( | 334 | assert!( |
322 | fix.fix_trigger_range.contains_inclusive(file_position.offset), | 335 | fix.target.contains_inclusive(file_position.offset), |
323 | "diagnostic fix range {:?} does not touch cursor position {:?}", | 336 | "diagnostic fix range {:?} does not touch cursor position {:?}", |
324 | fix.fix_trigger_range, | 337 | fix.target, |
325 | file_position.offset | 338 | file_position.offset |
326 | ); | 339 | ); |
327 | } | 340 | } |
@@ -330,7 +343,7 @@ mod tests { | |||
330 | fn check_no_fix(ra_fixture: &str) { | 343 | fn check_no_fix(ra_fixture: &str) { |
331 | let (analysis, file_position) = fixture::position(ra_fixture); | 344 | let (analysis, file_position) = fixture::position(ra_fixture); |
332 | let diagnostic = analysis | 345 | let diagnostic = analysis |
333 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
334 | .unwrap() | 347 | .unwrap() |
335 | .pop() | 348 | .pop() |
336 | .unwrap(); | 349 | .unwrap(); |
@@ -344,7 +357,7 @@ mod tests { | |||
344 | let diagnostics = files | 357 | let diagnostics = files |
345 | .into_iter() | 358 | .into_iter() |
346 | .flat_map(|file_id| { | 359 | .flat_map(|file_id| { |
347 | analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() | 360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() |
348 | }) | 361 | }) |
349 | .collect::<Vec<_>>(); | 362 | .collect::<Vec<_>>(); |
350 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -352,7 +365,8 @@ mod tests { | |||
352 | 365 | ||
353 | fn check_expect(ra_fixture: &str, expect: Expect) { | 366 | fn check_expect(ra_fixture: &str, expect: Expect) { |
354 | let (analysis, file_id) = fixture::file(ra_fixture); | 367 | let (analysis, file_id) = fixture::file(ra_fixture); |
355 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 368 | let diagnostics = |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
356 | expect.assert_debug_eq(&diagnostics) | 370 | expect.assert_debug_eq(&diagnostics) |
357 | } | 371 | } |
358 | 372 | ||
@@ -665,24 +679,31 @@ fn test_fn() { | |||
665 | range: 0..8, | 679 | range: 0..8, |
666 | severity: Error, | 680 | severity: Error, |
667 | fix: Some( | 681 | fix: Some( |
668 | Fix { | 682 | Assist { |
683 | id: AssistId( | ||
684 | "create_module", | ||
685 | QuickFix, | ||
686 | ), | ||
669 | label: "Create module", | 687 | label: "Create module", |
670 | source_change: SourceChange { | 688 | group: None, |
671 | source_file_edits: {}, | 689 | target: 0..8, |
672 | file_system_edits: [ | 690 | source_change: Some( |
673 | CreateFile { | 691 | SourceChange { |
674 | dst: AnchoredPathBuf { | 692 | source_file_edits: {}, |
675 | anchor: FileId( | 693 | file_system_edits: [ |
676 | 0, | 694 | CreateFile { |
677 | ), | 695 | dst: AnchoredPathBuf { |
678 | path: "foo.rs", | 696 | anchor: FileId( |
697 | 0, | ||
698 | ), | ||
699 | path: "foo.rs", | ||
700 | }, | ||
701 | initial_contents: "", | ||
679 | }, | 702 | }, |
680 | initial_contents: "", | 703 | ], |
681 | }, | 704 | is_snippet: false, |
682 | ], | 705 | }, |
683 | is_snippet: false, | 706 | ), |
684 | }, | ||
685 | fix_trigger_range: 0..8, | ||
686 | }, | 707 | }, |
687 | ), | 708 | ), |
688 | unused: false, | 709 | unused: false, |
@@ -704,7 +725,7 @@ fn test_fn() { | |||
704 | expect![[r#" | 725 | expect![[r#" |
705 | [ | 726 | [ |
706 | Diagnostic { | 727 | Diagnostic { |
707 | message: "unresolved macro call", | 728 | message: "unresolved macro `foo::bar!`", |
708 | range: 5..8, | 729 | range: 5..8, |
709 | severity: Error, | 730 | severity: Error, |
710 | fix: None, | 731 | fix: None, |
@@ -890,10 +911,11 @@ struct Foo { | |||
890 | 911 | ||
891 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
892 | 913 | ||
893 | let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); | 914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); |
894 | assert!(diagnostics.is_empty()); | 915 | assert!(diagnostics.is_empty()); |
895 | 916 | ||
896 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 917 | let diagnostics = |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
897 | assert!(!diagnostics.is_empty()); | 919 | assert!(!diagnostics.is_empty()); |
898 | } | 920 | } |
899 | 921 | ||
@@ -999,8 +1021,9 @@ impl TestStruct { | |||
999 | let expected = r#"fn foo() {}"#; | 1021 | let expected = r#"fn foo() {}"#; |
1000 | 1022 | ||
1001 | let (analysis, file_position) = fixture::position(input); | 1023 | let (analysis, file_position) = fixture::position(input); |
1002 | let diagnostics = | 1024 | let diagnostics = analysis |
1003 | analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); | 1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
1026 | .unwrap(); | ||
1004 | assert_eq!(diagnostics.len(), 1); | 1027 | assert_eq!(diagnostics.len(), 1); |
1005 | 1028 | ||
1006 | check_fix(input, expected); | 1029 | check_fix(input, expected); |