diff --git a/Cargo.lock b/Cargo.lock index b19d697f5f7..cf0479f0b7f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1464,10 +1464,12 @@ dependencies = [ "ra_hir_def", "ra_hir_ty", "ra_ide", + "ra_ide_db", "ra_mbe", "ra_proc_macro_srv", "ra_prof", "ra_project_model", + "ra_ssr", "ra_syntax", "ra_text_edit", "ra_toolchain", diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 53621fb444d..97d9bf5d4df 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml @@ -34,8 +34,10 @@ stdx = { path = "../stdx" } lsp-server = "0.3.3" flycheck = { path = "../flycheck" } ra_ide = { path = "../ra_ide" } +ra_ide_db = { path = "../ra_ide_db" } ra_prof = { path = "../ra_prof" } ra_project_model = { path = "../ra_project_model" } +ra_ssr = { path = "../ra_ssr" } ra_syntax = { path = "../ra_syntax" } ra_text_edit = { path = "../ra_text_edit" } vfs = { path = "../vfs" } diff --git a/crates/rust-analyzer/src/bin/args.rs b/crates/rust-analyzer/src/bin/args.rs index 8e3ca9343eb..3f0bb3865f6 100644 --- a/crates/rust-analyzer/src/bin/args.rs +++ b/crates/rust-analyzer/src/bin/args.rs @@ -5,6 +5,7 @@ use anyhow::{bail, Result}; use pico_args::Arguments; +use ra_ssr::SsrRule; use rust_analyzer::cli::{BenchWhat, Position, Verbosity}; use std::{fmt::Write, path::PathBuf}; @@ -45,6 +46,9 @@ pub(crate) enum Command { /// this would include the parser test files. all: bool, }, + Ssr { + rules: Vec, + }, ProcMacro, RunServer, Version, @@ -270,6 +274,32 @@ pub(crate) fn parse() -> Result> { Command::Diagnostics { path, load_output_dirs, with_proc_macro, all } } "proc-macro" => Command::ProcMacro, + "ssr" => { + if matches.contains(["-h", "--help"]) { + eprintln!( + "\ +rust-analyzer ssr + +USAGE: + rust-analyzer ssr [FLAGS] [RULE...] + +EXAMPLE: + rust-analyzer ssr '$a.foo($b) ==> bar($a, $b)' + +FLAGS: + -h, --help Prints help information + +ARGS: + A structured search replace rule" + ); + return Ok(Err(HelpPrinted)); + } + let mut rules = Vec::new(); + while let Some(rule) = matches.free_from_str()? { + rules.push(rule); + } + Command::Ssr { rules } + } _ => { print_subcommands(); return Ok(Err(HelpPrinted)); @@ -297,6 +327,7 @@ fn print_subcommands() { diagnostics proc-macro parse + ssr symbols" ) } diff --git a/crates/rust-analyzer/src/bin/main.rs b/crates/rust-analyzer/src/bin/main.rs index 45204d1a35a..16882fc1376 100644 --- a/crates/rust-analyzer/src/bin/main.rs +++ b/crates/rust-analyzer/src/bin/main.rs @@ -60,6 +60,9 @@ fn main() -> Result<()> { args::Command::Diagnostics { path, load_output_dirs, with_proc_macro, all } => { cli::diagnostics(path.as_ref(), load_output_dirs, with_proc_macro, all)? } + args::Command::Ssr { rules } => { + cli::apply_ssr_rules(rules)?; + } args::Command::Version => println!("rust-analyzer {}", env!("REV")), } Ok(()) diff --git a/crates/rust-analyzer/src/cli.rs b/crates/rust-analyzer/src/cli.rs index c5faec83a98..13e3d75be11 100644 --- a/crates/rust-analyzer/src/cli.rs +++ b/crates/rust-analyzer/src/cli.rs @@ -5,6 +5,7 @@ mod analysis_bench; mod diagnostics; mod progress_report; +mod ssr; use std::io::Read; @@ -17,6 +18,7 @@ pub use analysis_stats::analysis_stats; pub use diagnostics::diagnostics; pub use load_cargo::load_cargo; +pub use ssr::apply_ssr_rules; #[derive(Clone, Copy)] pub enum Verbosity { diff --git a/crates/rust-analyzer/src/cli/ssr.rs b/crates/rust-analyzer/src/cli/ssr.rs new file mode 100644 index 00000000000..a5265ac1587 --- /dev/null +++ b/crates/rust-analyzer/src/cli/ssr.rs @@ -0,0 +1,33 @@ +//! 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, 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 }); + } + } + } + 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(); + edit.edit.apply(&mut contents); + std::fs::write(path, contents)?; + } + } + Ok(()) +}