diff options
-rw-r--r-- | crates/ra_ide/src/ssr.rs | 16 | ||||
-rw-r--r-- | crates/ra_ssr/src/lib.rs | 48 | ||||
-rw-r--r-- | crates/ra_ssr/src/matching.rs | 15 | ||||
-rw-r--r-- | crates/ra_ssr/src/search.rs | 21 | ||||
-rw-r--r-- | crates/ra_ssr/src/tests.rs | 40 | ||||
-rw-r--r-- | crates/rust-analyzer/src/cli/ssr.rs | 41 |
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 @@ | |||
1 | use ra_db::SourceDatabaseExt; | 1 | use ra_ide_db::RootDatabase; |
2 | use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; | ||
3 | 2 | ||
4 | use crate::SourceFileEdit; | 3 | use crate::SourceFileEdit; |
5 | use ra_ssr::{MatchFinder, SsrError, SsrRule}; | 4 | use 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; | |||
17 | use crate::matching::MatchFailureReason; | 17 | use crate::matching::MatchFailureReason; |
18 | use hir::Semantics; | 18 | use hir::Semantics; |
19 | use ra_db::{FileId, FileRange}; | 19 | use ra_db::{FileId, FileRange}; |
20 | use ra_ide_db::source_change::SourceFileEdit; | ||
20 | use ra_syntax::{ast, AstNode, SyntaxNode, TextRange}; | 21 | use ra_syntax::{ast, AstNode, SyntaxNode, TextRange}; |
21 | use ra_text_edit::TextEdit; | 22 | use 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; | |||
5 | use ra_syntax::{ast, AstNode, SyntaxNode}; | 5 | use ra_syntax::{ast, AstNode, SyntaxNode}; |
6 | 6 | ||
7 | impl<'db> MatchFinder<'db> { | 7 | impl<'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 @@ | |||
1 | use crate::{MatchFinder, SsrRule}; | 1 | use crate::{MatchFinder, SsrRule}; |
2 | use expect::{expect, Expect}; | 2 | use expect::{expect, Expect}; |
3 | use ra_db::{FileId, SourceDatabaseExt}; | 3 | use ra_db::{salsa::Durability, FileId, SourceDatabaseExt}; |
4 | use rustc_hash::FxHashSet; | ||
5 | use std::sync::Arc; | ||
4 | use test_utils::mark; | 6 | use test_utils::mark; |
5 | 7 | ||
6 | fn parse_error_text(query: &str) -> String { | 8 | fn parse_error_text(query: &str) -> String { |
@@ -57,9 +59,15 @@ fn parser_undefined_placeholder_in_replacement() { | |||
57 | ); | 59 | ); |
58 | } | 60 | } |
59 | 61 | ||
60 | fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { | 62 | pub(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 | ||
65 | fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { | 73 | fn 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 | ||
87 | fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) { | 96 | fn 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 | ||
3 | use crate::cli::{load_cargo::load_cargo, Result}; | 3 | use crate::cli::{load_cargo::load_cargo, Result}; |
4 | use ra_ide::SourceFileEdit; | ||
5 | use ra_ssr::{MatchFinder, SsrPattern, SsrRule}; | 4 | use ra_ssr::{MatchFinder, SsrPattern, SsrRule}; |
6 | 5 | ||
7 | pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> { | 6 | pub 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<()> { | |||
38 | pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> { | 28 | pub 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 | } |