aboutsummaryrefslogtreecommitdiff
path: root/crates/ide/src/diagnostics.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide/src/diagnostics.rs')
-rw-r--r--crates/ide/src/diagnostics.rs143
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::{
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,
@@ -99,6 +84,7 @@ pub struct DiagnosticsConfig {
99pub(crate) fn diagnostics( 84pub(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
226fn diagnostic_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 212fn 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
232fn warning_with_fix<D: DiagnosticWithFix>(d: &D, sema: &Semantics<RootDatabase>) -> Diagnostic { 222fn 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
282fn 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
288fn 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)]
288mod tests { 300mod 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);