diff options
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r-- | crates/ra_ide/src/diagnostics.rs | 156 | ||||
-rw-r--r-- | crates/ra_ide/src/lib.rs | 11 | ||||
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 35 |
3 files changed, 111 insertions, 91 deletions
diff --git a/crates/ra_ide/src/diagnostics.rs b/crates/ra_ide/src/diagnostics.rs index e029af0dc..897177d05 100644 --- a/crates/ra_ide/src/diagnostics.rs +++ b/crates/ra_ide/src/diagnostics.rs | |||
@@ -7,7 +7,7 @@ | |||
7 | use std::cell::RefCell; | 7 | use std::cell::RefCell; |
8 | 8 | ||
9 | use hir::{ | 9 | use hir::{ |
10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSink}, | 10 | diagnostics::{AstDiagnostic, Diagnostic as _, DiagnosticSinkBuilder}, |
11 | HasSource, HirDisplay, Semantics, VariantDef, | 11 | HasSource, HirDisplay, Semantics, VariantDef, |
12 | }; | 12 | }; |
13 | use itertools::Itertools; | 13 | use itertools::Itertools; |
@@ -29,7 +29,11 @@ pub enum Severity { | |||
29 | WeakWarning, | 29 | WeakWarning, |
30 | } | 30 | } |
31 | 31 | ||
32 | pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> { | 32 | pub(crate) fn diagnostics( |
33 | db: &RootDatabase, | ||
34 | file_id: FileId, | ||
35 | enable_experimental: bool, | ||
36 | ) -> Vec<Diagnostic> { | ||
33 | let _p = profile("diagnostics"); | 37 | let _p = profile("diagnostics"); |
34 | let sema = Semantics::new(db); | 38 | let sema = Semantics::new(db); |
35 | let parse = db.parse(file_id); | 39 | let parse = db.parse(file_id); |
@@ -48,79 +52,85 @@ pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> | |||
48 | check_struct_shorthand_initialization(&mut res, file_id, &node); | 52 | check_struct_shorthand_initialization(&mut res, file_id, &node); |
49 | } | 53 | } |
50 | let res = RefCell::new(res); | 54 | let res = RefCell::new(res); |
51 | let mut sink = DiagnosticSink::new(|d| { | 55 | let mut sink = DiagnosticSinkBuilder::new() |
52 | res.borrow_mut().push(Diagnostic { | 56 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { |
53 | message: d.message(), | 57 | let original_file = d.source().file_id.original_file(db); |
54 | range: sema.diagnostics_range(d).range, | 58 | let fix = Fix::new( |
55 | severity: Severity::Error, | 59 | "Create module", |
56 | fix: None, | 60 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() } |
57 | }) | 61 | .into(), |
58 | }) | 62 | ); |
59 | .on::<hir::diagnostics::UnresolvedModule, _>(|d| { | 63 | res.borrow_mut().push(Diagnostic { |
60 | let original_file = d.source().file_id.original_file(db); | 64 | range: sema.diagnostics_range(d).range, |
61 | let fix = Fix::new( | 65 | message: d.message(), |
62 | "Create module", | 66 | severity: Severity::Error, |
63 | FileSystemEdit::CreateFile { anchor: original_file, dst: d.candidate.clone() }.into(), | 67 | fix: Some(fix), |
64 | ); | 68 | }) |
65 | res.borrow_mut().push(Diagnostic { | ||
66 | range: sema.diagnostics_range(d).range, | ||
67 | message: d.message(), | ||
68 | severity: Severity::Error, | ||
69 | fix: Some(fix), | ||
70 | }) | 69 | }) |
71 | }) | 70 | .on::<hir::diagnostics::MissingFields, _>(|d| { |
72 | .on::<hir::diagnostics::MissingFields, _>(|d| { | 71 | // Note that although we could add a diagnostics to |
73 | // Note that although we could add a diagnostics to | 72 | // fill the missing tuple field, e.g : |
74 | // fill the missing tuple field, e.g : | 73 | // `struct A(usize);` |
75 | // `struct A(usize);` | 74 | // `let a = A { 0: () }` |
76 | // `let a = A { 0: () }` | 75 | // but it is uncommon usage and it should not be encouraged. |
77 | // but it is uncommon usage and it should not be encouraged. | 76 | let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { |
78 | let fix = if d.missed_fields.iter().any(|it| it.as_tuple_index().is_some()) { | 77 | None |
79 | None | 78 | } else { |
80 | } else { | 79 | let mut field_list = d.ast(db); |
81 | let mut field_list = d.ast(db); | 80 | for f in d.missed_fields.iter() { |
82 | for f in d.missed_fields.iter() { | 81 | let field = |
83 | let field = | 82 | make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); |
84 | make::record_field(make::name_ref(&f.to_string()), Some(make::expr_unit())); | 83 | field_list = field_list.append_field(&field); |
85 | field_list = field_list.append_field(&field); | 84 | } |
86 | } | 85 | |
87 | 86 | let edit = { | |
88 | let edit = { | 87 | let mut builder = TextEditBuilder::default(); |
89 | let mut builder = TextEditBuilder::default(); | 88 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()) |
90 | algo::diff(&d.ast(db).syntax(), &field_list.syntax()).into_text_edit(&mut builder); | 89 | .into_text_edit(&mut builder); |
91 | builder.finish() | 90 | builder.finish() |
91 | }; | ||
92 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) | ||
92 | }; | 93 | }; |
93 | Some(Fix::new("Fill struct fields", SourceFileEdit { file_id, edit }.into())) | ||
94 | }; | ||
95 | 94 | ||
96 | res.borrow_mut().push(Diagnostic { | 95 | res.borrow_mut().push(Diagnostic { |
97 | range: sema.diagnostics_range(d).range, | 96 | range: sema.diagnostics_range(d).range, |
98 | message: d.message(), | 97 | message: d.message(), |
99 | severity: Severity::Error, | 98 | severity: Severity::Error, |
100 | fix, | 99 | fix, |
100 | }) | ||
101 | }) | 101 | }) |
102 | }) | 102 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { |
103 | .on::<hir::diagnostics::MissingOkInTailExpr, _>(|d| { | 103 | let node = d.ast(db); |
104 | let node = d.ast(db); | 104 | let replacement = format!("Ok({})", node.syntax()); |
105 | let replacement = format!("Ok({})", node.syntax()); | 105 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); |
106 | let edit = TextEdit::replace(node.syntax().text_range(), replacement); | 106 | let source_change = SourceFileEdit { file_id, edit }.into(); |
107 | let source_change = SourceFileEdit { file_id, edit }.into(); | 107 | let fix = Fix::new("Wrap with ok", source_change); |
108 | let fix = Fix::new("Wrap with ok", source_change); | 108 | res.borrow_mut().push(Diagnostic { |
109 | res.borrow_mut().push(Diagnostic { | 109 | range: sema.diagnostics_range(d).range, |
110 | range: sema.diagnostics_range(d).range, | 110 | message: d.message(), |
111 | message: d.message(), | 111 | severity: Severity::Error, |
112 | severity: Severity::Error, | 112 | fix: Some(fix), |
113 | fix: Some(fix), | 113 | }) |
114 | }) | 114 | }) |
115 | }) | 115 | .on::<hir::diagnostics::NoSuchField, _>(|d| { |
116 | .on::<hir::diagnostics::NoSuchField, _>(|d| { | 116 | res.borrow_mut().push(Diagnostic { |
117 | res.borrow_mut().push(Diagnostic { | 117 | range: sema.diagnostics_range(d).range, |
118 | range: sema.diagnostics_range(d).range, | 118 | message: d.message(), |
119 | message: d.message(), | 119 | severity: Severity::Error, |
120 | severity: Severity::Error, | 120 | fix: missing_struct_field_fix(&sema, file_id, d), |
121 | fix: missing_struct_field_fix(&sema, file_id, d), | 121 | }) |
122 | }) | 122 | }) |
123 | }); | 123 | // Only collect experimental diagnostics when they're enabled. |
124 | .filter(|diag| !diag.is_experimental() || enable_experimental) | ||
125 | // Diagnostics not handled above get no fix and default treatment. | ||
126 | .build(|d| { | ||
127 | res.borrow_mut().push(Diagnostic { | ||
128 | message: d.message(), | ||
129 | range: sema.diagnostics_range(d).range, | ||
130 | severity: Severity::Error, | ||
131 | fix: None, | ||
132 | }) | ||
133 | }); | ||
124 | 134 | ||
125 | if let Some(m) = sema.to_module_def(file_id) { | 135 | if let Some(m) = sema.to_module_def(file_id) { |
126 | m.diagnostics(db, &mut sink); | 136 | m.diagnostics(db, &mut sink); |
@@ -298,7 +308,7 @@ mod tests { | |||
298 | let after = trim_indent(ra_fixture_after); | 308 | let after = trim_indent(ra_fixture_after); |
299 | 309 | ||
300 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); | 310 | let (analysis, file_position) = analysis_and_position(ra_fixture_before); |
301 | let diagnostic = analysis.diagnostics(file_position.file_id).unwrap().pop().unwrap(); | 311 | let diagnostic = analysis.diagnostics(file_position.file_id, true).unwrap().pop().unwrap(); |
302 | let mut fix = diagnostic.fix.unwrap(); | 312 | let mut fix = diagnostic.fix.unwrap(); |
303 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; | 313 | let edit = fix.source_change.source_file_edits.pop().unwrap().edit; |
304 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); | 314 | let target_file_contents = analysis.file_text(file_position.file_id).unwrap(); |
@@ -324,7 +334,7 @@ mod tests { | |||
324 | let ra_fixture_after = &trim_indent(ra_fixture_after); | 334 | let ra_fixture_after = &trim_indent(ra_fixture_after); |
325 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); | 335 | let (analysis, file_pos) = analysis_and_position(ra_fixture_before); |
326 | let current_file_id = file_pos.file_id; | 336 | let current_file_id = file_pos.file_id; |
327 | let diagnostic = analysis.diagnostics(current_file_id).unwrap().pop().unwrap(); | 337 | let diagnostic = analysis.diagnostics(current_file_id, true).unwrap().pop().unwrap(); |
328 | let mut fix = diagnostic.fix.unwrap(); | 338 | let mut fix = diagnostic.fix.unwrap(); |
329 | let edit = fix.source_change.source_file_edits.pop().unwrap(); | 339 | let edit = fix.source_change.source_file_edits.pop().unwrap(); |
330 | let changed_file_id = edit.file_id; | 340 | let changed_file_id = edit.file_id; |
@@ -345,14 +355,14 @@ mod tests { | |||
345 | let analysis = mock.analysis(); | 355 | let analysis = mock.analysis(); |
346 | let diagnostics = files | 356 | let diagnostics = files |
347 | .into_iter() | 357 | .into_iter() |
348 | .flat_map(|file_id| analysis.diagnostics(file_id).unwrap()) | 358 | .flat_map(|file_id| analysis.diagnostics(file_id, true).unwrap()) |
349 | .collect::<Vec<_>>(); | 359 | .collect::<Vec<_>>(); |
350 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); | 360 | assert_eq!(diagnostics.len(), 0, "unexpected diagnostics:\n{:#?}", diagnostics); |
351 | } | 361 | } |
352 | 362 | ||
353 | fn check_expect(ra_fixture: &str, expect: Expect) { | 363 | fn check_expect(ra_fixture: &str, expect: Expect) { |
354 | let (analysis, file_id) = single_file(ra_fixture); | 364 | let (analysis, file_id) = single_file(ra_fixture); |
355 | let diagnostics = analysis.diagnostics(file_id).unwrap(); | 365 | let diagnostics = analysis.diagnostics(file_id, true).unwrap(); |
356 | expect.assert_debug_eq(&diagnostics) | 366 | expect.assert_debug_eq(&diagnostics) |
357 | } | 367 | } |
358 | 368 | ||
diff --git a/crates/ra_ide/src/lib.rs b/crates/ra_ide/src/lib.rs index dc9192d42..4c4d9f6fa 100644 --- a/crates/ra_ide/src/lib.rs +++ b/crates/ra_ide/src/lib.rs | |||
@@ -487,8 +487,12 @@ impl Analysis { | |||
487 | } | 487 | } |
488 | 488 | ||
489 | /// Computes the set of diagnostics for the given file. | 489 | /// Computes the set of diagnostics for the given file. |
490 | pub fn diagnostics(&self, file_id: FileId) -> Cancelable<Vec<Diagnostic>> { | 490 | pub fn diagnostics( |
491 | self.with_db(|db| diagnostics::diagnostics(db, file_id)) | 491 | &self, |
492 | file_id: FileId, | ||
493 | enable_experimental: bool, | ||
494 | ) -> Cancelable<Vec<Diagnostic>> { | ||
495 | self.with_db(|db| diagnostics::diagnostics(db, file_id, enable_experimental)) | ||
492 | } | 496 | } |
493 | 497 | ||
494 | /// Returns the edit required to rename reference at the position to the new | 498 | /// Returns the edit required to rename reference at the position to the new |
@@ -505,9 +509,10 @@ impl Analysis { | |||
505 | &self, | 509 | &self, |
506 | query: &str, | 510 | query: &str, |
507 | parse_only: bool, | 511 | parse_only: bool, |
512 | position: FilePosition, | ||
508 | ) -> Cancelable<Result<SourceChange, SsrError>> { | 513 | ) -> Cancelable<Result<SourceChange, SsrError>> { |
509 | self.with_db(|db| { | 514 | self.with_db(|db| { |
510 | let edits = ssr::parse_search_replace(query, parse_only, db)?; | 515 | let edits = ssr::parse_search_replace(query, parse_only, db, position)?; |
511 | Ok(SourceChange::from(edits)) | 516 | Ok(SourceChange::from(edits)) |
512 | }) | 517 | }) |
513 | } | 518 | } |
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs index b3e9e5dfe..95d8f79b8 100644 --- a/crates/ra_ide/src/ssr.rs +++ b/crates/ra_ide/src/ssr.rs | |||
@@ -1,5 +1,5 @@ | |||
1 | use ra_db::SourceDatabaseExt; | 1 | use ra_db::FilePosition; |
2 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | 2 | use ra_ide_db::RootDatabase; |
3 | 3 | ||
4 | use crate::SourceFileEdit; | 4 | use crate::SourceFileEdit; |
5 | use ra_ssr::{MatchFinder, SsrError, SsrRule}; | 5 | use ra_ssr::{MatchFinder, SsrError, SsrRule}; |
@@ -11,6 +11,19 @@ use ra_ssr::{MatchFinder, SsrError, SsrRule}; | |||
11 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. | 11 | // A `$<name>` placeholder in the search pattern will match any AST node and `$<name>` will reference it in the replacement. |
12 | // Within a macro call, a placeholder will match up until whatever token follows the placeholder. | 12 | // Within a macro call, a placeholder will match up until whatever token follows the placeholder. |
13 | // | 13 | // |
14 | // All paths in both the search pattern and the replacement template must resolve in the context | ||
15 | // in which this command is invoked. Paths in the search pattern will then match the code if they | ||
16 | // resolve to the same item, even if they're written differently. For example if we invoke the | ||
17 | // command in the module `foo` with a pattern of `Bar`, then code in the parent module that refers | ||
18 | // to `foo::Bar` will match. | ||
19 | // | ||
20 | // Paths in the replacement template will be rendered appropriately for the context in which the | ||
21 | // replacement occurs. For example if our replacement template is `foo::Bar` and we match some | ||
22 | // code in the `foo` module, we'll insert just `Bar`. | ||
23 | // | ||
24 | // Method calls should generally be written in UFCS form. e.g. `foo::Bar::baz($s, $a)` will match | ||
25 | // `$s.baz($a)`, provided the method call `baz` resolves to the method `foo::Bar::baz`. | ||
26 | // | ||
14 | // Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`. | 27 | // Placeholders may be given constraints by writing them as `${<name>:<constraint1>:<constraint2>...}`. |
15 | // | 28 | // |
16 | // Supported constraints: | 29 | // Supported constraints: |
@@ -43,21 +56,13 @@ pub fn parse_search_replace( | |||
43 | rule: &str, | 56 | rule: &str, |
44 | parse_only: bool, | 57 | parse_only: bool, |
45 | db: &RootDatabase, | 58 | db: &RootDatabase, |
59 | position: FilePosition, | ||
46 | ) -> Result<Vec<SourceFileEdit>, SsrError> { | 60 | ) -> Result<Vec<SourceFileEdit>, SsrError> { |
47 | let mut edits = vec![]; | ||
48 | let rule: SsrRule = rule.parse()?; | 61 | let rule: SsrRule = rule.parse()?; |
62 | let mut match_finder = MatchFinder::in_context(db, position); | ||
63 | match_finder.add_rule(rule)?; | ||
49 | if parse_only { | 64 | if parse_only { |
50 | return Ok(edits); | 65 | return Ok(Vec::new()); |
51 | } | ||
52 | let mut match_finder = MatchFinder::new(db); | ||
53 | match_finder.add_rule(rule); | ||
54 | for &root in db.local_roots().iter() { | ||
55 | let sr = db.source_root(root); | ||
56 | for file_id in sr.iter() { | ||
57 | if let Some(edit) = match_finder.edits_for_file(file_id) { | ||
58 | edits.push(SourceFileEdit { file_id, edit }); | ||
59 | } | ||
60 | } | ||
61 | } | 66 | } |
62 | Ok(edits) | 67 | Ok(match_finder.edits()) |
63 | } | 68 | } |