mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-27 05:23:24 +00:00
add apply ssr assist
This commit is contained in:
parent
a9b1e5cde1
commit
09307be75b
4 changed files with 300 additions and 1 deletions
|
@ -41,6 +41,7 @@ mod parent_module;
|
|||
mod references;
|
||||
mod fn_references;
|
||||
mod runnables;
|
||||
mod ssr;
|
||||
mod status;
|
||||
mod syntax_highlighting;
|
||||
mod syntax_tree;
|
||||
|
@ -51,6 +52,7 @@ mod doc_links;
|
|||
use std::sync::Arc;
|
||||
|
||||
use cfg::CfgOptions;
|
||||
|
||||
use ide_db::base_db::{
|
||||
salsa::{self, ParallelDatabase},
|
||||
CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
|
||||
|
@ -502,7 +504,11 @@ impl Analysis {
|
|||
resolve: bool,
|
||||
frange: FileRange,
|
||||
) -> 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.
|
||||
|
|
259
crates/ide/src/ssr.rs
Normal file
259
crates/ide/src/ssr.rs
Normal 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);
|
||||
}
|
||||
}
|
32
crates/ide_ssr/src/from_comment.rs
Normal file
32
crates/ide_ssr/src/from_comment.rs
Normal 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()))
|
||||
}
|
|
@ -58,6 +58,7 @@
|
|||
// | VS Code | **Rust Analyzer: Structural Search Replace**
|
||||
// |===
|
||||
|
||||
mod from_comment;
|
||||
mod matching;
|
||||
mod nester;
|
||||
mod parsing;
|
||||
|
@ -71,6 +72,7 @@ mod tests;
|
|||
|
||||
use crate::errors::bail;
|
||||
pub use crate::errors::SsrError;
|
||||
pub use crate::from_comment::ssr_from_comment;
|
||||
pub use crate::matching::Match;
|
||||
use crate::matching::MatchFailureReason;
|
||||
use hir::Semantics;
|
||||
|
|
Loading…
Reference in a new issue