diff options
-rw-r--r-- | crates/ide/src/lib.rs | 8 | ||||
-rw-r--r-- | crates/ide/src/ssr.rs | 259 | ||||
-rw-r--r-- | crates/ide_ssr/src/from_comment.rs | 32 | ||||
-rw-r--r-- | crates/ide_ssr/src/lib.rs | 2 |
4 files changed, 300 insertions, 1 deletions
diff --git a/crates/ide/src/lib.rs b/crates/ide/src/lib.rs index f83ed65d5..d1a250d48 100644 --- a/crates/ide/src/lib.rs +++ b/crates/ide/src/lib.rs | |||
@@ -41,6 +41,7 @@ mod parent_module; | |||
41 | mod references; | 41 | mod references; |
42 | mod fn_references; | 42 | mod fn_references; |
43 | mod runnables; | 43 | mod runnables; |
44 | mod ssr; | ||
44 | mod status; | 45 | mod status; |
45 | mod syntax_highlighting; | 46 | mod syntax_highlighting; |
46 | mod syntax_tree; | 47 | mod syntax_tree; |
@@ -51,6 +52,7 @@ mod doc_links; | |||
51 | use std::sync::Arc; | 52 | use std::sync::Arc; |
52 | 53 | ||
53 | use cfg::CfgOptions; | 54 | use cfg::CfgOptions; |
55 | |||
54 | use ide_db::base_db::{ | 56 | use ide_db::base_db::{ |
55 | salsa::{self, ParallelDatabase}, | 57 | salsa::{self, ParallelDatabase}, |
56 | CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, | 58 | CheckCanceled, Env, FileLoader, FileSet, SourceDatabase, VfsPath, |
@@ -502,7 +504,11 @@ impl Analysis { | |||
502 | resolve: bool, | 504 | resolve: bool, |
503 | frange: FileRange, | 505 | frange: FileRange, |
504 | ) -> Cancelable<Vec<Assist>> { | 506 | ) -> 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 | }) | ||
506 | } | 512 | } |
507 | 513 | ||
508 | /// Computes the set of diagnostics for the given file. | 514 | /// Computes the set of diagnostics for the given file. |
diff --git a/crates/ide/src/ssr.rs b/crates/ide/src/ssr.rs new file mode 100644 index 000000000..f3638d928 --- /dev/null +++ b/crates/ide/src/ssr.rs | |||
@@ -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 | } | ||
diff --git a/crates/ide_ssr/src/from_comment.rs b/crates/ide_ssr/src/from_comment.rs new file mode 100644 index 000000000..f1b312284 --- /dev/null +++ b/crates/ide_ssr/src/from_comment.rs | |||
@@ -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 | } | ||
diff --git a/crates/ide_ssr/src/lib.rs b/crates/ide_ssr/src/lib.rs index a97fc8bca..e72c611a3 100644 --- a/crates/ide_ssr/src/lib.rs +++ b/crates/ide_ssr/src/lib.rs | |||
@@ -58,6 +58,7 @@ | |||
58 | // | VS Code | **Rust Analyzer: Structural Search Replace** | 58 | // | VS Code | **Rust Analyzer: Structural Search Replace** |
59 | // |=== | 59 | // |=== |
60 | 60 | ||
61 | mod from_comment; | ||
61 | mod matching; | 62 | mod matching; |
62 | mod nester; | 63 | mod nester; |
63 | mod parsing; | 64 | mod parsing; |
@@ -71,6 +72,7 @@ mod tests; | |||
71 | 72 | ||
72 | use crate::errors::bail; | 73 | use crate::errors::bail; |
73 | pub use crate::errors::SsrError; | 74 | pub use crate::errors::SsrError; |
75 | pub use crate::from_comment::ssr_from_comment; | ||
74 | pub use crate::matching::Match; | 76 | pub use crate::matching::Match; |
75 | use crate::matching::MatchFailureReason; | 77 | use crate::matching::MatchFailureReason; |
76 | use hir::Semantics; | 78 | use hir::Semantics; |