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.rs158
1 files changed, 91 insertions, 67 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs
index 22697a537..1c911a8b2 100644
--- a/crates/ide/src/diagnostics.rs
+++ b/crates/ide/src/diagnostics.rs
@@ -20,12 +20,12 @@ use itertools::Itertools;
20use rustc_hash::FxHashSet; 20use rustc_hash::FxHashSet;
21use syntax::{ 21use syntax::{
22 ast::{self, AstNode}, 22 ast::{self, AstNode},
23 SyntaxNode, SyntaxNodePtr, TextRange, 23 SyntaxNode, SyntaxNodePtr, TextRange, TextSize,
24}; 24};
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.
@@ -159,15 +145,16 @@ pub(crate) fn diagnostics(
159 ); 145 );
160 }) 146 })
161 .on::<UnlinkedFile, _>(|d| { 147 .on::<UnlinkedFile, _>(|d| {
148 // Limit diagnostic to the first few characters in the file. This matches how VS Code
149 // renders it with the full span, but on other editors, and is less invasive.
150 let range = sema.diagnostics_display_range(d.display_source()).range;
151 let range = range.intersect(TextRange::up_to(TextSize::of("..."))).unwrap_or(range);
152
162 // Override severity and mark as unused. 153 // Override severity and mark as unused.
163 res.borrow_mut().push( 154 res.borrow_mut().push(
164 Diagnostic::hint( 155 Diagnostic::hint(range, d.message())
165 sema.diagnostics_display_range(d.display_source()).range, 156 .with_fix(d.fix(&sema, resolve))
166 d.message(), 157 .with_code(Some(d.code())),
167 )
168 .with_unused(true)
169 .with_fix(d.fix(&sema))
170 .with_code(Some(d.code())),
171 ); 158 );
172 }) 159 })
173 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| { 160 .on::<hir::diagnostics::UnresolvedProcMacro, _>(|d| {
@@ -222,15 +209,23 @@ pub(crate) fn diagnostics(
222 res.into_inner() 209 res.into_inner()
223} 210}
224 211
225fn 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 {
226 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())
227 .with_fix(d.fix(&sema)) 218 .with_fix(d.fix(&sema, resolve))
228 .with_code(Some(d.code())) 219 .with_code(Some(d.code()))
229} 220}
230 221
231fn 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 {
232 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())
233 .with_fix(d.fix(&sema)) 228 .with_fix(d.fix(&sema, resolve))
234 .with_code(Some(d.code())) 229 .with_code(Some(d.code()))
235} 230}
236 231
@@ -260,7 +255,8 @@ fn check_unnecessary_braces_in_use_statement(
260 255
261 acc.push( 256 acc.push(
262 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string()) 257 Diagnostic::hint(use_range, "Unnecessary braces in use statement".to_string())
263 .with_fix(Some(Fix::new( 258 .with_fix(Some(fix(
259 "remove_braces",
264 "Remove unnecessary braces", 260 "Remove unnecessary braces",
265 SourceChange::from_text_edit(file_id, edit), 261 SourceChange::from_text_edit(file_id, edit),
266 use_range, 262 use_range,
@@ -283,6 +279,23 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement(
283 None 279 None
284} 280}
285 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
286#[cfg(test)] 299#[cfg(test)]
287mod tests { 300mod tests {
288 use expect_test::{expect, Expect}; 301 use expect_test::{expect, Expect};
@@ -301,16 +314,17 @@ mod tests {
301 314
302 let (analysis, file_position) = fixture::position(ra_fixture_before); 315 let (analysis, file_position) = fixture::position(ra_fixture_before);
303 let diagnostic = analysis 316 let diagnostic = analysis
304 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 317 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
305 .unwrap() 318 .unwrap()
306 .pop() 319 .pop()
307 .unwrap(); 320 .unwrap();
308 let fix = diagnostic.fix.unwrap(); 321 let fix = diagnostic.fix.unwrap();
309 let actual = { 322 let actual = {
310 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();
311 let mut actual = analysis.file_text(file_id).unwrap().to_string(); 325 let mut actual = analysis.file_text(file_id).unwrap().to_string();
312 326
313 for edit in fix.source_change.source_file_edits.values() { 327 for edit in source_change.source_file_edits.values() {
314 edit.apply(&mut actual); 328 edit.apply(&mut actual);
315 } 329 }
316 actual 330 actual
@@ -318,9 +332,9 @@ mod tests {
318 332
319 assert_eq_text!(&after, &actual); 333 assert_eq_text!(&after, &actual);
320 assert!( 334 assert!(
321 fix.fix_trigger_range.contains_inclusive(file_position.offset), 335 fix.target.contains_inclusive(file_position.offset),
322 "diagnostic fix range {:?} does not touch cursor position {:?}", 336 "diagnostic fix range {:?} does not touch cursor position {:?}",
323 fix.fix_trigger_range, 337 fix.target,
324 file_position.offset 338 file_position.offset
325 ); 339 );
326 } 340 }
@@ -329,7 +343,7 @@ mod tests {
329 fn check_no_fix(ra_fixture: &str) { 343 fn check_no_fix(ra_fixture: &str) {
330 let (analysis, file_position) = fixture::position(ra_fixture); 344 let (analysis, file_position) = fixture::position(ra_fixture);
331 let diagnostic = analysis 345 let diagnostic = analysis
332 .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) 346 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
333 .unwrap() 347 .unwrap()
334 .pop() 348 .pop()
335 .unwrap(); 349 .unwrap();
@@ -343,7 +357,7 @@ mod tests {
343 let diagnostics = files 357 let diagnostics = files
344 .into_iter() 358 .into_iter()
345 .flat_map(|file_id| { 359 .flat_map(|file_id| {
346 analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() 360 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap()
347 }) 361 })
348 .collect::<Vec<_>>(); 362 .collect::<Vec<_>>();
349 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); 363 assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics);
@@ -351,7 +365,8 @@ mod tests {
351 365
352 fn check_expect(ra_fixture: &str, expect: Expect) { 366 fn check_expect(ra_fixture: &str, expect: Expect) {
353 let (analysis, file_id) = fixture::file(ra_fixture); 367 let (analysis, file_id) = fixture::file(ra_fixture);
354 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 368 let diagnostics =
369 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
355 expect.assert_debug_eq(&diagnostics) 370 expect.assert_debug_eq(&diagnostics)
356 } 371 }
357 372
@@ -664,24 +679,31 @@ fn test_fn() {
664 range: 0..8, 679 range: 0..8,
665 severity: Error, 680 severity: Error,
666 fix: Some( 681 fix: Some(
667 Fix { 682 Assist {
683 id: AssistId(
684 "create_module",
685 QuickFix,
686 ),
668 label: "Create module", 687 label: "Create module",
669 source_change: SourceChange { 688 group: None,
670 source_file_edits: {}, 689 target: 0..8,
671 file_system_edits: [ 690 source_change: Some(
672 CreateFile { 691 SourceChange {
673 dst: AnchoredPathBuf { 692 source_file_edits: {},
674 anchor: FileId( 693 file_system_edits: [
675 0, 694 CreateFile {
676 ), 695 dst: AnchoredPathBuf {
677 path: "foo.rs", 696 anchor: FileId(
697 0,
698 ),
699 path: "foo.rs",
700 },
701 initial_contents: "",
678 }, 702 },
679 initial_contents: "", 703 ],
680 }, 704 is_snippet: false,
681 ], 705 },
682 is_snippet: false, 706 ),
683 },
684 fix_trigger_range: 0..8,
685 }, 707 },
686 ), 708 ),
687 unused: false, 709 unused: false,
@@ -703,7 +725,7 @@ fn test_fn() {
703 expect![[r#" 725 expect![[r#"
704 [ 726 [
705 Diagnostic { 727 Diagnostic {
706 message: "unresolved macro call", 728 message: "unresolved macro `foo::bar!`",
707 range: 5..8, 729 range: 5..8,
708 severity: Error, 730 severity: Error,
709 fix: None, 731 fix: None,
@@ -889,10 +911,11 @@ struct Foo {
889 911
890 let (analysis, file_id) = fixture::file(r#"mod foo;"#); 912 let (analysis, file_id) = fixture::file(r#"mod foo;"#);
891 913
892 let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); 914 let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap();
893 assert!(diagnostics.is_empty()); 915 assert!(diagnostics.is_empty());
894 916
895 let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); 917 let diagnostics =
918 analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap();
896 assert!(!diagnostics.is_empty()); 919 assert!(!diagnostics.is_empty());
897 } 920 }
898 921
@@ -998,8 +1021,9 @@ impl TestStruct {
998 let expected = r#"fn foo() {}"#; 1021 let expected = r#"fn foo() {}"#;
999 1022
1000 let (analysis, file_position) = fixture::position(input); 1023 let (analysis, file_position) = fixture::position(input);
1001 let diagnostics = 1024 let diagnostics = analysis
1002 analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); 1025 .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id)
1026 .unwrap();
1003 assert_eq!(diagnostics.len(), 1); 1027 assert_eq!(diagnostics.len(), 1);
1004 1028
1005 check_fix(input, expected); 1029 check_fix(input, expected);