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::RootDatabase;
|
||||||
use ra_ide_db::{symbol_index::SymbolsDatabase, RootDatabase};
|
|
||||||
|
|
||||||
use crate::SourceFileEdit;
|
use crate::SourceFileEdit;
|
||||||
use ra_ssr::{MatchFinder, SsrError, SsrRule};
|
use ra_ssr::{MatchFinder, SsrError, SsrRule};
|
||||||
@ -44,20 +43,11 @@ pub fn parse_search_replace(
|
|||||||
parse_only: bool,
|
parse_only: bool,
|
||||||
db: &RootDatabase,
|
db: &RootDatabase,
|
||||||
) -> Result<Vec<SourceFileEdit>, SsrError> {
|
) -> Result<Vec<SourceFileEdit>, SsrError> {
|
||||||
let mut edits = vec![];
|
|
||||||
let rule: SsrRule = rule.parse()?;
|
let rule: SsrRule = rule.parse()?;
|
||||||
if parse_only {
|
if parse_only {
|
||||||
return Ok(edits);
|
return Ok(Vec::new());
|
||||||
}
|
}
|
||||||
let mut match_finder = MatchFinder::new(db);
|
let mut match_finder = MatchFinder::new(db);
|
||||||
match_finder.add_rule(rule);
|
match_finder.add_rule(rule);
|
||||||
for &root in db.local_roots().iter() {
|
Ok(match_finder.edits())
|
||||||
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)
|
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,9 @@ pub use crate::matching::Match;
|
|||||||
use crate::matching::MatchFailureReason;
|
use crate::matching::MatchFailureReason;
|
||||||
use hir::Semantics;
|
use hir::Semantics;
|
||||||
use ra_db::{FileId, FileRange};
|
use ra_db::{FileId, FileRange};
|
||||||
|
use ra_ide_db::source_change::SourceFileEdit;
|
||||||
use ra_syntax::{ast, AstNode, SyntaxNode, TextRange};
|
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.
|
// A structured search replace rule. Create by calling `parse` on a str.
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
@ -60,32 +61,37 @@ impl<'db> MatchFinder<'db> {
|
|||||||
self.add_parsed_rules(rule.parsed_rules);
|
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
|
/// 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.
|
/// intend to do replacement, use `add_rule` instead.
|
||||||
pub fn add_search_pattern(&mut self, pattern: SsrPattern) {
|
pub fn add_search_pattern(&mut self, pattern: SsrPattern) {
|
||||||
self.add_parsed_rules(pattern.parsed_rules);
|
self.add_parsed_rules(pattern.parsed_rules);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edits_for_file(&self, file_id: FileId) -> Option<TextEdit> {
|
/// Returns matches for all added rules.
|
||||||
let matches = self.find_matches_in_file(file_id);
|
pub fn matches(&self) -> SsrMatches {
|
||||||
if matches.matches.is_empty() {
|
let mut matches = Vec::new();
|
||||||
None
|
self.find_all_matches(&mut matches);
|
||||||
} else {
|
SsrMatches { matches }
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
|
/// Finds all nodes in `file_id` whose text is exactly equal to `snippet` and attempts to match
|
||||||
|
@ -570,13 +570,12 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn parse_match_replace() {
|
fn parse_match_replace() {
|
||||||
let rule: SsrRule = "foo($x) ==>> bar($x)".parse().unwrap();
|
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, _) = crate::tests::single_file(input);
|
||||||
let (db, file_id) = ra_ide_db::RootDatabase::with_single_file(input);
|
|
||||||
let mut match_finder = MatchFinder::new(&db);
|
let mut match_finder = MatchFinder::new(&db);
|
||||||
match_finder.add_rule(rule);
|
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.len(), 1);
|
||||||
assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
|
assert_eq!(matches.matches[0].matched_node.text(), "foo(1+2)");
|
||||||
assert_eq!(matches.matches[0].placeholder_values.len(), 1);
|
assert_eq!(matches.matches[0].placeholder_values.len(), 1);
|
||||||
@ -589,9 +588,11 @@ mod tests {
|
|||||||
"1+2"
|
"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();
|
let mut after = input.to_string();
|
||||||
edit.apply(&mut after);
|
edit.edit.apply(&mut after);
|
||||||
assert_eq!(after, "fn foo() {} fn main() { bar(1+2); }");
|
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};
|
use ra_syntax::{ast, AstNode, SyntaxNode};
|
||||||
|
|
||||||
impl<'db> MatchFinder<'db> {
|
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,
|
&self,
|
||||||
code: &SyntaxNode,
|
code: &SyntaxNode,
|
||||||
restrict_range: &Option<FileRange>,
|
restrict_range: &Option<FileRange>,
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
use crate::{MatchFinder, SsrRule};
|
use crate::{MatchFinder, SsrRule};
|
||||||
use expect::{expect, Expect};
|
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;
|
use test_utils::mark;
|
||||||
|
|
||||||
fn parse_error_text(query: &str) -> String {
|
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;
|
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) {
|
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();
|
let rule: SsrRule = rule.parse().unwrap();
|
||||||
match_finder.add_rule(rule);
|
match_finder.add_rule(rule);
|
||||||
}
|
}
|
||||||
if let Some(edits) = match_finder.edits_for_file(file_id) {
|
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
|
// Note, db.file_text is not necessarily the same as `input`, since fixture parsing alters
|
||||||
// stuff.
|
// stuff.
|
||||||
let mut actual = db.file_text(file_id).to_string();
|
let mut actual = db.file_text(file_id).to_string();
|
||||||
edits.apply(&mut actual);
|
edits[0].edit.apply(&mut actual);
|
||||||
expected.assert_eq(&actual);
|
expected.assert_eq(&actual);
|
||||||
} else {
|
|
||||||
panic!("No edits were made");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn print_match_debug_info(match_finder: &MatchFinder, file_id: FileId, snippet: &str) {
|
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 (db, file_id) = single_file(code);
|
||||||
let mut match_finder = MatchFinder::new(&db);
|
let mut match_finder = MatchFinder::new(&db);
|
||||||
match_finder.add_search_pattern(pattern.parse().unwrap());
|
match_finder.add_search_pattern(pattern.parse().unwrap());
|
||||||
let matched_strings: Vec<String> = match_finder
|
let matched_strings: Vec<String> =
|
||||||
.find_matches_in_file(file_id)
|
match_finder.matches().flattened().matches.iter().map(|m| m.matched_text()).collect();
|
||||||
.flattened()
|
|
||||||
.matches
|
|
||||||
.iter()
|
|
||||||
.map(|m| m.matched_text())
|
|
||||||
.collect();
|
|
||||||
if matched_strings != expected && !expected.is_empty() {
|
if matched_strings != expected && !expected.is_empty() {
|
||||||
print_match_debug_info(&match_finder, file_id, &expected[0]);
|
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 (db, file_id) = single_file(code);
|
||||||
let mut match_finder = MatchFinder::new(&db);
|
let mut match_finder = MatchFinder::new(&db);
|
||||||
match_finder.add_search_pattern(pattern.parse().unwrap());
|
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() {
|
if !matches.is_empty() {
|
||||||
print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
|
print_match_debug_info(&match_finder, file_id, &matches[0].matched_text());
|
||||||
panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
|
panic!("Got {} matches when we expected none: {:#?}", matches.len(), matches);
|
||||||
|
@ -1,27 +1,17 @@
|
|||||||
//! Applies structured search replace rules from the command line.
|
//! Applies structured search replace rules from the command line.
|
||||||
|
|
||||||
use crate::cli::{load_cargo::load_cargo, Result};
|
use crate::cli::{load_cargo::load_cargo, Result};
|
||||||
use ra_ide::SourceFileEdit;
|
|
||||||
use ra_ssr::{MatchFinder, SsrPattern, SsrRule};
|
use ra_ssr::{MatchFinder, SsrPattern, SsrRule};
|
||||||
|
|
||||||
pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
|
pub fn apply_ssr_rules(rules: Vec<SsrRule>) -> Result<()> {
|
||||||
use ra_db::SourceDatabaseExt;
|
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 db = host.raw_database();
|
||||||
let mut match_finder = MatchFinder::new(db);
|
let mut match_finder = MatchFinder::new(db);
|
||||||
for rule in rules {
|
for rule in rules {
|
||||||
match_finder.add_rule(rule);
|
match_finder.add_rule(rule);
|
||||||
}
|
}
|
||||||
let mut edits = Vec::new();
|
let edits = match_finder.edits();
|
||||||
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 });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for edit in edits {
|
for edit in edits {
|
||||||
if let Some(path) = vfs.file_path(edit.file_id).as_path() {
|
if let Some(path) = vfs.file_path(edit.file_id).as_path() {
|
||||||
let mut contents = db.file_text(edit.file_id).to_string();
|
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<()> {
|
pub fn search_for_patterns(patterns: Vec<SsrPattern>, debug_snippet: Option<String>) -> Result<()> {
|
||||||
use ra_db::SourceDatabaseExt;
|
use ra_db::SourceDatabaseExt;
|
||||||
use ra_ide_db::symbol_index::SymbolsDatabase;
|
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 db = host.raw_database();
|
||||||
let mut match_finder = MatchFinder::new(db);
|
let mut match_finder = MatchFinder::new(db);
|
||||||
for pattern in patterns {
|
for pattern in patterns {
|
||||||
match_finder.add_search_pattern(pattern);
|
match_finder.add_search_pattern(pattern);
|
||||||
}
|
}
|
||||||
|
if let Some(debug_snippet) = &debug_snippet {
|
||||||
for &root in db.local_roots().iter() {
|
for &root in db.local_roots().iter() {
|
||||||
let sr = db.source_root(root);
|
let sr = db.source_root(root);
|
||||||
for file_id in sr.iter() {
|
for file_id in sr.iter() {
|
||||||
if let Some(debug_snippet) = &debug_snippet {
|
|
||||||
for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) {
|
for debug_info in match_finder.debug_where_text_equal(file_id, debug_snippet) {
|
||||||
println!("{:#?}", debug_info);
|
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());
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for m in match_finder.matches().flattened().matches {
|
||||||
// We could possibly at some point do something more useful than just printing
|
// 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.
|
// the matched text. For now though, that's the easiest thing to do.
|
||||||
for m in matches {
|
|
||||||
println!("{}", m.matched_text());
|
println!("{}", m.matched_text());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user