Skip to content

Commit 09307be

Browse files
committed
add apply ssr assist
1 parent a9b1e5c commit 09307be

File tree

4 files changed

+300
-1
lines changed

4 files changed

+300
-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,
@@ -502,7 +504,11 @@ impl Analysis {
502504
resolve: bool,
503505
frange: FileRange,
504506
) -> Cancelable<Vec<Assist>> {
505-
self.with_db(|db| Assist::get(db, config, resolve, frange))
507+
self.with_db(|db| {
508+
let mut acc = Assist::get(db, config, resolve, frange);
509+
ssr::add_ssr_assist(db, &mut acc, resolve, frange);
510+
acc
511+
})
506512
}
507513

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

crates/ide/src/ssr.rs

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

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)