From a45682ed96f18f962ac403419b4d143d59ba5283 Mon Sep 17 00:00:00 2001 From: David Lattimore Date: Wed, 22 Jul 2020 16:23:43 +1000 Subject: Move iteration over all files into the SSR crate The methods `edits_for_file` and `find_matches_in_file` are replaced with just `edits` and `matches`. This simplifies the API a bit, but more importantly it makes it possible in a subsequent commit for SSR to decide to not search all files. --- crates/ra_ide/src/ssr.rs | 16 +++---------- crates/ra_ssr/src/lib.rs | 48 +++++++++++++++++++++---------------- crates/ra_ssr/src/matching.rs | 15 ++++++------ crates/ra_ssr/src/search.rs | 21 +++++++++++++++- crates/ra_ssr/src/tests.rs | 40 +++++++++++++++++-------------- 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 @@ -use ra_db::SourceDatabaseExt; -use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase}; +use ra_ide_db::RootDatabase; use crate::SourceFileEdit; use ra_ssr::{MatchFinder, SsrError, SsrRule}; @@ -44,20 +43,11 @@ pub fn parse_search_replace( parse_only: bool, db: &RootDatabase, ) -> Result, SsrError> { - let mut edits = vec![]; let rule: SsrRule = rule.parse()?; if parse_only { - return Ok(edits); + return Ok(Vec::new()); } let mut match_finder = MatchFinder::new(db); match_finder.add_rule(rule); - for &root in db.local_roots().iter() { - let sr = db.source_root(root); - for file_id in sr.iter() { - if let Some(edit) = match_finder.edits_for_file(file_id) { - edits.push(SourceFileEdit { file_id, edit }); - } - } - } - Ok(edits) + Ok(match_finder.edits()) } 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; use crate::matching::MatchFailureReason; use hir::Semantics; use ra_db::{FileId, FileRange}; +use ra_ide_db::source_change::SourceFileEdit; use ra_syntax::{ast, AstNode, SyntaxNode, TextRange}; -use ra_text_edit::TextEdit; +use rustc_hash::FxHashMap; // A structured search replace rule. Create by calling `parse` on a str. #[derive(Debug)] @@ -60,32 +61,37 @@ impl<'db> MatchFinder<'db> { self.add_parsed_rules(rule.parsed_rules); } + /// Finds matches for all added rules and returns edits for all found matches. + pub fn edits(&self) -> Vec { + use ra_db::SourceDatabaseExt; + let mut matches_by_file = FxHashMap::default(); + for m in self.matches().matches { + matches_by_file + .entry(m.range.file_id) + .or_insert_with(|| SsrMatches::default()) + .matches + .push(m); + } + let mut edits = vec![]; + for (file_id, matches) in matches_by_file { + let edit = + replacing::matches_to_edit(&matches, &self.sema.db.file_text(file_id), &self.rules); + edits.push(SourceFileEdit { file_id, edit }); + } + edits + } + /// Adds a search pattern. For use if you intend to only call `find_matches_in_file`. If you /// intend to do replacement, use `add_rule` instead. pub fn add_search_pattern(&mut self, pattern: SsrPattern) { self.add_parsed_rules(pattern.parsed_rules); } - pub fn edits_for_file(&self, file_id: FileId) -> Option { - let matches = self.find_matches_in_file(file_id); - if matches.matches.is_empty() { - None - } else { - use ra_db::SourceDatabaseExt; - Some(replacing::matches_to_edit( - &matches, - &self.sema.db.file_text(file_id), - &self.rules, - )) - } - } - - pub fn find_matches_in_file(&self, file_id: FileId) -> SsrMatches { - let file = self.sema.parse(file_id); - let code = file.syntax(); - let mut matches = SsrMatches::default(); - self.slow_scan_node(code, &None, &mut matches.matches); - matches + /// Returns matches for all added rules. + pub fn matches(&self) -> SsrMatches { + let mut matches = Vec::new(); + self.find_all_matches(&mut matches); + SsrMatches { matches } } /// 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 { #[test] fn parse_match_replace() { let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap(); - let input = "fn foo() {} fn main() { foo(1+2); }"; + let input = "fn foo() {} fn bar() {} fn main() { foo(1+2); }"; - use ra_db::fixture::WithFixture; - let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input); + let (db, _) = crate::tests::single_file(input); let mut match_finder = MatchFinder::new(&db); match_finder.add_rule(rule); - let matches = match_finder.find_matches_in_file(file_id); + let matches = match_finder.matches(); assert_eq!(matches.matches.len(), 1); assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)"); assert_eq!(matches.matches[0].placeholder_values.len(), 1); @@ -589,9 +588,11 @@ mod tests { "1+2" ); - let edit = crate::replacing::matches_to_edit(&matches, input, &match_finder.rules); + let edits = match_finder.edits(); + assert_eq!(edits.len(), 1); + let edit = &edits[0]; let mut after = input.to_string(); - edit.apply(&mut after); - assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }"); + edit.edit.apply(&mut after); + assert_eq!(after, "fn foo() {} fn bar() {} fn main() { bar(1+2); }"); } } 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; use ra_syntax::{ast, AstNode, SyntaxNode}; impl<'db> MatchFinder<'db> { - pub(crate) fn slow_scan_node( + pub(crate) fn find_all_matches(&self, matches_out: &mut Vec) { + // FIXME: Use resolved paths in the pattern to find places to search instead of always + // scanning every node. + self.slow_scan(matches_out); + } + + fn slow_scan(&self, matches_out: &mut Vec) { + use ra_db::SourceDatabaseExt; + use ra_ide_db::symbol_index::SymbolsDatabase; + for &root in self.sema.db.local_roots().iter() { + let sr = self.sema.db.source_root(root); + for file_id in sr.iter() { + let file = self.sema.parse(file_id); + let code = file.syntax(); + self.slow_scan_node(code, &None, matches_out); + } + } + } + + fn slow_scan_node( &self, code: &SyntaxNode, restrict_range: &Option, 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 @@ use crate::{MatchFinder, SsrRule}; use expect::{expect, Expect}; -use ra_db::{FileId, SourceDatabaseExt}; +use ra_db::{salsa::Durability, FileId, SourceDatabaseExt}; +use rustc_hash::FxHashSet; +use std::sync::Arc; use test_utils::mark; fn parse_error_text(query: &str) -> String { @@ -57,9 +59,15 @@ fn parser_undefined_placeholder_in_replacement() { ); } -fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { +pub(crate) fn single_file(code: &str) -> (ra_ide_db::RootDatabase, FileId) { use ra_db::fixture::WithFixture; - ra_ide_db::RootDatabase::with_single_file(code) + use ra_ide_db::symbol_index::SymbolsDatabase; + let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(code); + let mut db = db; + let mut local_roots = FxHashSet::default(); + local_roots.insert(ra_db::fixture::WORKSPACE); + db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH); + (db, file_id) } fn assert_ssr_transform(rule: &str, input: &str, expected: Expect) { @@ -73,15 +81,16 @@ fn assert_ssr_transforms(rules: &[&str], input: &str, expected: Expect) { let rule: SsrRule = rule.parse().unwrap(); match_finder.add_rule(rule); } - if let Some(edits) = match_finder.edits_for_file(file_id) { - // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters - // stuff. - let mut actual = db.file_text(file_id).to_string(); - edits.apply(&mut actual); - expected.assert_eq(&actual); - } else { + let edits = match_finder.edits(); + if edits.is_empty() { panic!("No edits were made"); } + assert_eq!(edits[0].file_id, file_id); + // Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters + // stuff. + let mut actual = db.file_text(file_id).to_string(); + edits[0].edit.apply(&mut actual); + expected.assert_eq(&actual); } 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]) { let (db, file_id) = single_file(code); let mut match_finder = MatchFinder::new(&db); match_finder.add_search_pattern(pattern.parse().unwrap()); - let matched_strings: Vec = match_finder - .find_matches_in_file(file_id) - .flattened() - .matches - .iter() - .map(|m| m.matched_text()) - .collect(); + let matched_strings: Vec = + match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect(); if matched_strings != expected && !expected.is_empty() { print_match_debug_info(&match_finder, file_id, &expected[0]); } @@ -117,7 +121,7 @@ fn assert_no_match(pattern: &str, code: &str) { let (db, file_id) = single_file(code); let mut match_finder = MatchFinder::new(&db); match_finder.add_search_pattern(pattern.parse().unwrap()); - let matches = match_finder.find_matches_in_file(file_id).flattened().matches; + let matches = match_finder.matches().flattened().matches; if !matches.is_empty() { print_match_debug_info(&match_finder, file_id, &matches[0].matched_text()); 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 @@ //! Applies structured search replace rules from the command line. use crate::cli::{load_cargo::load_cargo, Result}; -use ra_ide::SourceFileEdit; use ra_ssr::{MatchFinder, SsrPattern, SsrRule}; pub fn apply_ssr_rules(rules: Vec) -> Result<()> { use ra_db::SourceDatabaseExt; - use ra_ide_db::symbol_index::SymbolsDatabase; let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?; let db = host.raw_database(); let mut match_finder = MatchFinder::new(db); for rule in rules { match_finder.add_rule(rule); } - let mut edits = Vec::new(); - for &root in db.local_roots().iter() { - let sr = db.source_root(root); - for file_id in sr.iter() { - if let Some(edit) = match_finder.edits_for_file(file_id) { - edits.push(SourceFileEdit { file_id, edit }); - } - } - } + let edits = match_finder.edits(); for edit in edits { if let Some(path) = vfs.file_path(edit.file_id).as_path() { let mut contents = db.file_text(edit.file_id).to_string(); @@ -38,34 +28,27 @@ pub fn apply_ssr_rules(rules: Vec) -> Result<()> { pub fn search_for_patterns(patterns: Vec, debug_snippet: Option) -> Result<()> { use ra_db::SourceDatabaseExt; use ra_ide_db::symbol_index::SymbolsDatabase; - let (host, vfs) = load_cargo(&std::env::current_dir()?, true, true)?; + let (host, _vfs) = load_cargo(&std::env::current_dir()?, true, true)?; let db = host.raw_database(); let mut match_finder = MatchFinder::new(db); for pattern in patterns { match_finder.add_search_pattern(pattern); } - for &root in db.local_roots().iter() { - let sr = db.source_root(root); - for file_id in sr.iter() { - if let Some(debug_snippet) = &debug_snippet { + if let Some(debug_snippet) = &debug_snippet { + for &root in db.local_roots().iter() { + let sr = db.source_root(root); + for file_id in sr.iter() { for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) { println!("{:#?}", debug_info); } - } else { - let matches = match_finder.find_matches_in_file(file_id); - if !matches.matches.is_empty() { - let matches = matches.flattened().matches; - if let Some(path) = vfs.file_path(file_id).as_path() { - println!("{} matches in '{}'", matches.len(), path.to_string_lossy()); - } - // We could possibly at some point do something more useful than just printing - // the matched text. For now though, that's the easiest thing to do. - for m in matches { - println!("{}", m.matched_text()); - } - } } } + } else { + for m in match_finder.matches().flattened().matches { + // We could possibly at some point do something more useful than just printing + // the matched text. For now though, that's the easiest thing to do. + println!("{}", m.matched_text()); + } } Ok(()) } -- cgit v1.2.3