Skip to content

add apply ssr assist #7874

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Mar 10, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion crates/ide/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ mod parent_module;
mod references;
mod fn_references;
mod runnables;
mod ssr;
mod status;
mod syntax_highlighting;
mod syntax_tree;
Expand All @@ -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,
Expand Down Expand Up @@ -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.
Expand Down
259 changes: 259 additions & 0 deletions crates/ide/src/ssr.rs
Original file line number Diff line number Diff line change
@@ -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 changes: 32 additions & 0 deletions crates/ide_ssr/src/from_comment.rs
Original file line number Diff line number Diff line change
@@ -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()))
}
2 changes: 2 additions & 0 deletions crates/ide_ssr/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@
// | VS Code | **Rust Analyzer: Structural Search Replace**
// |===

mod from_comment;
mod matching;
mod nester;
mod parsing;
Expand All @@ -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;
Expand Down