diff options
Diffstat (limited to 'crates/ide/src')
-rw-r--r-- | crates/ide/src/diagnostics.rs | 60 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/fixes.rs | 38 | ||||
-rw-r--r-- | crates/ide/src/diagnostics/unlinked_file.rs | 2 | ||||
-rw-r--r-- | crates/ide/src/lib.rs | 8 |
4 files changed, 70 insertions, 38 deletions
diff --git a/crates/ide/src/diagnostics.rs b/crates/ide/src/diagnostics.rs index 4f0b4a62e..9a883acb9 100644 --- a/crates/ide/src/diagnostics.rs +++ b/crates/ide/src/diagnostics.rs | |||
@@ -84,6 +84,7 @@ pub struct DiagnosticsConfig { | |||
84 | pub(crate) fn diagnostics( | 84 | pub(crate) fn diagnostics( |
85 | db: &RootDatabase, | 85 | db: &RootDatabase, |
86 | config: &DiagnosticsConfig, | 86 | config: &DiagnosticsConfig, |
87 | resolve: bool, | ||
87 | file_id: FileId, | 88 | file_id: FileId, |
88 | ) -> Vec<Diagnostic> { | 89 | ) -> Vec<Diagnostic> { |
89 | let _p = profile::span("diagnostics"); | 90 | let _p = profile::span("diagnostics"); |
@@ -107,25 +108,25 @@ pub(crate) fn diagnostics( | |||
107 | let res = RefCell::new(res); | 108 | let res = RefCell::new(res); |
108 | let sink_builder = DiagnosticSinkBuilder::new() | 109 | let sink_builder = DiagnosticSinkBuilder::new() |
109 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 110 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
110 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 111 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
111 | }) | 112 | }) |
112 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 113 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
113 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 114 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
114 | }) | 115 | }) |
115 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { | 116 | .on::<hir::diagnostics::MissingOkOrSomeInTailExpr, _>(|d| { |
116 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 117 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
117 | }) | 118 | }) |
118 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 119 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
119 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 120 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
120 | }) | 121 | }) |
121 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { | 122 | .on::<hir::diagnostics::RemoveThisSemicolon, _>(|d| { |
122 | res.borrow_mut().push(diagnostic_with_fix(d, &sema)); | 123 | res.borrow_mut().push(diagnostic_with_fix(d, &sema, resolve)); |
123 | }) | 124 | }) |
124 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { | 125 | .on::<hir::diagnostics::IncorrectCase, _>(|d| { |
125 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 126 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
126 | }) | 127 | }) |
127 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { | 128 | .on::<hir::diagnostics::ReplaceFilterMapNextWithFindMap, _>(|d| { |
128 | res.borrow_mut().push(warning_with_fix(d, &sema)); | 129 | res.borrow_mut().push(warning_with_fix(d, &sema, resolve)); |
129 | }) | 130 | }) |
130 | .on::<hir::diagnostics::InactiveCode, _>(|d| { | 131 | .on::<hir::diagnostics::InactiveCode, _>(|d| { |
131 | // 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. |
@@ -152,7 +153,7 @@ pub(crate) fn diagnostics( | |||
152 | // Override severity and mark as unused. | 153 | // Override severity and mark as unused. |
153 | res.borrow_mut().push( | 154 | res.borrow_mut().push( |
154 | Diagnostic::hint(range, d.message()) | 155 | Diagnostic::hint(range, d.message()) |
155 | .with_fix(d.fix(&sema)) | 156 | .with_fix(d.fix(&sema, resolve)) |
156 | .with_code(Some(d.code())), | 157 | .with_code(Some(d.code())), |
157 | ); | 158 | ); |
158 | }) | 159 | }) |
@@ -208,15 +209,23 @@ pub(crate) fn diagnostics( | |||
208 | res.into_inner() | 209 | res.into_inner() |
209 | } | 210 | } |
210 | 211 | ||
211 | 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 { | ||
212 | 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()) |
213 | .with_fix(d.fix(&sema)) | 218 | .with_fix(d.fix(&sema, resolve)) |
214 | .with_code(Some(d.code())) | 219 | .with_code(Some(d.code())) |
215 | } | 220 | } |
216 | 221 | ||
217 | 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 { | ||
218 | 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()) |
219 | .with_fix(d.fix(&sema)) | 228 | .with_fix(d.fix(&sema, resolve)) |
220 | .with_code(Some(d.code())) | 229 | .with_code(Some(d.code())) |
221 | } | 230 | } |
222 | 231 | ||
@@ -271,13 +280,19 @@ fn text_edit_for_remove_unnecessary_braces_with_self_in_use_statement( | |||
271 | } | 280 | } |
272 | 281 | ||
273 | fn fix(id: &'static str, label: &str, source_change: SourceChange, target: TextRange) -> Assist { | 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 { | ||
274 | assert!(!id.contains(' ')); | 289 | assert!(!id.contains(' ')); |
275 | Assist { | 290 | Assist { |
276 | id: AssistId(id, AssistKind::QuickFix), | 291 | id: AssistId(id, AssistKind::QuickFix), |
277 | label: Label::new(label), | 292 | label: Label::new(label), |
278 | group: None, | 293 | group: None, |
279 | target, | 294 | target, |
280 | source_change: Some(source_change), | 295 | source_change: None, |
281 | } | 296 | } |
282 | } | 297 | } |
283 | 298 | ||
@@ -299,7 +314,7 @@ mod tests { | |||
299 | 314 | ||
300 | let (analysis, file_position) = fixture::position(ra_fixture_before); | 315 | let (analysis, file_position) = fixture::position(ra_fixture_before); |
301 | let diagnostic = analysis | 316 | let diagnostic = analysis |
302 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 317 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
303 | .unwrap() | 318 | .unwrap() |
304 | .pop() | 319 | .pop() |
305 | .unwrap(); | 320 | .unwrap(); |
@@ -328,7 +343,7 @@ mod tests { | |||
328 | fn check_no_fix(ra_fixture: &str) { | 343 | fn check_no_fix(ra_fixture: &str) { |
329 | let (analysis, file_position) = fixture::position(ra_fixture); | 344 | let (analysis, file_position) = fixture::position(ra_fixture); |
330 | let diagnostic = analysis | 345 | let diagnostic = analysis |
331 | .diagnostics(&DiagnosticsConfig::default(), file_position.file_id) | 346 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
332 | .unwrap() | 347 | .unwrap() |
333 | .pop() | 348 | .pop() |
334 | .unwrap(); | 349 | .unwrap(); |
@@ -342,7 +357,7 @@ mod tests { | |||
342 | let diagnostics = files | 357 | let diagnostics = files |
343 | .into_iter() | 358 | .into_iter() |
344 | .flat_map(|file_id| { | 359 | .flat_map(|file_id| { |
345 | analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap() | 360 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap() |
346 | }) | 361 | }) |
347 | .collect::<Vec<_>>(); | 362 | .collect::<Vec<_>>(); |
348 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 363 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
@@ -350,7 +365,8 @@ mod tests { | |||
350 | 365 | ||
351 | fn check_expect(ra_fixture: &str, expect: Expect) { | 366 | fn check_expect(ra_fixture: &str, expect: Expect) { |
352 | let (analysis, file_id) = fixture::file(ra_fixture); | 367 | let (analysis, file_id) = fixture::file(ra_fixture); |
353 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 368 | let diagnostics = |
369 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
354 | expect.assert_debug_eq(&diagnostics) | 370 | expect.assert_debug_eq(&diagnostics) |
355 | } | 371 | } |
356 | 372 | ||
@@ -895,10 +911,11 @@ struct Foo { | |||
895 | 911 | ||
896 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); | 912 | let (analysis, file_id) = fixture::file(r#"mod foo;"#); |
897 | 913 | ||
898 | let diagnostics = analysis.diagnostics(&config, file_id).unwrap(); | 914 | let diagnostics = analysis.diagnostics(&config, true, file_id).unwrap(); |
899 | assert!(diagnostics.is_empty()); | 915 | assert!(diagnostics.is_empty()); |
900 | 916 | ||
901 | let diagnostics = analysis.diagnostics(&DiagnosticsConfig::default(), file_id).unwrap(); | 917 | let diagnostics = |
918 | analysis.diagnostics(&DiagnosticsConfig::default(), true, file_id).unwrap(); | ||
902 | assert!(!diagnostics.is_empty()); | 919 | assert!(!diagnostics.is_empty()); |
903 | } | 920 | } |
904 | 921 | ||
@@ -1004,8 +1021,9 @@ impl TestStruct { | |||
1004 | let expected = r#"fn foo() {}"#; | 1021 | let expected = r#"fn foo() {}"#; |
1005 | 1022 | ||
1006 | let (analysis, file_position) = fixture::position(input); | 1023 | let (analysis, file_position) = fixture::position(input); |
1007 | let diagnostics = | 1024 | let diagnostics = analysis |
1008 | analysis.diagnostics(&DiagnosticsConfig::default(), file_position.file_id).unwrap(); | 1025 | .diagnostics(&DiagnosticsConfig::default(), true, file_position.file_id) |
1026 | .unwrap(); | ||
1009 | assert_eq!(diagnostics.len(), 1); | 1027 | assert_eq!(diagnostics.len(), 1); |
1010 | 1028 | ||
1011 | check_fix(input, expected); | 1029 | check_fix(input, expected); |
diff --git a/crates/ide/src/diagnostics/fixes.rs b/crates/ide/src/diagnostics/fixes.rs index 69cf5288c..7be8b3459 100644 --- a/crates/ide/src/diagnostics/fixes.rs +++ b/crates/ide/src/diagnostics/fixes.rs | |||
@@ -20,17 +20,26 @@ use syntax::{ | |||
20 | }; | 20 | }; |
21 | use text_edit::TextEdit; | 21 | use text_edit::TextEdit; |
22 | 22 | ||
23 | use crate::{diagnostics::fix, references::rename::rename_with_semantics, Assist, FilePosition}; | 23 | use crate::{ |
24 | diagnostics::{fix, unresolved_fix}, | ||
25 | references::rename::rename_with_semantics, | ||
26 | Assist, FilePosition, | ||
27 | }; | ||
24 | 28 | ||
25 | /// A [Diagnostic] that potentially has a fix available. | 29 | /// A [Diagnostic] that potentially has a fix available. |
26 | /// | 30 | /// |
27 | /// [Diagnostic]: hir::diagnostics::Diagnostic | 31 | /// [Diagnostic]: hir::diagnostics::Diagnostic |
28 | pub(crate) trait DiagnosticWithFix: Diagnostic { | 32 | pub(crate) trait DiagnosticWithFix: Diagnostic { |
29 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist>; | 33 | /// `resolve` determines if the diagnostic should fill in the `edit` field |
34 | /// of the assist. | ||
35 | /// | ||
36 | /// If `resolve` is false, the edit will be computed later, on demand, and | ||
37 | /// can be omitted. | ||
38 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist>; | ||
30 | } | 39 | } |
31 | 40 | ||
32 | impl DiagnosticWithFix for UnresolvedModule { | 41 | impl DiagnosticWithFix for UnresolvedModule { |
33 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 42 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
34 | let root = sema.db.parse_or_expand(self.file)?; | 43 | let root = sema.db.parse_or_expand(self.file)?; |
35 | let unresolved_module = self.decl.to_node(&root); | 44 | let unresolved_module = self.decl.to_node(&root); |
36 | Some(fix( | 45 | Some(fix( |
@@ -50,7 +59,7 @@ impl DiagnosticWithFix for UnresolvedModule { | |||
50 | } | 59 | } |
51 | 60 | ||
52 | impl DiagnosticWithFix for NoSuchField { | 61 | impl DiagnosticWithFix for NoSuchField { |
53 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 62 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
54 | let root = sema.db.parse_or_expand(self.file)?; | 63 | let root = sema.db.parse_or_expand(self.file)?; |
55 | missing_record_expr_field_fix( | 64 | missing_record_expr_field_fix( |
56 | &sema, | 65 | &sema, |
@@ -61,7 +70,7 @@ impl DiagnosticWithFix for NoSuchField { | |||
61 | } | 70 | } |
62 | 71 | ||
63 | impl DiagnosticWithFix for MissingFields { | 72 | impl DiagnosticWithFix for MissingFields { |
64 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 73 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
65 | // Note that although we could add a diagnostics to | 74 | // Note that although we could add a diagnostics to |
66 | // fill the missing tuple field, e.g : | 75 | // fill the missing tuple field, e.g : |
67 | // `struct A(usize);` | 76 | // `struct A(usize);` |
@@ -97,7 +106,7 @@ impl DiagnosticWithFix for MissingFields { | |||
97 | } | 106 | } |
98 | 107 | ||
99 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | 108 | impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { |
100 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 109 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
101 | let root = sema.db.parse_or_expand(self.file)?; | 110 | let root = sema.db.parse_or_expand(self.file)?; |
102 | let tail_expr = self.expr.to_node(&root); | 111 | let tail_expr = self.expr.to_node(&root); |
103 | let tail_expr_range = tail_expr.syntax().text_range(); | 112 | let tail_expr_range = tail_expr.syntax().text_range(); |
@@ -110,7 +119,7 @@ impl DiagnosticWithFix for MissingOkOrSomeInTailExpr { | |||
110 | } | 119 | } |
111 | 120 | ||
112 | impl DiagnosticWithFix for RemoveThisSemicolon { | 121 | impl DiagnosticWithFix for RemoveThisSemicolon { |
113 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 122 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
114 | let root = sema.db.parse_or_expand(self.file)?; | 123 | let root = sema.db.parse_or_expand(self.file)?; |
115 | 124 | ||
116 | let semicolon = self | 125 | let semicolon = self |
@@ -130,7 +139,7 @@ impl DiagnosticWithFix for RemoveThisSemicolon { | |||
130 | } | 139 | } |
131 | 140 | ||
132 | impl DiagnosticWithFix for IncorrectCase { | 141 | impl DiagnosticWithFix for IncorrectCase { |
133 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 142 | fn fix(&self, sema: &Semantics<RootDatabase>, resolve: bool) -> Option<Assist> { |
134 | let root = sema.db.parse_or_expand(self.file)?; | 143 | let root = sema.db.parse_or_expand(self.file)?; |
135 | let name_node = self.ident.to_node(&root); | 144 | let name_node = self.ident.to_node(&root); |
136 | 145 | ||
@@ -138,16 +147,19 @@ impl DiagnosticWithFix for IncorrectCase { | |||
138 | let frange = name_node.original_file_range(sema.db); | 147 | let frange = name_node.original_file_range(sema.db); |
139 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; | 148 | let file_position = FilePosition { file_id: frange.file_id, offset: frange.range.start() }; |
140 | 149 | ||
141 | let rename_changes = | ||
142 | rename_with_semantics(sema, file_position, &self.suggested_text).ok()?; | ||
143 | |||
144 | let label = format!("Rename to {}", self.suggested_text); | 150 | let label = format!("Rename to {}", self.suggested_text); |
145 | Some(fix("change_case", &label, rename_changes, frange.range)) | 151 | let mut res = unresolved_fix("change_case", &label, frange.range); |
152 | if resolve { | ||
153 | let source_change = rename_with_semantics(sema, file_position, &self.suggested_text); | ||
154 | res.source_change = Some(source_change.ok().unwrap_or_default()); | ||
155 | } | ||
156 | |||
157 | Some(res) | ||
146 | } | 158 | } |
147 | } | 159 | } |
148 | 160 | ||
149 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { | 161 | impl DiagnosticWithFix for ReplaceFilterMapNextWithFindMap { |
150 | fn fix(&self, sema: &Semantics<RootDatabase>) -> Option<Assist> { | 162 | fn fix(&self, sema: &Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
151 | let root = sema.db.parse_or_expand(self.file)?; | 163 | let root = sema.db.parse_or_expand(self.file)?; |
152 | let next_expr = self.next_expr.to_node(&root); | 164 | let next_expr = self.next_expr.to_node(&root); |
153 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; | 165 | let next_call = ast::MethodCallExpr::cast(next_expr.syntax().clone())?; |
diff --git a/crates/ide/src/diagnostics/unlinked_file.rs b/crates/ide/src/diagnostics/unlinked_file.rs index 5482b7287..7d39f4fbe 100644 --- a/crates/ide/src/diagnostics/unlinked_file.rs +++ b/crates/ide/src/diagnostics/unlinked_file.rs | |||
@@ -50,7 +50,7 @@ impl Diagnostic for UnlinkedFile { | |||
50 | } | 50 | } |
51 | 51 | ||
52 | impl DiagnosticWithFix for UnlinkedFile { | 52 | impl DiagnosticWithFix for UnlinkedFile { |
53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>) -> Option<Assist> { | 53 | fn fix(&self, sema: &hir::Semantics<RootDatabase>, _resolve: bool) -> Option<Assist> { |
54 | // 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, |
55 | // suggest that as a fix. | 55 | // suggest that as a fix. |
56 | 56 | ||
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index bbc0d5eec..d481be09d 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -526,9 +526,10 @@ impl Analysis { | |||
526 | pub fn diagnostics( | 526 | pub fn diagnostics( |
527 | &self, | 527 | &self, |
528 | config: &DiagnosticsConfig, | 528 | config: &DiagnosticsConfig, |
529 | resolve: bool, | ||
529 | file_id: FileId, | 530 | file_id: FileId, |
530 | ) -> Cancelable<Vec<Diagnostic>> { | 531 | ) -> Cancelable<Vec<Diagnostic>> { |
531 | self.with_db(|db| diagnostics::diagnostics(db, config, file_id)) | 532 | self.with_db(|db| diagnostics::diagnostics(db, config, resolve, file_id)) |
532 | } | 533 | } |
533 | 534 | ||
534 | /// Convenience function to return assists + quick fixes for diagnostics | 535 | /// Convenience function to return assists + quick fixes for diagnostics |
@@ -550,9 +551,10 @@ impl Analysis { | |||
550 | 551 | ||
551 | if include_fixes { | 552 | if include_fixes { |
552 | res.extend( | 553 | res.extend( |
553 | diagnostics::diagnostics(db, diagnostics_config, frange.file_id) | 554 | diagnostics::diagnostics(db, diagnostics_config, resolve, frange.file_id) |
554 | .into_iter() | 555 | .into_iter() |
555 | .filter_map(|it| it.fix), | 556 | .filter_map(|it| it.fix) |
557 | .filter(|it| it.target.intersect(frange.range).is_some()), | ||
556 | ); | 558 | ); |
557 | } | 559 | } |
558 | res | 560 | res |