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.
This commit is contained in:
David Lattimore 2020-07-22 16:23:43 +10:00
parent 13f901f636
commit a45682ed96
6 changed files with 92 additions and 89 deletions

View File

@ -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<Vec<SourceFileEdit>, 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())
}

View File

@ -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<SourceFileEdit> {
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<TextEdit> {
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

View File

@ -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); }");
}
}

View File

@ -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<Match>) {
// 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<Match>) {
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<FileRange>,

View File

@ -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<String> = match_finder
.find_matches_in_file(file_id)
.flattened()
.matches
.iter()
.map(|m| m.matched_text())
.collect();
let matched_strings: Vec<String> =
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);

View File

@ -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<SsrRule>) -> 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<SsrRule>) -> Result<()> {
pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> 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(())
}