Skip to content

Commit 9befa07

Browse files
committed
add apply ssr assist
1 parent 3fdf26a commit 9befa07

File tree

4 files changed

+299
-1
lines changed

4 files changed

+299
-1
lines changed

crates/ide/src/lib.rs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ mod parent_module;
4141
mod references;
4242
mod fn_references;
4343
mod runnables;
44+
mod ssr;
4445
mod status;
4546
mod syntax_highlighting;
4647
mod syntax_tree;
@@ -51,6 +52,7 @@ mod doc_links;
5152
use std::sync::Arc;
5253

5354
use cfg::CfgOptions;
55+
5456
use ide_db::base_db::{
5557
salsa::{self, ParallelDatabase},
5658
CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath,
@@ -504,7 +506,11 @@ impl Analysis {
504506
resolve: bool,
505507
frange: FileRange,
506508
) -> Cancelable<Vec<Assist>> {
507-
self.with_db(|db| Assist::get(db, config, resolve, frange))
509+
self.with_db(|db| {
510+
let mut acc = Assist::get(db, config, resolve, frange);
511+
ssr::add_ssr_assist(db, &mut acc, resolve, frange);
512+
acc
513+
})
508514
}
509515

510516
/// Computes the set of diagnostics for the given file.

crates/ide/src/ssr.rs

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
1+
//! This module provides an SSR assist. It is not desirable to include this
2+
//! assist in ide_assists because that would require the ide_assists crate
3+
//! depend on the ide_ssr crate.
4+
5+
use ide_assists::{Assist, AssistId, AssistKind, GroupLabel};
6+
use ide_db::{base_db::FileRange, label::Label, source_change::SourceChange, RootDatabase};
7+
8+
pub(crate) fn add_ssr_assist(
9+
db: &RootDatabase,
10+
base: &mut Vec<Assist>,
11+
resolve: bool,
12+
frange: FileRange,
13+
) -> Option<()> {
14+
let (match_finder, comment_range) = ide_ssr::ssr_from_comment(db, frange)?;
15+
let edits = match_finder.edits();
16+
17+
let (source_change_for_file, source_change_for_workspace) = if resolve {
18+
let source_change_for_file = {
19+
let text_edit_for_file = edits.get(&frange.file_id).cloned().unwrap_or_default();
20+
SourceChange::from_text_edit(frange.file_id, text_edit_for_file)
21+
};
22+
23+
let source_change_for_workspace = SourceChange::from(match_finder.edits());
24+
25+
(Some(source_change_for_file), Some(source_change_for_workspace))
26+
} else {
27+
(None, None)
28+
};
29+
30+
let assists = vec![
31+
("Apply SSR in file", source_change_for_file),
32+
("Apply SSR in workspace", source_change_for_workspace),
33+
];
34+
35+
for (label, source_change) in assists.into_iter() {
36+
let assist = Assist {
37+
id: AssistId("ssr", AssistKind::RefactorRewrite),
38+
label: Label::new(label),
39+
group: Some(GroupLabel("Apply SSR".into())),
40+
target: comment_range,
41+
source_change,
42+
};
43+
44+
base.push(assist);
45+
}
46+
Some(())
47+
}
48+
49+
#[cfg(test)]
50+
mod tests {
51+
use std::sync::Arc;
52+
53+
use expect_test::expect;
54+
use ide_assists::Assist;
55+
use ide_db::{
56+
base_db::{fixture::WithFixture, salsa::Durability, FileRange},
57+
symbol_index::SymbolsDatabase,
58+
RootDatabase,
59+
};
60+
use rustc_hash::FxHashSet;
61+
62+
use super::add_ssr_assist;
63+
64+
fn get_assists(ra_fixture: &str, resolve: bool) -> Vec<Assist> {
65+
let (mut db, file_id, range_or_offset) = RootDatabase::with_range_or_offset(ra_fixture);
66+
let mut local_roots = FxHashSet::default();
67+
local_roots.insert(ide_db::base_db::fixture::WORKSPACE);
68+
db.set_local_roots_with_durability(Arc::new(local_roots), Durability::HIGH);
69+
70+
let mut assists = vec![];
71+
72+
add_ssr_assist(
73+
&db,
74+
&mut assists,
75+
resolve,
76+
FileRange { file_id, range: range_or_offset.into() },
77+
);
78+
79+
assists
80+
}
81+
82+
#[test]
83+
fn not_applicable_comment_not_ssr() {
84+
let ra_fixture = r#"
85+
//- /lib.rs
86+
87+
// This is foo $0
88+
fn foo() {}
89+
"#;
90+
let resolve = true;
91+
92+
let assists = get_assists(ra_fixture, resolve);
93+
94+
assert_eq!(0, assists.len());
95+
}
96+
97+
#[test]
98+
fn resolve_edits_true() {
99+
let resolve = true;
100+
let assists = get_assists(
101+
r#"
102+
//- /lib.rs
103+
mod bar;
104+
105+
// 2 ==>> 3$0
106+
fn foo() { 2 }
107+
108+
//- /bar.rs
109+
fn bar() { 2 }
110+
"#,
111+
resolve,
112+
);
113+
114+
assert_eq!(2, assists.len());
115+
let mut assists = assists.into_iter();
116+
117+
let apply_in_file_assist = assists.next().unwrap();
118+
expect![[r#"
119+
Assist {
120+
id: AssistId(
121+
"ssr",
122+
RefactorRewrite,
123+
),
124+
label: "Apply SSR in file",
125+
group: Some(
126+
GroupLabel(
127+
"Apply SSR",
128+
),
129+
),
130+
target: 10..21,
131+
source_change: Some(
132+
SourceChange {
133+
source_file_edits: {
134+
FileId(
135+
0,
136+
): TextEdit {
137+
indels: [
138+
Indel {
139+
insert: "3",
140+
delete: 33..34,
141+
},
142+
],
143+
},
144+
},
145+
file_system_edits: [],
146+
is_snippet: false,
147+
},
148+
),
149+
}
150+
"#]]
151+
.assert_debug_eq(&apply_in_file_assist);
152+
153+
let apply_in_workspace_assist = assists.next().unwrap();
154+
expect![[r#"
155+
Assist {
156+
id: AssistId(
157+
"ssr",
158+
RefactorRewrite,
159+
),
160+
label: "Apply SSR in workspace",
161+
group: Some(
162+
GroupLabel(
163+
"Apply SSR",
164+
),
165+
),
166+
target: 10..21,
167+
source_change: Some(
168+
SourceChange {
169+
source_file_edits: {
170+
FileId(
171+
0,
172+
): TextEdit {
173+
indels: [
174+
Indel {
175+
insert: "3",
176+
delete: 33..34,
177+
},
178+
],
179+
},
180+
FileId(
181+
1,
182+
): TextEdit {
183+
indels: [
184+
Indel {
185+
insert: "3",
186+
delete: 11..12,
187+
},
188+
],
189+
},
190+
},
191+
file_system_edits: [],
192+
is_snippet: false,
193+
},
194+
),
195+
}
196+
"#]]
197+
.assert_debug_eq(&apply_in_workspace_assist);
198+
}
199+
200+
#[test]
201+
fn resolve_edits_false() {
202+
let resolve = false;
203+
let assists = get_assists(
204+
r#"
205+
//- /lib.rs
206+
mod bar;
207+
208+
// 2 ==>> 3$0
209+
fn foo() { 2 }
210+
211+
//- /bar.rs
212+
fn bar() { 2 }
213+
"#,
214+
resolve,
215+
);
216+
217+
assert_eq!(2, assists.len());
218+
let mut assists = assists.into_iter();
219+
220+
let apply_in_file_assist = assists.next().unwrap();
221+
expect![[r#"
222+
Assist {
223+
id: AssistId(
224+
"ssr",
225+
RefactorRewrite,
226+
),
227+
label: "Apply SSR in file",
228+
group: Some(
229+
GroupLabel(
230+
"Apply SSR",
231+
),
232+
),
233+
target: 10..21,
234+
source_change: None,
235+
}
236+
"#]]
237+
.assert_debug_eq(&apply_in_file_assist);
238+
239+
let apply_in_workspace_assist = assists.next().unwrap();
240+
expect![[r#"
241+
Assist {
242+
id: AssistId(
243+
"ssr",
244+
RefactorRewrite,
245+
),
246+
label: "Apply SSR in workspace",
247+
group: Some(
248+
GroupLabel(
249+
"Apply SSR",
250+
),
251+
),
252+
target: 10..21,
253+
source_change: None,
254+
}
255+
"#]]
256+
.assert_debug_eq(&apply_in_workspace_assist);
257+
}
258+
}

crates/ide_ssr/src/from_comment.rs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
//! This module allows building an SSR MatchFinder by parsing the SSR rule
2+
//! from a comment.
3+
4+
use ide_db::{
5+
base_db::{FilePosition, FileRange, SourceDatabase},
6+
RootDatabase,
7+
};
8+
use syntax::{
9+
ast::{self, AstNode, AstToken},
10+
TextRange,
11+
};
12+
13+
use crate::MatchFinder;
14+
15+
/// Attempts to build an SSR MatchFinder from a comment at the given file
16+
/// range. If successful, returns the MatchFinder and a TextRange covering
17+
/// comment.
18+
pub fn ssr_from_comment(db: &RootDatabase, frange: FileRange) -> Option<(MatchFinder, TextRange)> {
19+
let comment = {
20+
let file = db.parse(frange.file_id);
21+
file.tree().syntax().token_at_offset(frange.range.start()).find_map(ast::Comment::cast)
22+
}?;
23+
let comment_text_without_prefix = comment.text().strip_prefix(comment.prefix()).unwrap();
24+
let ssr_rule = comment_text_without_prefix.parse().ok()?;
25+
26+
let lookup_context = FilePosition { file_id: frange.file_id, offset: frange.range.start() };
27+
28+
let mut match_finder = MatchFinder::in_context(db, lookup_context, vec![]);
29+
match_finder.add_rule(ssr_rule).ok()?;
30+
31+
Some((match_finder, comment.syntax().text_range()))
32+
}

crates/ide_ssr/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@
5858
// | VS Code | **Rust Analyzer: Structural Search Replace**
5959
// |===
6060

61+
mod from_comment;
6162
mod matching;
6263
mod nester;
6364
mod parsing;
@@ -71,6 +72,7 @@ mod tests;
7172

7273
use crate::errors::bail;
7374
pub use crate::errors::SsrError;
75+
pub use crate::from_comment::ssr_from_comment;
7476
pub use crate::matching::Match;
7577
use crate::matching::MatchFailureReason;
7678
use hir::Semantics;

0 commit comments

Comments
 (0)