add apply ssr assist

This commit is contained in:
Josh Mcguigan 2021-03-08 21:11:28 -08:00
parent a9b1e5cde1
commit 09307be75b
4 changed files with 300 additions and 1 deletions

View file

@ -41,6 +41,7 @@ mod parent_module;
mod references; mod references;
mod fn_references; mod fn_references;
mod runnables; mod runnables;
mod ssr;
mod status; mod status;
mod syntax_highlighting; mod syntax_highlighting;
mod syntax_tree; mod syntax_tree;
@ -51,6 +52,7 @@ mod doc_links;
use std::sync::Arc; use std::sync::Arc;
use cfg::CfgOptions; use cfg::CfgOptions;
use ide_db::base_db::{ use ide_db::base_db::{
salsa::{self, ParallelDatabase}, salsa::{self, ParallelDatabase},
CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
@ -502,7 +504,11 @@ impl Analysis {
resolve: bool, resolve: bool,
frange: FileRange, frange: FileRange,
) -> Cancelable<Vec<Assist>> { ) -> Cancelable<Vec<Assist>> {
self.with_db(|db| Assist::get(db, config, resolve, frange)) self.with_db(|db| {
let mut acc = Assist::get(db, config, resolve, frange);
ssr::add_ssr_assist(db, &mut acc, resolve, frange);
acc
})
} }
/// Computes the set of diagnostics for the given file. /// Computes the set of diagnostics for the given file.

259
crates/ide/src/ssr.rs Normal file
View file

@ -0,0 +1,259 @@
//! This module provides an SSR assist. It is not desirable to include this
//! assist in ide_assists because that would require the ide_assists crate
//! depend on the ide_ssr crate.
use ide_assists::{Assist, AssistId, AssistKind, GroupLabel};
use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
pub(crate) fn add_ssr_assist(
db: &RootDatabase,
base: &mut Vec<Assist>,
resolve: bool,
frange: FileRange,
) -> Option<()> {
let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?;
let (source_change_for_file, source_change_for_workspace) = if resolve {
let edits = match_finder.edits();
let source_change_for_file = {
let text_edit_for_file = edits.get(&frange.file_id).cloned().unwrap_or_default();
SourceChange::from_text_edit(frange.file_id, text_edit_for_file)
};
let source_change_for_workspace = SourceChange::from(match_finder.edits());
(Some(source_change_for_file), Some(source_change_for_workspace))
} else {
(None, None)
};
let assists = vec![
("Apply SSR in file", source_change_for_file),
("Apply SSR in workspace", source_change_for_workspace),
];
for (label, source_change) in assists.into_iter() {
let assist = Assist {
id: AssistId("ssr", AssistKind::RefactorRewrite),
label: Label::new(label),
group: Some(GroupLabel("Apply SSR".into())),
target: comment_range,
source_change,
};
base.push(assist);
}
Some(())
}
#[cfg(test)]
mod tests {
use std::sync::Arc;
use expect_test::expect;
use ide_assists::Assist;
use ide_db::{
base_db::{fixture::WithFixture, salsa::Durability, FileRange},
symbol_index::SymbolsDatabase,
RootDatabase,
};
use rustc_hash::FxHashSet;
use super::add_ssr_assist;
fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> {
let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
let mut local_roots = FxHashSet::default();
local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
let mut assists = vec![];
add_ssr_assist(
&db,
&mut assists,
resolve,
FileRange { file_id, range: range_or_offset.into() },
);
assists
}
#[test]
fn not_applicable_comment_not_ssr() {
let ra_fixture = r#"
//- /lib.rs
// This is foo $0
fn foo() {}
"#;
let resolve = true;
let assists = get_assists(ra_fixture, resolve);
assert_eq!(0, assists.len());
}
#[test]
fn resolve_edits_true() {
let resolve = true;
let assists = get_assists(
r#"
//- /lib.rs
mod bar;
// 2 ==>> 3$0
fn foo() { 2 }
//- /bar.rs
fn bar() { 2 }
"#,
resolve,
);
assert_eq!(2, assists.len());
let mut assists = assists.into_iter();
let apply_in_file_assist = assists.next().unwrap();
expect![[r#"
Assist {
id: AssistId(
"ssr",
RefactorRewrite,
),
label: "Apply SSR in file",
group: Some(
GroupLabel(
"Apply SSR",
),
),
target: 10..21,
source_change: Some(
SourceChange {
source_file_edits: {
FileId(
0,
): TextEdit {
indels: [
Indel {
insert: "3",
delete: 33..34,
},
],
},
},
file_system_edits: [],
is_snippet: false,
},
),
}
"#]]
.assert_debug_eq(&apply_in_file_assist);
let apply_in_workspace_assist = assists.next().unwrap();
expect![[r#"
Assist {
id: AssistId(
"ssr",
RefactorRewrite,
),
label: "Apply SSR in workspace",
group: Some(
GroupLabel(
"Apply SSR",
),
),
target: 10..21,
source_change: Some(
SourceChange {
source_file_edits: {
FileId(
0,
): TextEdit {
indels: [
Indel {
insert: "3",
delete: 33..34,
},
],
},
FileId(
1,
): TextEdit {
indels: [
Indel {
insert: "3",
delete: 11..12,
},
],
},
},
file_system_edits: [],
is_snippet: false,
},
),
}
"#]]
.assert_debug_eq(&apply_in_workspace_assist);
}
#[test]
fn resolve_edits_false() {
let resolve = false;
let assists = get_assists(
r#"
//- /lib.rs
mod bar;
// 2 ==>> 3$0
fn foo() { 2 }
//- /bar.rs
fn bar() { 2 }
"#,
resolve,
);
assert_eq!(2, assists.len());
let mut assists = assists.into_iter();
let apply_in_file_assist = assists.next().unwrap();
expect![[r#"
Assist {
id: AssistId(
"ssr",
RefactorRewrite,
),
label: "Apply SSR in file",
group: Some(
GroupLabel(
"Apply SSR",
),
),
target: 10..21,
source_change: None,
}
"#]]
.assert_debug_eq(&apply_in_file_assist);
let apply_in_workspace_assist = assists.next().unwrap();
expect![[r#"
Assist {
id: AssistId(
"ssr",
RefactorRewrite,
),
label: "Apply SSR in workspace",
group: Some(
GroupLabel(
"Apply SSR",
),
),
target: 10..21,
source_change: None,
}
"#]]
.assert_debug_eq(&apply_in_workspace_assist);
}
}

View file

@ -0,0 +1,32 @@
//! This module allows building an SSR MatchFinder by parsing the SSR rule
//! from a comment.
use ide_db::{
base_db::{FilePosition, FileRange, SourceDatabase},
RootDatabase,
};
use syntax::{
ast::{self, AstNode, AstToken},
TextRange,
};
use crate::MatchFinder;
/// Attempts to build an SSR MatchFinder from a comment at the given file
/// range. If successful, returns the MatchFinder and a TextRange covering
/// comment.
pub fn ssr_from_comment(db: &RootDatabase, frange: FileRange) -> Option<(MatchFinder, TextRange)> {
let comment = {
let file = db.parse(frange.file_id);
file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast)
}?;
let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap();
let ssr_rule = comment_text_without_prefix.parse().ok()?;
let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
let mut match_finder = MatchFinder::in_context(db, lookup_context, vec![]);
match_finder.add_rule(ssr_rule).ok()?;
Some((match_finder, comment.syntax().text_range()))
}

View file

@ -58,6 +58,7 @@
// | VS Code | **Rust Analyzer: Structural Search Replace** // | VS Code | **Rust Analyzer: Structural Search Replace**
// |=== // |===
mod from_comment;
mod matching; mod matching;
mod nester; mod nester;
mod parsing; mod parsing;
@ -71,6 +72,7 @@ mod tests;
use crate::errors::bail; use crate::errors::bail;
pub use crate::errors::SsrError; pub use crate::errors::SsrError;
pub use crate::from_comment::ssr_from_comment;
pub use crate::matching::Match; pub use crate::matching::Match;
use crate::matching::MatchFailureReason; use crate::matching::MatchFailureReason;
use hir::Semantics; use hir::Semantics;