aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_ide/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_ide/src')
-rw-r--r--crates/ra_ide/src/diagnostics.rs156
-rw-r--r--crates/ra_ide/src/lib.rs11
-rw-r--r--crates/ra_ide/src/ssr.rs35
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 @@
7use std::cell::RefCell; 7use std::cell::RefCell;
8 8
9use hir::{ 9use 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};
13use itertools::Itertools; 13use itertools::Itertools;
@@ -29,7 +29,11 @@ pub enum Severity {
29 WeakWarning, 29 WeakWarning,
30} 30}
31 31
32pub(crate) fn diagnostics(db: &RootDatabase, file_id: FileId) -> Vec<Diagnostic> { 32pub(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 @@
1use ra_db::SourceDatabaseExt; 1use ra_db::FilePosition;
2use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; 2use ra_ide_db::RootDatabase;
3 3
4use crate::SourceFileEdit; 4use crate::SourceFileEdit;
5use ra_ssr::{MatchFinder, SsrError, SsrRule}; 5use 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}