aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/ra_ide/src/ssr.rs16
-rw-r--r--crates/ra_ssr/src/lib.rs48
-rw-r--r--crates/ra_ssr/src/matching.rs15
-rw-r--r--crates/ra_ssr/src/search.rs21
-rw-r--r--crates/ra_ssr/src/tests.rs40
-rw-r--r--crates/rust-analyzer/src/cli/ssr.rs41
6 files changed, 92 insertions, 89 deletions
diff --git a/crates/ra_ide/src/ssr.rs b/crates/ra_ide/src/ssr.rs
index b3e9e5dfe..ca7e0ad86 100644
--- a/crates/ra_ide/src/ssr.rs
+++ b/crates/ra_ide/src/ssr.rs
@@ -1,5 +1,4 @@
1use ra_db::SourceDatabaseExt; 1use ra_ide_db::RootDatabase;
2use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
3 2
4use crate::SourceFileEdit; 3use crate::SourceFileEdit;
5use ra_ssr::{MatchFinder, SsrError, SsrRule}; 4use ra_ssr::{MatchFinder, SsrError, SsrRule};
@@ -44,20 +43,11 @@ pub fn parse_search_replace(
44 parse_only: bool, 43 parse_only: bool,
45 db: &RootDatabase, 44 db: &RootDatabase,
46) -> Result<Vec<SourceFileEdit>, SsrError> { 45) -> Result<Vec<SourceFileEdit>, SsrError> {
47 let mut edits = vec![];
48 let rule: SsrRule = rule.parse()?; 46 let rule: SsrRule = rule.parse()?;
49 if parse_only { 47 if parse_only {
50 return Ok(edits); 48 return Ok(Vec::new());
51 } 49 }
52 let mut match_finder = MatchFinder::new(db); 50 let mut match_finder = MatchFinder::new(db);
53 match_finder.add_rule(rule); 51 match_finder.add_rule(rule);
54 for &root in db.local_roots().iter() { 52 Ok(match_finder.edits())
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 }
62 Ok(edits)
63} 53}
diff --git a/crates/ra_ssr/src/lib.rs b/crates/ra_ssr/src/lib.rs
index dac73c07c..7b6409806 100644
--- a/crates/ra_ssr/src/lib.rs
+++ b/crates/ra_ssr/src/lib.rs
@@ -17,8 +17,9 @@ pub use crate::matching::Match;
17use crate::matching::MatchFailureReason; 17use crate::matching::MatchFailureReason;
18use hir::Semantics; 18use hir::Semantics;
19use ra_db::{FileId, FileRange}; 19use ra_db::{FileId, FileRange};
20use ra_ide_db::source_change::SourceFileEdit;
20use ra_syntax::{ast, AstNode, SyntaxNode, TextRange}; 21use ra_syntax::{ast, AstNode, SyntaxNode, TextRange};
21use ra_text_edit::TextEdit; 22use rustc_hash::FxHashMap;
22 23
23// A structured search replace rule. Create by calling `parse` on a str. 24// A structured search replace rule. Create by calling `parse` on a str.
24#[derive(Debug)] 25#[derive(Debug)]
@@ -60,32 +61,37 @@ impl<'db> MatchFinder<'db> {
60 self.add_parsed_rules(rule.parsed_rules); 61 self.add_parsed_rules(rule.parsed_rules);
61 } 62 }
62 63
64 /// Finds matches for all added rules and returns edits for all found matches.
65 pub fn edits(&self) -> Vec<SourceFileEdit> {
66 use ra_db::SourceDatabaseExt;
67 let mut matches_by_file = FxHashMap::default();
68 for m in self.matches().matches {
69 matches_by_file
70 .entry(m.range.file_id)
71 .or_insert_with(|| SsrMatches::default())
72 .matches
73 .push(m);
74 }
75 let mut edits = vec![];
76 for (file_id, matches) in matches_by_file {
77 let edit =
78 replacing::matches_to_edit(&matches, &self.sema.db.file_text(file_id), &self.rules);
79 edits.push(SourceFileEdit { file_id, edit });
80 }
81 edits
82 }
83
63 /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you 84 /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you
64 /// intend to do replacement, use `add_rule` instead. 85 /// intend to do replacement, use `add_rule` instead.
65 pub fn add_search_pattern(&mut self, pattern: SsrPattern) { 86 pub fn add_search_pattern(&mut self, pattern: SsrPattern) {
66 self.add_parsed_rules(pattern.parsed_rules); 87 self.add_parsed_rules(pattern.parsed_rules);
67 } 88 }
68 89
69 pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> { 90 /// Returns matches for all added rules.
70 let matches = self.find_matches_in_file(file_id); 91 pub fn matches(&self) -> SsrMatches {
71 if matches.matches.is_empty() { 92 let mut matches = Vec::new();
72 None 93 self.find_all_matches(&mut matches);
73 } else { 94 SsrMatches { matches }
74 use ra_db::SourceDatabaseExt;
75 Some(replacing::matches_to_edit(
76 &matches,
77 &self.sema.db.file_text(file_id),
78 &self.rules,
79 ))
80 }
81 }
82
83 pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches {
84 let file = self.sema.parse(file_id);
85 let code = file.syntax();
86 let mut matches = SsrMatches::default();
87 self.slow_scan_node(code, &None, &mut matches.matches);
88 matches
89 } 95 }
90 96
91 /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match 97 /// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
diff --git a/crates/ra_ssr/src/matching.rs b/crates/ra_ssr/src/matching.rs
index 486191635..064e3a204 100644
--- a/crates/ra_ssr/src/matching.rs
+++ b/crates/ra_ssr/src/matching.rs
@@ -570,13 +570,12 @@ mod tests {
570 #[test] 570 #[test]
571 fn parse_match_replace() { 571 fn parse_match_replace() {
572 let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); 572 let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
573 let input = "fn foo() {} fn main() { foo(1+2); }"; 573 let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }";
574 574
575 use ra_db::fixture::WithFixture; 575 let (db, _) = crate::tests::single_file(input);
576 let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input);
577 let mut match_finder = MatchFinder::new(&db); 576 let mut match_finder = MatchFinder::new(&db);
578 match_finder.add_rule(rule); 577 match_finder.add_rule(rule);
579 let matches = match_finder.find_matches_in_file(file_id); 578 let matches = match_finder.matches();
580 assert_eq!(matches.matches.len(), 1); 579 assert_eq!(matches.matches.len(), 1);
581 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); 580 assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
582 assert_eq!(matches.matches[0].placeholder_values.len(), 1); 581 assert_eq!(matches.matches[0].placeholder_values.len(), 1);
@@ -589,9 +588,11 @@ mod tests {
589 "1+2" 588 "1+2"
590 ); 589 );
591 590
592 let edit = crate::replacing::matches_to_edit(&matches, input, &match_finder.rules); 591 let edits = match_finder.edits();
592 assert_eq!(edits.len(), 1);
593 let edit = &edits[0];
593 let mut after = input.to_string(); 594 let mut after = input.to_string();
594 edit.apply(&mut after); 595 edit.edit.apply(&mut after);
595 assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }"); 596 assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }");
596 } 597 }
597} 598}
diff --git a/crates/ra_ssr/src/search.rs b/crates/ra_ssr/src/search.rs
index 6f21452ac..ec3addcf8 100644
--- a/crates/ra_ssr/src/search.rs
+++ b/crates/ra_ssr/src/search.rs
@@ -5,7 +5,26 @@ use ra_db::FileRange;
5use ra_syntax::{ast, AstNode, SyntaxNode}; 5use ra_syntax::{ast, AstNode, SyntaxNode};
6 6
7impl<'db> MatchFinder<'db> { 7impl<'db> MatchFinder<'db> {
8 pub(crate) fn slow_scan_node( 8 pub(crate) fn find_all_matches(&self, matches_out: &mut Vec<Match>) {
9 // FIXME: Use resolved paths in the pattern to find places to search instead of always
10 // scanning every node.
11 self.slow_scan(matches_out);
12 }
13
14 fn slow_scan(&self, matches_out: &mut Vec<Match>) {
15 use ra_db::SourceDatabaseExt;
16 use ra_ide_db::symbol_index::SymbolsDatabase;
17 for &root in self.sema.db.local_roots().iter() {
18 let sr = self.sema.db.source_root(root);
19 for file_id in sr.iter() {
20 let file = self.sema.parse(file_id);
21 let code = file.syntax();
22 self.slow_scan_node(code, &None, matches_out);
23 }
24 }
25 }
26
27 fn slow_scan_node(
9 &self, 28 &self,
10 code: &SyntaxNode, 29 code: &SyntaxNode,
11 restrict_range: &Option<FileRange>, 30 restrict_range: &Option<FileRange>,
diff --git a/crates/ra_ssr/src/tests.rs b/crates/ra_ssr/src/tests.rs
index 1b03b7f4b..c7c37af2f 100644
--- a/crates/ra_ssr/src/tests.rs
+++ b/crates/ra_ssr/src/tests.rs
@@ -1,6 +1,8 @@
1use crate::{MatchFinder, SsrRule}; 1use crate::{MatchFinder, SsrRule};
2use expect::{expect, Expect}; 2use expect::{expect, Expect};
3use ra_db::{FileId, SourceDatabaseExt}; 3use ra_db::{salsa::Durability, FileId, SourceDatabaseExt};
4use rustc_hash::FxHashSet;
5use std::sync::Arc;
4use test_utils::mark; 6use test_utils::mark;
5 7
6fn parse_error_text(query: &str) -> String { 8fn parse_error_text(query: &str) -> String {
@@ -57,9 +59,15 @@ fn parser_undefined_placeholder_in_replacement() {
57 ); 59 );
58} 60}
59 61
60fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { 62pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) {
61 use ra_db::fixture::WithFixture; 63 use ra_db::fixture::WithFixture;
62 ra_ide_db::RootDatabase::with_single_file(code) 64 use ra_ide_db::symbol_index::SymbolsDatabase;
65 let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code);
66 let mut db = db;
67 let mut local_roots = FxHashSet::default();
68 local_roots.insert(ra_db::fixture::WORKSPACE);
69 db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
70 (db, file_id)
63} 71}
64 72
65fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { 73fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) {
@@ -73,15 +81,16 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) {
73 let rule: SsrRule = rule.parse().unwrap(); 81 let rule: SsrRule = rule.parse().unwrap();
74 match_finder.add_rule(rule); 82 match_finder.add_rule(rule);
75 } 83 }
76 if let Some(edits) = match_finder.edits_for_file(file_id) { 84 let edits = match_finder.edits();
77 // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters 85 if edits.is_empty() {
78 // stuff.
79 let mut actual = db.file_text(file_id).to_string();
80 edits.apply(&mut actual);
81 expected.assert_eq(&actual);
82 } else {
83 panic!("No edits were made"); 86 panic!("No edits were made");
84 } 87 }
88 assert_eq!(edits[0].file_id, file_id);
89 // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
90 // stuff.
91 let mut actual = db.file_text(file_id).to_string();
92 edits[0].edit.apply(&mut actual);
93 expected.assert_eq(&actual);
85} 94}
86 95
87fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) { 96fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) {
@@ -100,13 +109,8 @@ fn assert_matches(pattern: &str, code: &str, expected: &[&str]) {
100 let (db, file_id) = single_file(code); 109 let (db, file_id) = single_file(code);
101 let mut match_finder = MatchFinder::new(&db); 110 let mut match_finder = MatchFinder::new(&db);
102 match_finder.add_search_pattern(pattern.parse().unwrap()); 111 match_finder.add_search_pattern(pattern.parse().unwrap());
103 let matched_strings: Vec<String> = match_finder 112 let matched_strings: Vec<String> =
104 .find_matches_in_file(file_id) 113 match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect();
105 .flattened()
106 .matches
107 .iter()
108 .map(|m| m.matched_text())
109 .collect();
110 if matched_strings != expected && !expected.is_empty() { 114 if matched_strings != expected && !expected.is_empty() {
111 print_match_debug_info(&match_finder, file_id, &expected[0]); 115 print_match_debug_info(&match_finder, file_id, &expected[0]);
112 } 116 }
@@ -117,7 +121,7 @@ fn assert_no_match(pattern: &str, code: &str) {
117 let (db, file_id) = single_file(code); 121 let (db, file_id) = single_file(code);
118 let mut match_finder = MatchFinder::new(&db); 122 let mut match_finder = MatchFinder::new(&db);
119 match_finder.add_search_pattern(pattern.parse().unwrap()); 123 match_finder.add_search_pattern(pattern.parse().unwrap());
120 let matches = match_finder.find_matches_in_file(file_id).flattened().matches; 124 let matches = match_finder.matches().flattened().matches;
121 if !matches.is_empty() { 125 if !matches.is_empty() {
122 print_match_debug_info(&match_finder, file_id, &matches[0].matched_text()); 126 print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
123 panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches); 127 panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs
index 4fb829ea5..014bc70a4 100644
--- a/crates/rust-analyzer/src/cli/ssr.rs
+++ b/crates/rust-analyzer/src/cli/ssr.rs
@@ -1,27 +1,17 @@
1//! Applies structured search replace rules from the command line. 1//! Applies structured search replace rules from the command line.
2 2
3use crate::cli::{load_cargo::load_cargo, Result}; 3use crate::cli::{load_cargo::load_cargo, Result};
4use ra_ide::SourceFileEdit;
5use ra_ssr::{MatchFinder, SsrPattern, SsrRule}; 4use ra_ssr::{MatchFinder, SsrPattern, SsrRule};
6 5
7pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> { 6pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
8 use ra_db::SourceDatabaseExt; 7 use ra_db::SourceDatabaseExt;
9 use ra_ide_db::symbol_index::SymbolsDatabase;
10 let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?; 8 let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
11 let db = host.raw_database(); 9 let db = host.raw_database();
12 let mut match_finder = MatchFinder::new(db); 10 let mut match_finder = MatchFinder::new(db);
13 for rule in rules { 11 for rule in rules {
14 match_finder.add_rule(rule); 12 match_finder.add_rule(rule);
15 } 13 }
16 let mut edits = Vec::new(); 14 let edits = match_finder.edits();
17 for &root in db.local_roots().iter() {
18 let sr = db.source_root(root);
19 for file_id in sr.iter() {
20 if let Some(edit) = match_finder.edits_for_file(file_id) {
21 edits.push(SourceFileEdit { file_id, edit });
22 }
23 }
24 }
25 for edit in edits { 15 for edit in edits {
26 if let Some(path) = vfs.file_path(edit.file_id).as_path() { 16 if let Some(path) = vfs.file_path(edit.file_id).as_path() {
27 let mut contents = db.file_text(edit.file_id).to_string(); 17 let mut contents = db.file_text(edit.file_id).to_string();
@@ -38,34 +28,27 @@ pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
38pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> { 28pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> {
39 use ra_db::SourceDatabaseExt; 29 use ra_db::SourceDatabaseExt;
40 use ra_ide_db::symbol_index::SymbolsDatabase; 30 use ra_ide_db::symbol_index::SymbolsDatabase;
41 let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?; 31 let (host, _vfs) = load_cargo(&std::env::current_dir()?, true, true)?;
42 let db = host.raw_database(); 32 let db = host.raw_database();
43 let mut match_finder = MatchFinder::new(db); 33 let mut match_finder = MatchFinder::new(db);
44 for pattern in patterns { 34 for pattern in patterns {
45 match_finder.add_search_pattern(pattern); 35 match_finder.add_search_pattern(pattern);
46 } 36 }
47 for &root in db.local_roots().iter() { 37 if let Some(debug_snippet) = &debug_snippet {
48 let sr = db.source_root(root); 38 for &root in db.local_roots().iter() {
49 for file_id in sr.iter() { 39 let sr = db.source_root(root);
50 if let Some(debug_snippet) = &debug_snippet { 40 for file_id in sr.iter() {
51 for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) { 41 for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) {
52 println!("{:#?}", debug_info); 42 println!("{:#?}", debug_info);
53 } 43 }
54 } else {
55 let matches = match_finder.find_matches_in_file(file_id);
56 if !matches.matches.is_empty() {
57 let matches = matches.flattened().matches;
58 if let Some(path) = vfs.file_path(file_id).as_path() {
59 println!("{} matches in '{}'", matches.len(), path.to_string_lossy());
60 }
61 // We could possibly at some point do something more useful than just printing
62 // the matched text. For now though, that's the easiest thing to do.
63 for m in matches {
64 println!("{}", m.matched_text());
65 }
66 }
67 } 44 }
68 } 45 }
46 } else {
47 for m in match_finder.matches().flattened().matches {
48 // We could possibly at some point do something more useful than just printing
49 // the matched text. For now though, that's the easiest thing to do.
50 println!("{}", m.matched_text());
51 }
69 } 52 }
70 Ok(()) 53 Ok(())
71} 54}