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:
parent
13f901f636
commit
a45682ed96
@ -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())
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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); }");
|
||||
}
|
||||
}
|
||||
|
@ -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>,
|
||||
|
@ -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);
|
||||
|
@ -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(())
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user