aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists')
-rw-r--r--crates/ra_assists/Cargo.toml25
-rw-r--r--crates/ra_assists/src/assist_config.rs30
-rw-r--r--crates/ra_assists/src/assist_context.rs302
-rw-r--r--crates/ra_assists/src/ast_transform.rs206
-rw-r--r--crates/ra_assists/src/handlers/add_custom_impl.rs208
-rw-r--r--crates/ra_assists/src/handlers/add_explicit_type.rs211
-rw-r--r--crates/ra_assists/src/handlers/add_missing_impl_members.rs711
-rw-r--r--crates/ra_assists/src/handlers/add_turbo_fish.rs164
-rw-r--r--crates/ra_assists/src/handlers/apply_demorgan.rs93
-rw-r--r--crates/ra_assists/src/handlers/auto_import.rs1089
-rw-r--r--crates/ra_assists/src/handlers/change_return_type_to_result.rs990
-rw-r--r--crates/ra_assists/src/handlers/change_visibility.rs200
-rw-r--r--crates/ra_assists/src/handlers/early_return.rs515
-rw-r--r--crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs321
-rw-r--r--crates/ra_assists/src/handlers/extract_variable.rs588
-rw-r--r--crates/ra_assists/src/handlers/fill_match_arms.rs750
-rw-r--r--crates/ra_assists/src/handlers/fix_visibility.rs607
-rw-r--r--crates/ra_assists/src/handlers/flip_binexpr.rs142
-rw-r--r--crates/ra_assists/src/handlers/flip_comma.rs84
-rw-r--r--crates/ra_assists/src/handlers/flip_trait_bound.rs121
-rw-r--r--crates/ra_assists/src/handlers/generate_derive.rs132
-rw-r--r--crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs200
-rw-r--r--crates/ra_assists/src/handlers/generate_function.rs1058
-rw-r--r--crates/ra_assists/src/handlers/generate_impl.rs109
-rw-r--r--crates/ra_assists/src/handlers/generate_new.rs420
-rw-r--r--crates/ra_assists/src/handlers/inline_local_variable.rs695
-rw-r--r--crates/ra_assists/src/handlers/introduce_named_lifetime.rs318
-rw-r--r--crates/ra_assists/src/handlers/invert_if.rs109
-rw-r--r--crates/ra_assists/src/handlers/merge_imports.rs294
-rw-r--r--crates/ra_assists/src/handlers/merge_match_arms.rs248
-rw-r--r--crates/ra_assists/src/handlers/move_bounds.rs152
-rw-r--r--crates/ra_assists/src/handlers/move_guard.rs303
-rw-r--r--crates/ra_assists/src/handlers/raw_string.rs504
-rw-r--r--crates/ra_assists/src/handlers/remove_dbg.rs205
-rw-r--r--crates/ra_assists/src/handlers/remove_mut.rs37
-rw-r--r--crates/ra_assists/src/handlers/reorder_fields.rs220
-rw-r--r--crates/ra_assists/src/handlers/replace_if_let_with_match.rs255
-rw-r--r--crates/ra_assists/src/handlers/replace_let_with_if_let.rs101
-rw-r--r--crates/ra_assists/src/handlers/replace_qualified_name_with_use.rs646
-rw-r--r--crates/ra_assists/src/handlers/replace_unwrap_with_match.rs187
-rw-r--r--crates/ra_assists/src/handlers/split_import.rs79
-rw-r--r--crates/ra_assists/src/handlers/unwrap_block.rs518
-rw-r--r--crates/ra_assists/src/lib.rs224
-rw-r--r--crates/ra_assists/src/tests.rs179
-rw-r--r--crates/ra_assists/src/tests/generated.rs863
-rw-r--r--crates/ra_assists/src/utils.rs278
-rw-r--r--crates/ra_assists/src/utils/insert_use.rs525
47 files changed, 0 insertions, 16216 deletions
diff --git a/crates/ra_assists/Cargo.toml b/crates/ra_assists/Cargo.toml
deleted file mode 100644
index bd2905f08..000000000
--- a/crates/ra_assists/Cargo.toml
+++ /dev/null
@@ -1,25 +0,0 @@
1[package]
2edition = "2018"
3name = "ra_assists"
4version = "0.1.0"
5authors = ["rust-analyzer developers"]
6license = "MIT OR Apache-2.0"
7
8[lib]
9doctest = false
10
11[dependencies]
12rustc-hash = "1.1.0"
13itertools = "0.9.0"
14either = "1.5.3"
15
16stdx = { path = "../stdx" }
17
18ra_syntax = { path = "../ra_syntax" }
19ra_text_edit = { path = "../ra_text_edit" }
20ra_fmt = { path = "../ra_fmt" }
21ra_prof = { path = "../ra_prof" }
22ra_db = { path = "../ra_db" }
23ra_ide_db = { path = "../ra_ide_db" }
24hir = { path = "../ra_hir", package = "ra_hir" }
25test_utils = { path = "../test_utils" }
diff --git a/crates/ra_assists/src/assist_config.rs b/crates/ra_assists/src/assist_config.rs
deleted file mode 100644
index cda2abfb9..000000000
--- a/crates/ra_assists/src/assist_config.rs
+++ /dev/null
@@ -1,30 +0,0 @@
1//! Settings for tweaking assists.
2//!
3//! The fun thing here is `SnippetCap` -- this type can only be created in this
4//! module, and we use to statically check that we only produce snippet
5//! assists if we are allowed to.
6
7use crate::AssistKind;
8
9#[derive(Clone, Debug, PartialEq, Eq)]
10pub struct AssistConfig {
11 pub snippet_cap: Option<SnippetCap>,
12 pub allowed: Option<Vec<AssistKind>>,
13}
14
15impl AssistConfig {
16 pub fn allow_snippets(&mut self, yes: bool) {
17 self.snippet_cap = if yes { Some(SnippetCap { _private: () }) } else { None }
18 }
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
22pub struct SnippetCap {
23 _private: (),
24}
25
26impl Default for AssistConfig {
27 fn default() -> Self {
28 AssistConfig { snippet_cap: Some(SnippetCap { _private: () }), allowed: None }
29 }
30}
diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs
deleted file mode 100644
index 3407df856..000000000
--- a/crates/ra_assists/src/assist_context.rs
+++ /dev/null
@@ -1,302 +0,0 @@
1//! See `AssistContext`
2
3use std::mem;
4
5use algo::find_covering_element;
6use hir::Semantics;
7use ra_db::{FileId, FileRange};
8use ra_fmt::{leading_indent, reindent};
9use ra_ide_db::{
10 source_change::{SourceChange, SourceFileEdit},
11 RootDatabase,
12};
13use ra_syntax::{
14 algo::{self, find_node_at_offset, SyntaxRewriter},
15 AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, TextSize,
16 TokenAtOffset,
17};
18use ra_text_edit::TextEditBuilder;
19
20use crate::{
21 assist_config::{AssistConfig, SnippetCap},
22 Assist, AssistId, AssistKind, GroupLabel, ResolvedAssist,
23};
24
25/// `AssistContext` allows to apply an assist or check if it could be applied.
26///
27/// Assists use a somewhat over-engineered approach, given the current needs.
28/// The assists workflow consists of two phases. In the first phase, a user asks
29/// for the list of available assists. In the second phase, the user picks a
30/// particular assist and it gets applied.
31///
32/// There are two peculiarities here:
33///
34/// * first, we ideally avoid computing more things then necessary to answer "is
35/// assist applicable" in the first phase.
36/// * second, when we are applying assist, we don't have a guarantee that there
37/// weren't any changes between the point when user asked for assists and when
38/// they applied a particular assist. So, when applying assist, we need to do
39/// all the checks from scratch.
40///
41/// To avoid repeating the same code twice for both "check" and "apply"
42/// functions, we use an approach reminiscent of that of Django's function based
43/// views dealing with forms. Each assist receives a runtime parameter,
44/// `resolve`. It first check if an edit is applicable (potentially computing
45/// info required to compute the actual edit). If it is applicable, and
46/// `resolve` is `true`, it then computes the actual edit.
47///
48/// So, to implement the original assists workflow, we can first apply each edit
49/// with `resolve = false`, and then applying the selected edit again, with
50/// `resolve = true` this time.
51///
52/// Note, however, that we don't actually use such two-phase logic at the
53/// moment, because the LSP API is pretty awkward in this place, and it's much
54/// easier to just compute the edit eagerly :-)
55pub(crate) struct AssistContext<'a> {
56 pub(crate) config: &'a AssistConfig,
57 pub(crate) sema: Semantics<'a, RootDatabase>,
58 pub(crate) frange: FileRange,
59 source_file: SourceFile,
60}
61
62impl<'a> AssistContext<'a> {
63 pub(crate) fn new(
64 sema: Semantics<'a, RootDatabase>,
65 config: &'a AssistConfig,
66 frange: FileRange,
67 ) -> AssistContext<'a> {
68 let source_file = sema.parse(frange.file_id);
69 AssistContext { config, sema, frange, source_file }
70 }
71
72 pub(crate) fn db(&self) -> &RootDatabase {
73 self.sema.db
74 }
75
76 // NB, this ignores active selection.
77 pub(crate) fn offset(&self) -> TextSize {
78 self.frange.range.start()
79 }
80
81 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
82 self.source_file.syntax().token_at_offset(self.offset())
83 }
84 pub(crate) fn find_token_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
85 self.token_at_offset().find(|it| it.kind() == kind)
86 }
87 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
88 find_node_at_offset(self.source_file.syntax(), self.offset())
89 }
90 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
91 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
92 }
93 pub(crate) fn covering_element(&self) -> SyntaxElement {
94 find_covering_element(self.source_file.syntax(), self.frange.range)
95 }
96 // FIXME: remove
97 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
98 find_covering_element(self.source_file.syntax(), range)
99 }
100}
101
102pub(crate) struct Assists {
103 resolve: bool,
104 file: FileId,
105 buf: Vec<(Assist, Option<SourceChange>)>,
106 allowed: Option<Vec<AssistKind>>,
107}
108
109impl Assists {
110 pub(crate) fn new_resolved(ctx: &AssistContext) -> Assists {
111 Assists {
112 resolve: true,
113 file: ctx.frange.file_id,
114 buf: Vec::new(),
115 allowed: ctx.config.allowed.clone(),
116 }
117 }
118
119 pub(crate) fn new_unresolved(ctx: &AssistContext) -> Assists {
120 Assists {
121 resolve: false,
122 file: ctx.frange.file_id,
123 buf: Vec::new(),
124 allowed: ctx.config.allowed.clone(),
125 }
126 }
127
128 pub(crate) fn finish_unresolved(self) -> Vec<Assist> {
129 assert!(!self.resolve);
130 self.finish()
131 .into_iter()
132 .map(|(label, edit)| {
133 assert!(edit.is_none());
134 label
135 })
136 .collect()
137 }
138
139 pub(crate) fn finish_resolved(self) -> Vec<ResolvedAssist> {
140 assert!(self.resolve);
141 self.finish()
142 .into_iter()
143 .map(|(label, edit)| ResolvedAssist { assist: label, source_change: edit.unwrap() })
144 .collect()
145 }
146
147 pub(crate) fn add(
148 &mut self,
149 id: AssistId,
150 label: impl Into<String>,
151 target: TextRange,
152 f: impl FnOnce(&mut AssistBuilder),
153 ) -> Option<()> {
154 if !self.is_allowed(&id) {
155 return None;
156 }
157 let label = Assist::new(id, label.into(), None, target);
158 self.add_impl(label, f)
159 }
160
161 pub(crate) fn add_group(
162 &mut self,
163 group: &GroupLabel,
164 id: AssistId,
165 label: impl Into<String>,
166 target: TextRange,
167 f: impl FnOnce(&mut AssistBuilder),
168 ) -> Option<()> {
169 if !self.is_allowed(&id) {
170 return None;
171 }
172
173 let label = Assist::new(id, label.into(), Some(group.clone()), target);
174 self.add_impl(label, f)
175 }
176
177 fn add_impl(&mut self, label: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
178 let source_change = if self.resolve {
179 let mut builder = AssistBuilder::new(self.file);
180 f(&mut builder);
181 Some(builder.finish())
182 } else {
183 None
184 };
185
186 self.buf.push((label, source_change));
187 Some(())
188 }
189
190 fn finish(mut self) -> Vec<(Assist, Option<SourceChange>)> {
191 self.buf.sort_by_key(|(label, _edit)| label.target.len());
192 self.buf
193 }
194
195 fn is_allowed(&self, id: &AssistId) -> bool {
196 match &self.allowed {
197 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
198 None => true,
199 }
200 }
201}
202
203pub(crate) struct AssistBuilder {
204 edit: TextEditBuilder,
205 file_id: FileId,
206 is_snippet: bool,
207 change: SourceChange,
208}
209
210impl AssistBuilder {
211 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
212 AssistBuilder {
213 edit: TextEditBuilder::default(),
214 file_id,
215 is_snippet: false,
216 change: SourceChange::default(),
217 }
218 }
219
220 pub(crate) fn edit_file(&mut self, file_id: FileId) {
221 self.file_id = file_id;
222 }
223
224 fn commit(&mut self) {
225 let edit = mem::take(&mut self.edit).finish();
226 if !edit.is_empty() {
227 let new_edit = SourceFileEdit { file_id: self.file_id, edit };
228 assert!(!self.change.source_file_edits.iter().any(|it| it.file_id == new_edit.file_id));
229 self.change.source_file_edits.push(new_edit);
230 }
231 }
232
233 /// Remove specified `range` of text.
234 pub(crate) fn delete(&mut self, range: TextRange) {
235 self.edit.delete(range)
236 }
237 /// Append specified `text` at the given `offset`
238 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
239 self.edit.insert(offset, text.into())
240 }
241 /// Append specified `snippet` at the given `offset`
242 pub(crate) fn insert_snippet(
243 &mut self,
244 _cap: SnippetCap,
245 offset: TextSize,
246 snippet: impl Into<String>,
247 ) {
248 self.is_snippet = true;
249 self.insert(offset, snippet);
250 }
251 /// Replaces specified `range` of text with a given string.
252 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
253 self.edit.replace(range, replace_with.into())
254 }
255 /// Replaces specified `range` of text with a given `snippet`.
256 pub(crate) fn replace_snippet(
257 &mut self,
258 _cap: SnippetCap,
259 range: TextRange,
260 snippet: impl Into<String>,
261 ) {
262 self.is_snippet = true;
263 self.replace(range, snippet);
264 }
265 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
266 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
267 }
268 /// Replaces specified `node` of text with a given string, reindenting the
269 /// string to maintain `node`'s existing indent.
270 // FIXME: remove in favor of ra_syntax::edit::IndentLevel::increase_indent
271 pub(crate) fn replace_node_and_indent(
272 &mut self,
273 node: &SyntaxNode,
274 replace_with: impl Into<String>,
275 ) {
276 let mut replace_with = replace_with.into();
277 if let Some(indent) = leading_indent(node) {
278 replace_with = reindent(&replace_with, &indent)
279 }
280 self.replace(node.text_range(), replace_with)
281 }
282 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
283 let node = rewriter.rewrite_root().unwrap();
284 let new = rewriter.rewrite(&node);
285 algo::diff(&node, &new).into_text_edit(&mut self.edit);
286 }
287
288 // FIXME: kill this API
289 /// Get access to the raw `TextEditBuilder`.
290 pub(crate) fn text_edit_builder(&mut self) -> &mut TextEditBuilder {
291 &mut self.edit
292 }
293
294 fn finish(mut self) -> SourceChange {
295 self.commit();
296 let mut change = mem::take(&mut self.change);
297 if self.is_snippet {
298 change.is_snippet = true;
299 }
300 change
301 }
302}
diff --git a/crates/ra_assists/src/ast_transform.rs b/crates/ra_assists/src/ast_transform.rs
deleted file mode 100644
index 5ea4f9f5b..000000000
--- a/crates/ra_assists/src/ast_transform.rs
+++ /dev/null
@@ -1,206 +0,0 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use rustc_hash::FxHashMap;
3
4use hir::{HirDisplay, PathResolution, SemanticsScope};
5use ra_syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode},
8};
9
10pub trait AstTransform<'a> {
11 fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode>;
12
13 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a>;
14 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
15 where
16 Self: Sized + 'a,
17 {
18 self.chain_before(Box::new(other))
19 }
20}
21
22struct NullTransformer;
23
24impl<'a> AstTransform<'a> for NullTransformer {
25 fn get_substitution(&self, _node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
26 None
27 }
28 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
29 other
30 }
31}
32
33pub struct SubstituteTypeParams<'a> {
34 source_scope: &'a SemanticsScope<'a>,
35 substs: FxHashMap<hir::TypeParam, ast::TypeRef>,
36 previous: Box<dyn AstTransform<'a> + 'a>,
37}
38
39impl<'a> SubstituteTypeParams<'a> {
40 pub fn for_trait_impl(
41 source_scope: &'a SemanticsScope<'a>,
42 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
43 trait_: hir::Trait,
44 impl_def: ast::Impl,
45 ) -> SubstituteTypeParams<'a> {
46 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
47 let generic_def: hir::GenericDef = trait_.into();
48 let substs_by_param: FxHashMap<_, _> = generic_def
49 .params(source_scope.db)
50 .into_iter()
51 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
52 .skip(1)
53 // The actual list of trait type parameters may be longer than the one
54 // used in the `impl` block due to trailing default type parametrs.
55 // For that case we extend the `substs` with an empty iterator so we
56 // can still hit those trailing values and check if they actually have
57 // a default type. If they do, go for that type from `hir` to `ast` so
58 // the resulting change can be applied correctly.
59 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
60 .filter_map(|(k, v)| match v {
61 Some(v) => Some((k, v)),
62 None => {
63 let default = k.default(source_scope.db)?;
64 Some((
65 k,
66 ast::make::type_ref(
67 &default
68 .display_source_code(source_scope.db, source_scope.module()?.into())
69 .ok()?,
70 ),
71 ))
72 }
73 })
74 .collect();
75 return SubstituteTypeParams {
76 source_scope,
77 substs: substs_by_param,
78 previous: Box::new(NullTransformer),
79 };
80
81 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
82 // trait ref, and then go from the types in the substs back to the syntax)
83 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::TypeRef>> {
84 let target_trait = impl_def.target_trait()?;
85 let path_type = match target_trait {
86 ast::TypeRef::PathType(path) => path,
87 _ => return None,
88 };
89 let type_arg_list = path_type.path()?.segment()?.type_arg_list()?;
90 let mut result = Vec::new();
91 for type_arg in type_arg_list.type_args() {
92 let type_arg: ast::TypeArg = type_arg;
93 result.push(type_arg.type_ref()?);
94 }
95 Some(result)
96 }
97 }
98 fn get_substitution_inner(
99 &self,
100 node: &ra_syntax::SyntaxNode,
101 ) -> Option<ra_syntax::SyntaxNode> {
102 let type_ref = ast::TypeRef::cast(node.clone())?;
103 let path = match &type_ref {
104 ast::TypeRef::PathType(path_type) => path_type.path()?,
105 _ => return None,
106 };
107 // FIXME: use `hir::Path::from_src` instead.
108 #[allow(deprecated)]
109 let path = hir::Path::from_ast(path)?;
110 let resolution = self.source_scope.resolve_hir_path(&path)?;
111 match resolution {
112 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
113 _ => None,
114 }
115 }
116}
117
118impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
119 fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
120 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
121 }
122 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
123 Box::new(SubstituteTypeParams { previous: other, ..self })
124 }
125}
126
127pub struct QualifyPaths<'a> {
128 target_scope: &'a SemanticsScope<'a>,
129 source_scope: &'a SemanticsScope<'a>,
130 previous: Box<dyn AstTransform<'a> + 'a>,
131}
132
133impl<'a> QualifyPaths<'a> {
134 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
135 Self { target_scope, source_scope, previous: Box::new(NullTransformer) }
136 }
137
138 fn get_substitution_inner(
139 &self,
140 node: &ra_syntax::SyntaxNode,
141 ) -> Option<ra_syntax::SyntaxNode> {
142 // FIXME handle value ns?
143 let from = self.target_scope.module()?;
144 let p = ast::Path::cast(node.clone())?;
145 if p.segment().and_then(|s| s.param_list()).is_some() {
146 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
147 return None;
148 }
149 // FIXME: use `hir::Path::from_src` instead.
150 #[allow(deprecated)]
151 let hir_path = hir::Path::from_ast(p.clone());
152 let resolution = self.source_scope.resolve_hir_path(&hir_path?)?;
153 match resolution {
154 PathResolution::Def(def) => {
155 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
156 let mut path = path_to_ast(found_path);
157
158 let type_args = p
159 .segment()
160 .and_then(|s| s.type_arg_list())
161 .map(|arg_list| apply(self, arg_list));
162 if let Some(type_args) = type_args {
163 let last_segment = path.segment().unwrap();
164 path = path.with_segment(last_segment.with_type_args(type_args))
165 }
166
167 Some(path.syntax().clone())
168 }
169 PathResolution::Local(_)
170 | PathResolution::TypeParam(_)
171 | PathResolution::SelfType(_) => None,
172 PathResolution::Macro(_) => None,
173 PathResolution::AssocItem(_) => None,
174 }
175 }
176}
177
178pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
179 SyntaxRewriter::from_fn(|element| match element {
180 ra_syntax::SyntaxElement::Node(n) => {
181 let replacement = transformer.get_substitution(&n)?;
182 Some(replacement.into())
183 }
184 _ => None,
185 })
186 .rewrite_ast(&node)
187}
188
189impl<'a> AstTransform<'a> for QualifyPaths<'a> {
190 fn get_substitution(&self, node: &ra_syntax::SyntaxNode) -> Option<ra_syntax::SyntaxNode> {
191 self.get_substitution_inner(node).or_else(|| self.previous.get_substitution(node))
192 }
193 fn chain_before(self, other: Box<dyn AstTransform<'a> + 'a>) -> Box<dyn AstTransform<'a> + 'a> {
194 Box::new(QualifyPaths { previous: other, ..self })
195 }
196}
197
198pub(crate) fn path_to_ast(path: hir::ModPath) -> ast::Path {
199 let parse = ast::SourceFile::parse(&path.to_string());
200 parse
201 .tree()
202 .syntax()
203 .descendants()
204 .find_map(ast::Path::cast)
205 .unwrap_or_else(|| panic!("failed to parse path {:?}, `{}`", path, path))
206}
diff --git a/crates/ra_assists/src/handlers/add_custom_impl.rs b/crates/ra_assists/src/handlers/add_custom_impl.rs
deleted file mode 100644
index b67438b6b..000000000
--- a/crates/ra_assists/src/handlers/add_custom_impl.rs
+++ /dev/null
@@ -1,208 +0,0 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 Direction, SmolStr,
4 SyntaxKind::{IDENT, WHITESPACE},
5 TextRange, TextSize,
6};
7use stdx::SepBy;
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: add_custom_impl
15//
16// Adds impl block for derived trait.
17//
18// ```
19// #[derive(Deb<|>ug, Display)]
20// struct S;
21// ```
22// ->
23// ```
24// #[derive(Display)]
25// struct S;
26//
27// impl Debug for S {
28// $0
29// }
30// ```
31pub(crate) fn add_custom_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
33 let input = attr.token_tree()?;
34
35 let attr_name = attr
36 .syntax()
37 .descendants_with_tokens()
38 .filter(|t| t.kind() == IDENT)
39 .find_map(|i| i.into_token())
40 .filter(|t| *t.text() == "derive")?
41 .text()
42 .clone();
43
44 let trait_token =
45 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
46
47 let annotated = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
48 let annotated_name = annotated.syntax().text().to_string();
49 let start_offset = annotated.syntax().parent()?.text_range().end();
50
51 let label =
52 format!("Add custom impl `{}` for `{}`", trait_token.text().as_str(), annotated_name);
53
54 let target = attr.syntax().text_range();
55 acc.add(AssistId("add_custom_impl", AssistKind::Refactor), label, target, |builder| {
56 let new_attr_input = input
57 .syntax()
58 .descendants_with_tokens()
59 .filter(|t| t.kind() == IDENT)
60 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
61 .filter(|t| t != trait_token.text())
62 .collect::<Vec<SmolStr>>();
63 let has_more_derives = !new_attr_input.is_empty();
64 let new_attr_input = new_attr_input.iter().sep_by(", ").surround_with("(", ")").to_string();
65
66 if has_more_derives {
67 builder.replace(input.syntax().text_range(), new_attr_input);
68 } else {
69 let attr_range = attr.syntax().text_range();
70 builder.delete(attr_range);
71
72 let line_break_range = attr
73 .syntax()
74 .next_sibling_or_token()
75 .filter(|t| t.kind() == WHITESPACE)
76 .map(|t| t.text_range())
77 .unwrap_or_else(|| TextRange::new(TextSize::from(0), TextSize::from(0)));
78 builder.delete(line_break_range);
79 }
80
81 match ctx.config.snippet_cap {
82 Some(cap) => {
83 builder.insert_snippet(
84 cap,
85 start_offset,
86 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_token, annotated_name),
87 );
88 }
89 None => {
90 builder.insert(
91 start_offset,
92 format!("\n\nimpl {} for {} {{\n\n}}", trait_token, annotated_name),
93 );
94 }
95 }
96 })
97}
98
99#[cfg(test)]
100mod tests {
101 use crate::tests::{check_assist, check_assist_not_applicable};
102
103 use super::*;
104
105 #[test]
106 fn add_custom_impl_for_unique_input() {
107 check_assist(
108 add_custom_impl,
109 "
110#[derive(Debu<|>g)]
111struct Foo {
112 bar: String,
113}
114 ",
115 "
116struct Foo {
117 bar: String,
118}
119
120impl Debug for Foo {
121 $0
122}
123 ",
124 )
125 }
126
127 #[test]
128 fn add_custom_impl_for_with_visibility_modifier() {
129 check_assist(
130 add_custom_impl,
131 "
132#[derive(Debug<|>)]
133pub struct Foo {
134 bar: String,
135}
136 ",
137 "
138pub struct Foo {
139 bar: String,
140}
141
142impl Debug for Foo {
143 $0
144}
145 ",
146 )
147 }
148
149 #[test]
150 fn add_custom_impl_when_multiple_inputs() {
151 check_assist(
152 add_custom_impl,
153 "
154#[derive(Display, Debug<|>, Serialize)]
155struct Foo {}
156 ",
157 "
158#[derive(Display, Serialize)]
159struct Foo {}
160
161impl Debug for Foo {
162 $0
163}
164 ",
165 )
166 }
167
168 #[test]
169 fn test_ignore_derive_macro_without_input() {
170 check_assist_not_applicable(
171 add_custom_impl,
172 "
173#[derive(<|>)]
174struct Foo {}
175 ",
176 )
177 }
178
179 #[test]
180 fn test_ignore_if_cursor_on_param() {
181 check_assist_not_applicable(
182 add_custom_impl,
183 "
184#[derive<|>(Debug)]
185struct Foo {}
186 ",
187 );
188
189 check_assist_not_applicable(
190 add_custom_impl,
191 "
192#[derive(Debug)<|>]
193struct Foo {}
194 ",
195 )
196 }
197
198 #[test]
199 fn test_ignore_if_not_derive() {
200 check_assist_not_applicable(
201 add_custom_impl,
202 "
203#[allow(non_camel_<|>case_types)]
204struct Foo {}
205 ",
206 )
207 }
208}
diff --git a/crates/ra_assists/src/handlers/add_explicit_type.rs b/crates/ra_assists/src/handlers/add_explicit_type.rs
deleted file mode 100644
index e69f0a89b..000000000
--- a/crates/ra_assists/src/handlers/add_explicit_type.rs
+++ /dev/null
@@ -1,211 +0,0 @@
1use hir::HirDisplay;
2use ra_syntax::{
3 ast::{self, AstNode, LetStmt, NameOwner},
4 TextRange,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: add_explicit_type
10//
11// Specify type for a let binding.
12//
13// ```
14// fn main() {
15// let x<|> = 92;
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// let x: i32 = 92;
22// }
23// ```
24pub(crate) fn add_explicit_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let let_stmt = ctx.find_node_at_offset::<LetStmt>()?;
26 let module = ctx.sema.scope(let_stmt.syntax()).module()?;
27 let expr = let_stmt.initializer()?;
28 // Must be a binding
29 let pat = match let_stmt.pat()? {
30 ast::Pat::BindPat(bind_pat) => bind_pat,
31 _ => return None,
32 };
33 let pat_range = pat.syntax().text_range();
34 // The binding must have a name
35 let name = pat.name()?;
36 let name_range = name.syntax().text_range();
37 let stmt_range = let_stmt.syntax().text_range();
38 let eq_range = let_stmt.eq_token()?.text_range();
39 // Assist should only be applicable if cursor is between 'let' and '='
40 let let_range = TextRange::new(stmt_range.start(), eq_range.start());
41 let cursor_in_range = let_range.contains_range(ctx.frange.range);
42 if !cursor_in_range {
43 return None;
44 }
45 // Assist not applicable if the type has already been specified
46 // and it has no placeholders
47 let ascribed_ty = let_stmt.ty();
48 if let Some(ty) = &ascribed_ty {
49 if ty.syntax().descendants().find_map(ast::PlaceholderType::cast).is_none() {
50 return None;
51 }
52 }
53 // Infer type
54 let ty = ctx.sema.type_of_expr(&expr)?;
55
56 if ty.contains_unknown() || ty.is_closure() {
57 return None;
58 }
59
60 let inferred_type = ty.display_source_code(ctx.db(), module.into()).ok()?;
61 acc.add(
62 AssistId("add_explicit_type", AssistKind::RefactorRewrite),
63 format!("Insert explicit type `{}`", inferred_type),
64 pat_range,
65 |builder| match ascribed_ty {
66 Some(ascribed_ty) => {
67 builder.replace(ascribed_ty.syntax().text_range(), inferred_type);
68 }
69 None => {
70 builder.insert(name_range.end(), format!(": {}", inferred_type));
71 }
72 },
73 )
74}
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79
80 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
81
82 #[test]
83 fn add_explicit_type_target() {
84 check_assist_target(add_explicit_type, "fn f() { let a<|> = 1; }", "a");
85 }
86
87 #[test]
88 fn add_explicit_type_works_for_simple_expr() {
89 check_assist(add_explicit_type, "fn f() { let a<|> = 1; }", "fn f() { let a: i32 = 1; }");
90 }
91
92 #[test]
93 fn add_explicit_type_works_for_underscore() {
94 check_assist(
95 add_explicit_type,
96 "fn f() { let a<|>: _ = 1; }",
97 "fn f() { let a: i32 = 1; }",
98 );
99 }
100
101 #[test]
102 fn add_explicit_type_works_for_nested_underscore() {
103 check_assist(
104 add_explicit_type,
105 r#"
106 enum Option<T> {
107 Some(T),
108 None
109 }
110
111 fn f() {
112 let a<|>: Option<_> = Option::Some(1);
113 }"#,
114 r#"
115 enum Option<T> {
116 Some(T),
117 None
118 }
119
120 fn f() {
121 let a: Option<i32> = Option::Some(1);
122 }"#,
123 );
124 }
125
126 #[test]
127 fn add_explicit_type_works_for_macro_call() {
128 check_assist(
129 add_explicit_type,
130 r"macro_rules! v { () => {0u64} } fn f() { let a<|> = v!(); }",
131 r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
132 );
133 }
134
135 #[test]
136 fn add_explicit_type_works_for_macro_call_recursive() {
137 check_assist(
138 add_explicit_type,
139 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a<|> = v!(); }"#,
140 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
141 );
142 }
143
144 #[test]
145 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
146 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|> = None; }");
147 }
148
149 #[test]
150 fn add_explicit_type_not_applicable_if_ty_already_specified() {
151 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: i32 = 1; }");
152 }
153
154 #[test]
155 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
156 check_assist_not_applicable(add_explicit_type, "fn f() { let a<|>: (i32, i32) = (3, 4); }");
157 }
158
159 #[test]
160 fn add_explicit_type_not_applicable_if_cursor_after_equals() {
161 check_assist_not_applicable(
162 add_explicit_type,
163 "fn f() {let a =<|> match 1 {2 => 3, 3 => 5};}",
164 )
165 }
166
167 #[test]
168 fn add_explicit_type_not_applicable_if_cursor_before_let() {
169 check_assist_not_applicable(
170 add_explicit_type,
171 "fn f() <|>{let a = match 1 {2 => 3, 3 => 5};}",
172 )
173 }
174
175 #[test]
176 fn closure_parameters_are_not_added() {
177 check_assist_not_applicable(
178 add_explicit_type,
179 r#"
180fn main() {
181 let multiply_by_two<|> = |i| i * 3;
182 let six = multiply_by_two(2);
183}"#,
184 )
185 }
186
187 #[test]
188 fn default_generics_should_not_be_added() {
189 check_assist(
190 add_explicit_type,
191 r#"
192struct Test<K, T = u8> {
193 k: K,
194 t: T,
195}
196
197fn main() {
198 let test<|> = Test { t: 23u8, k: 33 };
199}"#,
200 r#"
201struct Test<K, T = u8> {
202 k: K,
203 t: T,
204}
205
206fn main() {
207 let test: Test<i32> = Test { t: 23u8, k: 33 };
208}"#,
209 );
210 }
211}
diff --git a/crates/ra_assists/src/handlers/add_missing_impl_members.rs b/crates/ra_assists/src/handlers/add_missing_impl_members.rs
deleted file mode 100644
index 95a750aee..000000000
--- a/crates/ra_assists/src/handlers/add_missing_impl_members.rs
+++ /dev/null
@@ -1,711 +0,0 @@
1use hir::HasSource;
2use ra_syntax::{
3 ast::{
4 self,
5 edit::{self, AstNodeEdit, IndentLevel},
6 make, AstNode, NameOwner,
7 },
8 SmolStr,
9};
10
11use crate::{
12 assist_context::{AssistContext, Assists},
13 ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams},
14 utils::{get_missing_assoc_items, render_snippet, resolve_target_trait, Cursor},
15 AssistId, AssistKind,
16};
17
18#[derive(PartialEq)]
19enum AddMissingImplMembersMode {
20 DefaultMethodsOnly,
21 NoDefaultMethods,
22}
23
24// Assist: add_impl_missing_members
25//
26// Adds scaffold for required impl members.
27//
28// ```
29// trait Trait<T> {
30// Type X;
31// fn foo(&self) -> T;
32// fn bar(&self) {}
33// }
34//
35// impl Trait<u32> for () {<|>
36//
37// }
38// ```
39// ->
40// ```
41// trait Trait<T> {
42// Type X;
43// fn foo(&self) -> T;
44// fn bar(&self) {}
45// }
46//
47// impl Trait<u32> for () {
48// fn foo(&self) -> u32 {
49// ${0:todo!()}
50// }
51//
52// }
53// ```
54pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
55 add_missing_impl_members_inner(
56 acc,
57 ctx,
58 AddMissingImplMembersMode::NoDefaultMethods,
59 "add_impl_missing_members",
60 "Implement missing members",
61 )
62}
63
64// Assist: add_impl_default_members
65//
66// Adds scaffold for overriding default impl members.
67//
68// ```
69// trait Trait {
70// Type X;
71// fn foo(&self);
72// fn bar(&self) {}
73// }
74//
75// impl Trait for () {
76// Type X = ();
77// fn foo(&self) {}<|>
78//
79// }
80// ```
81// ->
82// ```
83// trait Trait {
84// Type X;
85// fn foo(&self);
86// fn bar(&self) {}
87// }
88//
89// impl Trait for () {
90// Type X = ();
91// fn foo(&self) {}
92// $0fn bar(&self) {}
93//
94// }
95// ```
96pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
97 add_missing_impl_members_inner(
98 acc,
99 ctx,
100 AddMissingImplMembersMode::DefaultMethodsOnly,
101 "add_impl_default_members",
102 "Implement default members",
103 )
104}
105
106fn add_missing_impl_members_inner(
107 acc: &mut Assists,
108 ctx: &AssistContext,
109 mode: AddMissingImplMembersMode,
110 assist_id: &'static str,
111 label: &'static str,
112) -> Option<()> {
113 let _p = ra_prof::profile("add_missing_impl_members_inner");
114 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
115 let impl_item_list = impl_def.assoc_item_list()?;
116
117 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
118
119 let def_name = |item: &ast::AssocItem| -> Option<SmolStr> {
120 match item {
121 ast::AssocItem::Fn(def) => def.name(),
122 ast::AssocItem::TypeAlias(def) => def.name(),
123 ast::AssocItem::Const(def) => def.name(),
124 ast::AssocItem::MacroCall(_) => None,
125 }
126 .map(|it| it.text().clone())
127 };
128
129 let missing_items = get_missing_assoc_items(&ctx.sema, &impl_def)
130 .iter()
131 .map(|i| match i {
132 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(ctx.db()).value),
133 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(ctx.db()).value),
134 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(ctx.db()).value),
135 })
136 .filter(|t| def_name(&t).is_some())
137 .filter(|t| match t {
138 ast::AssocItem::Fn(def) => match mode {
139 AddMissingImplMembersMode::DefaultMethodsOnly => def.body().is_some(),
140 AddMissingImplMembersMode::NoDefaultMethods => def.body().is_none(),
141 },
142 _ => mode == AddMissingImplMembersMode::NoDefaultMethods,
143 })
144 .collect::<Vec<_>>();
145
146 if missing_items.is_empty() {
147 return None;
148 }
149
150 let target = impl_def.syntax().text_range();
151 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
152 let n_existing_items = impl_item_list.assoc_items().count();
153 let source_scope = ctx.sema.scope_for_def(trait_);
154 let target_scope = ctx.sema.scope(impl_item_list.syntax());
155 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
156 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def));
157 let items = missing_items
158 .into_iter()
159 .map(|it| ast_transform::apply(&*ast_transform, it))
160 .map(|it| match it {
161 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
162 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
163 _ => it,
164 })
165 .map(|it| edit::remove_attrs_and_docs(&it));
166 let new_impl_item_list = impl_item_list.append_items(items);
167 let first_new_item = new_impl_item_list.assoc_items().nth(n_existing_items).unwrap();
168
169 let original_range = impl_item_list.syntax().text_range();
170 match ctx.config.snippet_cap {
171 None => builder.replace(original_range, new_impl_item_list.to_string()),
172 Some(cap) => {
173 let mut cursor = Cursor::Before(first_new_item.syntax());
174 let placeholder;
175 if let ast::AssocItem::Fn(func) = &first_new_item {
176 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
177 if m.syntax().text() == "todo!()" {
178 placeholder = m;
179 cursor = Cursor::Replace(placeholder.syntax());
180 }
181 }
182 }
183 builder.replace_snippet(
184 cap,
185 original_range,
186 render_snippet(cap, new_impl_item_list.syntax(), cursor),
187 )
188 }
189 };
190 })
191}
192
193fn add_body(fn_def: ast::Fn) -> ast::Fn {
194 if fn_def.body().is_some() {
195 return fn_def;
196 }
197 let body = make::block_expr(None, Some(make::expr_todo())).indent(IndentLevel(1));
198 fn_def.with_body(body)
199}
200
201#[cfg(test)]
202mod tests {
203 use crate::tests::{check_assist, check_assist_not_applicable};
204
205 use super::*;
206
207 #[test]
208 fn test_add_missing_impl_members() {
209 check_assist(
210 add_missing_impl_members,
211 r#"
212trait Foo {
213 type Output;
214
215 const CONST: usize = 42;
216
217 fn foo(&self);
218 fn bar(&self);
219 fn baz(&self);
220}
221
222struct S;
223
224impl Foo for S {
225 fn bar(&self) {}
226<|>
227}"#,
228 r#"
229trait Foo {
230 type Output;
231
232 const CONST: usize = 42;
233
234 fn foo(&self);
235 fn bar(&self);
236 fn baz(&self);
237}
238
239struct S;
240
241impl Foo for S {
242 fn bar(&self) {}
243 $0type Output;
244 const CONST: usize = 42;
245 fn foo(&self) {
246 todo!()
247 }
248 fn baz(&self) {
249 todo!()
250 }
251
252}"#,
253 );
254 }
255
256 #[test]
257 fn test_copied_overriden_members() {
258 check_assist(
259 add_missing_impl_members,
260 r#"
261trait Foo {
262 fn foo(&self);
263 fn bar(&self) -> bool { true }
264 fn baz(&self) -> u32 { 42 }
265}
266
267struct S;
268
269impl Foo for S {
270 fn bar(&self) {}
271<|>
272}"#,
273 r#"
274trait Foo {
275 fn foo(&self);
276 fn bar(&self) -> bool { true }
277 fn baz(&self) -> u32 { 42 }
278}
279
280struct S;
281
282impl Foo for S {
283 fn bar(&self) {}
284 fn foo(&self) {
285 ${0:todo!()}
286 }
287
288}"#,
289 );
290 }
291
292 #[test]
293 fn test_empty_impl_def() {
294 check_assist(
295 add_missing_impl_members,
296 r#"
297trait Foo { fn foo(&self); }
298struct S;
299impl Foo for S { <|> }"#,
300 r#"
301trait Foo { fn foo(&self); }
302struct S;
303impl Foo for S {
304 fn foo(&self) {
305 ${0:todo!()}
306 }
307}"#,
308 );
309 }
310
311 #[test]
312 fn fill_in_type_params_1() {
313 check_assist(
314 add_missing_impl_members,
315 r#"
316trait Foo<T> { fn foo(&self, t: T) -> &T; }
317struct S;
318impl Foo<u32> for S { <|> }"#,
319 r#"
320trait Foo<T> { fn foo(&self, t: T) -> &T; }
321struct S;
322impl Foo<u32> for S {
323 fn foo(&self, t: u32) -> &u32 {
324 ${0:todo!()}
325 }
326}"#,
327 );
328 }
329
330 #[test]
331 fn fill_in_type_params_2() {
332 check_assist(
333 add_missing_impl_members,
334 r#"
335trait Foo<T> { fn foo(&self, t: T) -> &T; }
336struct S;
337impl<U> Foo<U> for S { <|> }"#,
338 r#"
339trait Foo<T> { fn foo(&self, t: T) -> &T; }
340struct S;
341impl<U> Foo<U> for S {
342 fn foo(&self, t: U) -> &U {
343 ${0:todo!()}
344 }
345}"#,
346 );
347 }
348
349 #[test]
350 fn test_cursor_after_empty_impl_def() {
351 check_assist(
352 add_missing_impl_members,
353 r#"
354trait Foo { fn foo(&self); }
355struct S;
356impl Foo for S {}<|>"#,
357 r#"
358trait Foo { fn foo(&self); }
359struct S;
360impl Foo for S {
361 fn foo(&self) {
362 ${0:todo!()}
363 }
364}"#,
365 )
366 }
367
368 #[test]
369 fn test_qualify_path_1() {
370 check_assist(
371 add_missing_impl_members,
372 r#"
373mod foo {
374 pub struct Bar;
375 trait Foo { fn foo(&self, bar: Bar); }
376}
377struct S;
378impl foo::Foo for S { <|> }"#,
379 r#"
380mod foo {
381 pub struct Bar;
382 trait Foo { fn foo(&self, bar: Bar); }
383}
384struct S;
385impl foo::Foo for S {
386 fn foo(&self, bar: foo::Bar) {
387 ${0:todo!()}
388 }
389}"#,
390 );
391 }
392
393 #[test]
394 fn test_qualify_path_generic() {
395 check_assist(
396 add_missing_impl_members,
397 r#"
398mod foo {
399 pub struct Bar<T>;
400 trait Foo { fn foo(&self, bar: Bar<u32>); }
401}
402struct S;
403impl foo::Foo for S { <|> }"#,
404 r#"
405mod foo {
406 pub struct Bar<T>;
407 trait Foo { fn foo(&self, bar: Bar<u32>); }
408}
409struct S;
410impl foo::Foo for S {
411 fn foo(&self, bar: foo::Bar<u32>) {
412 ${0:todo!()}
413 }
414}"#,
415 );
416 }
417
418 #[test]
419 fn test_qualify_path_and_substitute_param() {
420 check_assist(
421 add_missing_impl_members,
422 r#"
423mod foo {
424 pub struct Bar<T>;
425 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
426}
427struct S;
428impl foo::Foo<u32> for S { <|> }"#,
429 r#"
430mod foo {
431 pub struct Bar<T>;
432 trait Foo<T> { fn foo(&self, bar: Bar<T>); }
433}
434struct S;
435impl foo::Foo<u32> for S {
436 fn foo(&self, bar: foo::Bar<u32>) {
437 ${0:todo!()}
438 }
439}"#,
440 );
441 }
442
443 #[test]
444 fn test_substitute_param_no_qualify() {
445 // when substituting params, the substituted param should not be qualified!
446 check_assist(
447 add_missing_impl_members,
448 r#"
449mod foo {
450 trait Foo<T> { fn foo(&self, bar: T); }
451 pub struct Param;
452}
453struct Param;
454struct S;
455impl foo::Foo<Param> for S { <|> }"#,
456 r#"
457mod foo {
458 trait Foo<T> { fn foo(&self, bar: T); }
459 pub struct Param;
460}
461struct Param;
462struct S;
463impl foo::Foo<Param> for S {
464 fn foo(&self, bar: Param) {
465 ${0:todo!()}
466 }
467}"#,
468 );
469 }
470
471 #[test]
472 fn test_qualify_path_associated_item() {
473 check_assist(
474 add_missing_impl_members,
475 r#"
476mod foo {
477 pub struct Bar<T>;
478 impl Bar<T> { type Assoc = u32; }
479 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
480}
481struct S;
482impl foo::Foo for S { <|> }"#,
483 r#"
484mod foo {
485 pub struct Bar<T>;
486 impl Bar<T> { type Assoc = u32; }
487 trait Foo { fn foo(&self, bar: Bar<u32>::Assoc); }
488}
489struct S;
490impl foo::Foo for S {
491 fn foo(&self, bar: foo::Bar<u32>::Assoc) {
492 ${0:todo!()}
493 }
494}"#,
495 );
496 }
497
498 #[test]
499 fn test_qualify_path_nested() {
500 check_assist(
501 add_missing_impl_members,
502 r#"
503mod foo {
504 pub struct Bar<T>;
505 pub struct Baz;
506 trait Foo { fn foo(&self, bar: Bar<Baz>); }
507}
508struct S;
509impl foo::Foo for S { <|> }"#,
510 r#"
511mod foo {
512 pub struct Bar<T>;
513 pub struct Baz;
514 trait Foo { fn foo(&self, bar: Bar<Baz>); }
515}
516struct S;
517impl foo::Foo for S {
518 fn foo(&self, bar: foo::Bar<foo::Baz>) {
519 ${0:todo!()}
520 }
521}"#,
522 );
523 }
524
525 #[test]
526 fn test_qualify_path_fn_trait_notation() {
527 check_assist(
528 add_missing_impl_members,
529 r#"
530mod foo {
531 pub trait Fn<Args> { type Output; }
532 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
533}
534struct S;
535impl foo::Foo for S { <|> }"#,
536 r#"
537mod foo {
538 pub trait Fn<Args> { type Output; }
539 trait Foo { fn foo(&self, bar: dyn Fn(u32) -> i32); }
540}
541struct S;
542impl foo::Foo for S {
543 fn foo(&self, bar: dyn Fn(u32) -> i32) {
544 ${0:todo!()}
545 }
546}"#,
547 );
548 }
549
550 #[test]
551 fn test_empty_trait() {
552 check_assist_not_applicable(
553 add_missing_impl_members,
554 r#"
555trait Foo;
556struct S;
557impl Foo for S { <|> }"#,
558 )
559 }
560
561 #[test]
562 fn test_ignore_unnamed_trait_members_and_default_methods() {
563 check_assist_not_applicable(
564 add_missing_impl_members,
565 r#"
566trait Foo {
567 fn (arg: u32);
568 fn valid(some: u32) -> bool { false }
569}
570struct S;
571impl Foo for S { <|> }"#,
572 )
573 }
574
575 #[test]
576 fn test_with_docstring_and_attrs() {
577 check_assist(
578 add_missing_impl_members,
579 r#"
580#[doc(alias = "test alias")]
581trait Foo {
582 /// doc string
583 type Output;
584
585 #[must_use]
586 fn foo(&self);
587}
588struct S;
589impl Foo for S {}<|>"#,
590 r#"
591#[doc(alias = "test alias")]
592trait Foo {
593 /// doc string
594 type Output;
595
596 #[must_use]
597 fn foo(&self);
598}
599struct S;
600impl Foo for S {
601 $0type Output;
602 fn foo(&self) {
603 todo!()
604 }
605}"#,
606 )
607 }
608
609 #[test]
610 fn test_default_methods() {
611 check_assist(
612 add_missing_default_members,
613 r#"
614trait Foo {
615 type Output;
616
617 const CONST: usize = 42;
618
619 fn valid(some: u32) -> bool { false }
620 fn foo(some: u32) -> bool;
621}
622struct S;
623impl Foo for S { <|> }"#,
624 r#"
625trait Foo {
626 type Output;
627
628 const CONST: usize = 42;
629
630 fn valid(some: u32) -> bool { false }
631 fn foo(some: u32) -> bool;
632}
633struct S;
634impl Foo for S {
635 $0fn valid(some: u32) -> bool { false }
636}"#,
637 )
638 }
639
640 #[test]
641 fn test_generic_single_default_parameter() {
642 check_assist(
643 add_missing_impl_members,
644 r#"
645trait Foo<T = Self> {
646 fn bar(&self, other: &T);
647}
648
649struct S;
650impl Foo for S { <|> }"#,
651 r#"
652trait Foo<T = Self> {
653 fn bar(&self, other: &T);
654}
655
656struct S;
657impl Foo for S {
658 fn bar(&self, other: &Self) {
659 ${0:todo!()}
660 }
661}"#,
662 )
663 }
664
665 #[test]
666 fn test_generic_default_parameter_is_second() {
667 check_assist(
668 add_missing_impl_members,
669 r#"
670trait Foo<T1, T2 = Self> {
671 fn bar(&self, this: &T1, that: &T2);
672}
673
674struct S<T>;
675impl Foo<T> for S<T> { <|> }"#,
676 r#"
677trait Foo<T1, T2 = Self> {
678 fn bar(&self, this: &T1, that: &T2);
679}
680
681struct S<T>;
682impl Foo<T> for S<T> {
683 fn bar(&self, this: &T, that: &Self) {
684 ${0:todo!()}
685 }
686}"#,
687 )
688 }
689
690 #[test]
691 fn test_assoc_type_bounds_are_removed() {
692 check_assist(
693 add_missing_impl_members,
694 r#"
695trait Tr {
696 type Ty: Copy + 'static;
697}
698
699impl Tr for ()<|> {
700}"#,
701 r#"
702trait Tr {
703 type Ty: Copy + 'static;
704}
705
706impl Tr for () {
707 $0type Ty;
708}"#,
709 )
710 }
711}
diff --git a/crates/ra_assists/src/handlers/add_turbo_fish.rs b/crates/ra_assists/src/handlers/add_turbo_fish.rs
deleted file mode 100644
index 0c565e89a..000000000
--- a/crates/ra_assists/src/handlers/add_turbo_fish.rs
+++ /dev/null
@@ -1,164 +0,0 @@
1use ra_ide_db::defs::{classify_name_ref, Definition, NameRefClass};
2use ra_syntax::{ast, AstNode, SyntaxKind, T};
3use test_utils::mark;
4
5use crate::{
6 assist_context::{AssistContext, Assists},
7 AssistId, AssistKind,
8};
9
10// Assist: add_turbo_fish
11//
12// Adds `::<_>` to a call of a generic method or function.
13//
14// ```
15// fn make<T>() -> T { todo!() }
16// fn main() {
17// let x = make<|>();
18// }
19// ```
20// ->
21// ```
22// fn make<T>() -> T { todo!() }
23// fn main() {
24// let x = make::<${0:_}>();
25// }
26// ```
27pub(crate) fn add_turbo_fish(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let ident = ctx.find_token_at_offset(SyntaxKind::IDENT).or_else(|| {
29 let arg_list = ctx.find_node_at_offset::<ast::ArgList>()?;
30 if arg_list.args().count() > 0 {
31 return None;
32 }
33 mark::hit!(add_turbo_fish_after_call);
34 arg_list.l_paren_token()?.prev_token().filter(|it| it.kind() == SyntaxKind::IDENT)
35 })?;
36 let next_token = ident.next_token()?;
37 if next_token.kind() == T![::] {
38 mark::hit!(add_turbo_fish_one_fish_is_enough);
39 return None;
40 }
41 let name_ref = ast::NameRef::cast(ident.parent())?;
42 let def = match classify_name_ref(&ctx.sema, &name_ref)? {
43 NameRefClass::Definition(def) => def,
44 NameRefClass::FieldShorthand { .. } => return None,
45 };
46 let fun = match def {
47 Definition::ModuleDef(hir::ModuleDef::Function(it)) => it,
48 _ => return None,
49 };
50 let generics = hir::GenericDef::Function(fun).params(ctx.sema.db);
51 if generics.is_empty() {
52 mark::hit!(add_turbo_fish_non_generic);
53 return None;
54 }
55 acc.add(
56 AssistId("add_turbo_fish", AssistKind::RefactorRewrite),
57 "Add `::<>`",
58 ident.text_range(),
59 |builder| match ctx.config.snippet_cap {
60 Some(cap) => builder.insert_snippet(cap, ident.text_range().end(), "::<${0:_}>"),
61 None => builder.insert(ident.text_range().end(), "::<_>"),
62 },
63 )
64}
65
66#[cfg(test)]
67mod tests {
68 use crate::tests::{check_assist, check_assist_not_applicable};
69
70 use super::*;
71 use test_utils::mark;
72
73 #[test]
74 fn add_turbo_fish_function() {
75 check_assist(
76 add_turbo_fish,
77 r#"
78fn make<T>() -> T {}
79fn main() {
80 make<|>();
81}
82"#,
83 r#"
84fn make<T>() -> T {}
85fn main() {
86 make::<${0:_}>();
87}
88"#,
89 );
90 }
91
92 #[test]
93 fn add_turbo_fish_after_call() {
94 mark::check!(add_turbo_fish_after_call);
95 check_assist(
96 add_turbo_fish,
97 r#"
98fn make<T>() -> T {}
99fn main() {
100 make()<|>;
101}
102"#,
103 r#"
104fn make<T>() -> T {}
105fn main() {
106 make::<${0:_}>();
107}
108"#,
109 );
110 }
111
112 #[test]
113 fn add_turbo_fish_method() {
114 check_assist(
115 add_turbo_fish,
116 r#"
117struct S;
118impl S {
119 fn make<T>(&self) -> T {}
120}
121fn main() {
122 S.make<|>();
123}
124"#,
125 r#"
126struct S;
127impl S {
128 fn make<T>(&self) -> T {}
129}
130fn main() {
131 S.make::<${0:_}>();
132}
133"#,
134 );
135 }
136
137 #[test]
138 fn add_turbo_fish_one_fish_is_enough() {
139 mark::check!(add_turbo_fish_one_fish_is_enough);
140 check_assist_not_applicable(
141 add_turbo_fish,
142 r#"
143fn make<T>() -> T {}
144fn main() {
145 make<|>::<()>();
146}
147"#,
148 );
149 }
150
151 #[test]
152 fn add_turbo_fish_non_generic() {
153 mark::check!(add_turbo_fish_non_generic);
154 check_assist_not_applicable(
155 add_turbo_fish,
156 r#"
157fn make() -> () {}
158fn main() {
159 make<|>();
160}
161"#,
162 );
163 }
164}
diff --git a/crates/ra_assists/src/handlers/apply_demorgan.rs b/crates/ra_assists/src/handlers/apply_demorgan.rs
deleted file mode 100644
index de701f8b8..000000000
--- a/crates/ra_assists/src/handlers/apply_demorgan.rs
+++ /dev/null
@@ -1,93 +0,0 @@
1use ra_syntax::ast::{self, AstNode};
2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: apply_demorgan
6//
7// Apply [De Morgan's law](https://en.wikipedia.org/wiki/De_Morgan%27s_laws).
8// This transforms expressions of the form `!l || !r` into `!(l && r)`.
9// This also works with `&&`. This assist can only be applied with the cursor
10// on either `||` or `&&`, with both operands being a negation of some kind.
11// This means something of the form `!x` or `x != y`.
12//
13// ```
14// fn main() {
15// if x != 4 ||<|> !y {}
16// }
17// ```
18// ->
19// ```
20// fn main() {
21// if !(x == 4 && y) {}
22// }
23// ```
24pub(crate) fn apply_demorgan(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
26 let op = expr.op_kind()?;
27 let op_range = expr.op_token()?.text_range();
28 let opposite_op = opposite_logic_op(op)?;
29 let cursor_in_range = op_range.contains_range(ctx.frange.range);
30 if !cursor_in_range {
31 return None;
32 }
33
34 let lhs = expr.lhs()?;
35 let lhs_range = lhs.syntax().text_range();
36 let not_lhs = invert_boolean_expression(lhs);
37
38 let rhs = expr.rhs()?;
39 let rhs_range = rhs.syntax().text_range();
40 let not_rhs = invert_boolean_expression(rhs);
41
42 acc.add(
43 AssistId("apply_demorgan", AssistKind::RefactorRewrite),
44 "Apply De Morgan's law",
45 op_range,
46 |edit| {
47 edit.replace(op_range, opposite_op);
48 edit.replace(lhs_range, format!("!({}", not_lhs.syntax().text()));
49 edit.replace(rhs_range, format!("{})", not_rhs.syntax().text()));
50 },
51 )
52}
53
54// Return the opposite text for a given logical operator, if it makes sense
55fn opposite_logic_op(kind: ast::BinOp) -> Option<&'static str> {
56 match kind {
57 ast::BinOp::BooleanOr => Some("&&"),
58 ast::BinOp::BooleanAnd => Some("||"),
59 _ => None,
60 }
61}
62
63#[cfg(test)]
64mod tests {
65 use super::*;
66
67 use crate::tests::{check_assist, check_assist_not_applicable};
68
69 #[test]
70 fn demorgan_turns_and_into_or() {
71 check_assist(apply_demorgan, "fn f() { !x &&<|> !x }", "fn f() { !(x || x) }")
72 }
73
74 #[test]
75 fn demorgan_turns_or_into_and() {
76 check_assist(apply_demorgan, "fn f() { !x ||<|> !x }", "fn f() { !(x && x) }")
77 }
78
79 #[test]
80 fn demorgan_removes_inequality() {
81 check_assist(apply_demorgan, "fn f() { x != x ||<|> !x }", "fn f() { !(x == x && x) }")
82 }
83
84 #[test]
85 fn demorgan_general_case() {
86 check_assist(apply_demorgan, "fn f() { x ||<|> x }", "fn f() { !(!x && !x) }")
87 }
88
89 #[test]
90 fn demorgan_doesnt_apply_with_cursor_not_on_op() {
91 check_assist_not_applicable(apply_demorgan, "fn f() { <|> !x || !x }")
92 }
93}
diff --git a/crates/ra_assists/src/handlers/auto_import.rs b/crates/ra_assists/src/handlers/auto_import.rs
deleted file mode 100644
index 01e7b7a44..000000000
--- a/crates/ra_assists/src/handlers/auto_import.rs
+++ /dev/null
@@ -1,1089 +0,0 @@
1use std::collections::BTreeSet;
2
3use either::Either;
4use hir::{
5 AsAssocItem, AssocItemContainer, ModPath, Module, ModuleDef, PathResolution, Semantics, Trait,
6 Type,
7};
8use ra_ide_db::{imports_locator, RootDatabase};
9use ra_prof::profile;
10use ra_syntax::{
11 ast::{self, AstNode},
12 SyntaxNode,
13};
14use rustc_hash::FxHashSet;
15
16use crate::{
17 utils::insert_use_statement, AssistContext, AssistId, AssistKind, Assists, GroupLabel,
18};
19
20// Assist: auto_import
21//
22// If the name is unresolved, provides all possible imports for it.
23//
24// ```
25// fn main() {
26// let map = HashMap<|>::new();
27// }
28// # pub mod std { pub mod collections { pub struct HashMap { } } }
29// ```
30// ->
31// ```
32// use std::collections::HashMap;
33//
34// fn main() {
35// let map = HashMap::new();
36// }
37// # pub mod std { pub mod collections { pub struct HashMap { } } }
38// ```
39pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let auto_import_assets = AutoImportAssets::new(ctx)?;
41 let proposed_imports = auto_import_assets.search_for_imports(ctx);
42 if proposed_imports.is_empty() {
43 return None;
44 }
45
46 let range = ctx.sema.original_range(&auto_import_assets.syntax_under_caret).range;
47 let group = auto_import_assets.get_import_group_message();
48 for import in proposed_imports {
49 acc.add_group(
50 &group,
51 AssistId("auto_import", AssistKind::QuickFix),
52 format!("Import `{}`", &import),
53 range,
54 |builder| {
55 insert_use_statement(
56 &auto_import_assets.syntax_under_caret,
57 &import,
58 ctx,
59 builder.text_edit_builder(),
60 );
61 },
62 );
63 }
64 Some(())
65}
66
67#[derive(Debug)]
68struct AutoImportAssets {
69 import_candidate: ImportCandidate,
70 module_with_name_to_import: Module,
71 syntax_under_caret: SyntaxNode,
72}
73
74impl AutoImportAssets {
75 fn new(ctx: &AssistContext) -> Option<Self> {
76 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
77 Self::for_regular_path(path_under_caret, &ctx)
78 } else {
79 Self::for_method_call(ctx.find_node_at_offset_with_descend()?, &ctx)
80 }
81 }
82
83 fn for_method_call(method_call: ast::MethodCallExpr, ctx: &AssistContext) -> Option<Self> {
84 let syntax_under_caret = method_call.syntax().to_owned();
85 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
86 Some(Self {
87 import_candidate: ImportCandidate::for_method_call(&ctx.sema, &method_call)?,
88 module_with_name_to_import,
89 syntax_under_caret,
90 })
91 }
92
93 fn for_regular_path(path_under_caret: ast::Path, ctx: &AssistContext) -> Option<Self> {
94 let syntax_under_caret = path_under_caret.syntax().to_owned();
95 if syntax_under_caret.ancestors().find_map(ast::Use::cast).is_some() {
96 return None;
97 }
98
99 let module_with_name_to_import = ctx.sema.scope(&syntax_under_caret).module()?;
100 Some(Self {
101 import_candidate: ImportCandidate::for_regular_path(&ctx.sema, &path_under_caret)?,
102 module_with_name_to_import,
103 syntax_under_caret,
104 })
105 }
106
107 fn get_search_query(&self) -> &str {
108 match &self.import_candidate {
109 ImportCandidate::UnqualifiedName(name) => name,
110 ImportCandidate::QualifierStart(qualifier_start) => qualifier_start,
111 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => trait_assoc_item_name,
112 ImportCandidate::TraitMethod(_, trait_method_name) => trait_method_name,
113 }
114 }
115
116 fn get_import_group_message(&self) -> GroupLabel {
117 let name = match &self.import_candidate {
118 ImportCandidate::UnqualifiedName(name) => format!("Import {}", name),
119 ImportCandidate::QualifierStart(qualifier_start) => {
120 format!("Import {}", qualifier_start)
121 }
122 ImportCandidate::TraitAssocItem(_, trait_assoc_item_name) => {
123 format!("Import a trait for item {}", trait_assoc_item_name)
124 }
125 ImportCandidate::TraitMethod(_, trait_method_name) => {
126 format!("Import a trait for method {}", trait_method_name)
127 }
128 };
129 GroupLabel(name)
130 }
131
132 fn search_for_imports(&self, ctx: &AssistContext) -> BTreeSet<ModPath> {
133 let _p = profile("auto_import::search_for_imports");
134 let db = ctx.db();
135 let current_crate = self.module_with_name_to_import.krate();
136 imports_locator::find_imports(&ctx.sema, current_crate, &self.get_search_query())
137 .into_iter()
138 .filter_map(|candidate| match &self.import_candidate {
139 ImportCandidate::TraitAssocItem(assoc_item_type, _) => {
140 let located_assoc_item = match candidate {
141 Either::Left(ModuleDef::Function(located_function)) => located_function
142 .as_assoc_item(db)
143 .map(|assoc| assoc.container(db))
144 .and_then(Self::assoc_to_trait),
145 Either::Left(ModuleDef::Const(located_const)) => located_const
146 .as_assoc_item(db)
147 .map(|assoc| assoc.container(db))
148 .and_then(Self::assoc_to_trait),
149 _ => None,
150 }?;
151
152 let mut trait_candidates = FxHashSet::default();
153 trait_candidates.insert(located_assoc_item.into());
154
155 assoc_item_type
156 .iterate_path_candidates(
157 db,
158 current_crate,
159 &trait_candidates,
160 None,
161 |_, assoc| Self::assoc_to_trait(assoc.container(db)),
162 )
163 .map(ModuleDef::from)
164 .map(Either::Left)
165 }
166 ImportCandidate::TraitMethod(function_callee, _) => {
167 let located_assoc_item =
168 if let Either::Left(ModuleDef::Function(located_function)) = candidate {
169 located_function
170 .as_assoc_item(db)
171 .map(|assoc| assoc.container(db))
172 .and_then(Self::assoc_to_trait)
173 } else {
174 None
175 }?;
176
177 let mut trait_candidates = FxHashSet::default();
178 trait_candidates.insert(located_assoc_item.into());
179
180 function_callee
181 .iterate_method_candidates(
182 db,
183 current_crate,
184 &trait_candidates,
185 None,
186 |_, function| {
187 Self::assoc_to_trait(function.as_assoc_item(db)?.container(db))
188 },
189 )
190 .map(ModuleDef::from)
191 .map(Either::Left)
192 }
193 _ => Some(candidate),
194 })
195 .filter_map(|candidate| match candidate {
196 Either::Left(module_def) => {
197 self.module_with_name_to_import.find_use_path(db, module_def)
198 }
199 Either::Right(macro_def) => {
200 self.module_with_name_to_import.find_use_path(db, macro_def)
201 }
202 })
203 .filter(|use_path| !use_path.segments.is_empty())
204 .take(20)
205 .collect::<BTreeSet<_>>()
206 }
207
208 fn assoc_to_trait(assoc: AssocItemContainer) -> Option<Trait> {
209 if let AssocItemContainer::Trait(extracted_trait) = assoc {
210 Some(extracted_trait)
211 } else {
212 None
213 }
214 }
215}
216
217#[derive(Debug)]
218enum ImportCandidate {
219 /// Simple name like 'HashMap'
220 UnqualifiedName(String),
221 /// First part of the qualified name.
222 /// For 'std::collections::HashMap', that will be 'std'.
223 QualifierStart(String),
224 /// A trait associated function (with no self parameter) or associated constant.
225 /// For 'test_mod::TestEnum::test_function', `Type` is the `test_mod::TestEnum` expression type
226 /// and `String` is the `test_function`
227 TraitAssocItem(Type, String),
228 /// A trait method with self parameter.
229 /// For 'test_enum.test_method()', `Type` is the `test_enum` expression type
230 /// and `String` is the `test_method`
231 TraitMethod(Type, String),
232}
233
234impl ImportCandidate {
235 fn for_method_call(
236 sema: &Semantics<RootDatabase>,
237 method_call: &ast::MethodCallExpr,
238 ) -> Option<Self> {
239 if sema.resolve_method_call(method_call).is_some() {
240 return None;
241 }
242 Some(Self::TraitMethod(
243 sema.type_of_expr(&method_call.expr()?)?,
244 method_call.name_ref()?.syntax().to_string(),
245 ))
246 }
247
248 fn for_regular_path(
249 sema: &Semantics<RootDatabase>,
250 path_under_caret: &ast::Path,
251 ) -> Option<Self> {
252 if sema.resolve_path(path_under_caret).is_some() {
253 return None;
254 }
255
256 let segment = path_under_caret.segment()?;
257 if let Some(qualifier) = path_under_caret.qualifier() {
258 let qualifier_start = qualifier.syntax().descendants().find_map(ast::NameRef::cast)?;
259 let qualifier_start_path =
260 qualifier_start.syntax().ancestors().find_map(ast::Path::cast)?;
261 if let Some(qualifier_start_resolution) = sema.resolve_path(&qualifier_start_path) {
262 let qualifier_resolution = if qualifier_start_path == qualifier {
263 qualifier_start_resolution
264 } else {
265 sema.resolve_path(&qualifier)?
266 };
267 if let PathResolution::Def(ModuleDef::Adt(assoc_item_path)) = qualifier_resolution {
268 Some(ImportCandidate::TraitAssocItem(
269 assoc_item_path.ty(sema.db),
270 segment.syntax().to_string(),
271 ))
272 } else {
273 None
274 }
275 } else {
276 Some(ImportCandidate::QualifierStart(qualifier_start.syntax().to_string()))
277 }
278 } else {
279 Some(ImportCandidate::UnqualifiedName(
280 segment.syntax().descendants().find_map(ast::NameRef::cast)?.syntax().to_string(),
281 ))
282 }
283 }
284}
285
286#[cfg(test)]
287mod tests {
288 use super::*;
289 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
290
291 #[test]
292 fn applicable_when_found_an_import() {
293 check_assist(
294 auto_import,
295 r"
296 <|>PubStruct
297
298 pub mod PubMod {
299 pub struct PubStruct;
300 }
301 ",
302 r"
303 use PubMod::PubStruct;
304
305 PubStruct
306
307 pub mod PubMod {
308 pub struct PubStruct;
309 }
310 ",
311 );
312 }
313
314 #[test]
315 fn applicable_when_found_an_import_in_macros() {
316 check_assist(
317 auto_import,
318 r"
319 macro_rules! foo {
320 ($i:ident) => { fn foo(a: $i) {} }
321 }
322 foo!(Pub<|>Struct);
323
324 pub mod PubMod {
325 pub struct PubStruct;
326 }
327 ",
328 r"
329 use PubMod::PubStruct;
330
331 macro_rules! foo {
332 ($i:ident) => { fn foo(a: $i) {} }
333 }
334 foo!(PubStruct);
335
336 pub mod PubMod {
337 pub struct PubStruct;
338 }
339 ",
340 );
341 }
342
343 #[test]
344 fn auto_imports_are_merged() {
345 check_assist(
346 auto_import,
347 r"
348 use PubMod::PubStruct1;
349
350 struct Test {
351 test: Pub<|>Struct2<u8>,
352 }
353
354 pub mod PubMod {
355 pub struct PubStruct1;
356 pub struct PubStruct2<T> {
357 _t: T,
358 }
359 }
360 ",
361 r"
362 use PubMod::{PubStruct2, PubStruct1};
363
364 struct Test {
365 test: PubStruct2<u8>,
366 }
367
368 pub mod PubMod {
369 pub struct PubStruct1;
370 pub struct PubStruct2<T> {
371 _t: T,
372 }
373 }
374 ",
375 );
376 }
377
378 #[test]
379 fn applicable_when_found_multiple_imports() {
380 check_assist(
381 auto_import,
382 r"
383 PubSt<|>ruct
384
385 pub mod PubMod1 {
386 pub struct PubStruct;
387 }
388 pub mod PubMod2 {
389 pub struct PubStruct;
390 }
391 pub mod PubMod3 {
392 pub struct PubStruct;
393 }
394 ",
395 r"
396 use PubMod3::PubStruct;
397
398 PubStruct
399
400 pub mod PubMod1 {
401 pub struct PubStruct;
402 }
403 pub mod PubMod2 {
404 pub struct PubStruct;
405 }
406 pub mod PubMod3 {
407 pub struct PubStruct;
408 }
409 ",
410 );
411 }
412
413 #[test]
414 fn not_applicable_for_already_imported_types() {
415 check_assist_not_applicable(
416 auto_import,
417 r"
418 use PubMod::PubStruct;
419
420 PubStruct<|>
421
422 pub mod PubMod {
423 pub struct PubStruct;
424 }
425 ",
426 );
427 }
428
429 #[test]
430 fn not_applicable_for_types_with_private_paths() {
431 check_assist_not_applicable(
432 auto_import,
433 r"
434 PrivateStruct<|>
435
436 pub mod PubMod {
437 struct PrivateStruct;
438 }
439 ",
440 );
441 }
442
443 #[test]
444 fn not_applicable_when_no_imports_found() {
445 check_assist_not_applicable(
446 auto_import,
447 "
448 PubStruct<|>",
449 );
450 }
451
452 #[test]
453 fn not_applicable_in_import_statements() {
454 check_assist_not_applicable(
455 auto_import,
456 r"
457 use PubStruct<|>;
458
459 pub mod PubMod {
460 pub struct PubStruct;
461 }",
462 );
463 }
464
465 #[test]
466 fn function_import() {
467 check_assist(
468 auto_import,
469 r"
470 test_function<|>
471
472 pub mod PubMod {
473 pub fn test_function() {};
474 }
475 ",
476 r"
477 use PubMod::test_function;
478
479 test_function
480
481 pub mod PubMod {
482 pub fn test_function() {};
483 }
484 ",
485 );
486 }
487
488 #[test]
489 fn macro_import() {
490 check_assist(
491 auto_import,
492 r"
493//- /lib.rs crate:crate_with_macro
494#[macro_export]
495macro_rules! foo {
496 () => ()
497}
498
499//- /main.rs crate:main deps:crate_with_macro
500fn main() {
501 foo<|>
502}
503",
504 r"use crate_with_macro::foo;
505
506fn main() {
507 foo
508}
509",
510 );
511 }
512
513 #[test]
514 fn auto_import_target() {
515 check_assist_target(
516 auto_import,
517 r"
518 struct AssistInfo {
519 group_label: Option<<|>GroupLabel>,
520 }
521
522 mod m { pub struct GroupLabel; }
523 ",
524 "GroupLabel",
525 )
526 }
527
528 #[test]
529 fn not_applicable_when_path_start_is_imported() {
530 check_assist_not_applicable(
531 auto_import,
532 r"
533 pub mod mod1 {
534 pub mod mod2 {
535 pub mod mod3 {
536 pub struct TestStruct;
537 }
538 }
539 }
540
541 use mod1::mod2;
542 fn main() {
543 mod2::mod3::TestStruct<|>
544 }
545 ",
546 );
547 }
548
549 #[test]
550 fn not_applicable_for_imported_function() {
551 check_assist_not_applicable(
552 auto_import,
553 r"
554 pub mod test_mod {
555 pub fn test_function() {}
556 }
557
558 use test_mod::test_function;
559 fn main() {
560 test_function<|>
561 }
562 ",
563 );
564 }
565
566 #[test]
567 fn associated_struct_function() {
568 check_assist(
569 auto_import,
570 r"
571 mod test_mod {
572 pub struct TestStruct {}
573 impl TestStruct {
574 pub fn test_function() {}
575 }
576 }
577
578 fn main() {
579 TestStruct::test_function<|>
580 }
581 ",
582 r"
583 use test_mod::TestStruct;
584
585 mod test_mod {
586 pub struct TestStruct {}
587 impl TestStruct {
588 pub fn test_function() {}
589 }
590 }
591
592 fn main() {
593 TestStruct::test_function
594 }
595 ",
596 );
597 }
598
599 #[test]
600 fn associated_struct_const() {
601 check_assist(
602 auto_import,
603 r"
604 mod test_mod {
605 pub struct TestStruct {}
606 impl TestStruct {
607 const TEST_CONST: u8 = 42;
608 }
609 }
610
611 fn main() {
612 TestStruct::TEST_CONST<|>
613 }
614 ",
615 r"
616 use test_mod::TestStruct;
617
618 mod test_mod {
619 pub struct TestStruct {}
620 impl TestStruct {
621 const TEST_CONST: u8 = 42;
622 }
623 }
624
625 fn main() {
626 TestStruct::TEST_CONST
627 }
628 ",
629 );
630 }
631
632 #[test]
633 fn associated_trait_function() {
634 check_assist(
635 auto_import,
636 r"
637 mod test_mod {
638 pub trait TestTrait {
639 fn test_function();
640 }
641 pub struct TestStruct {}
642 impl TestTrait for TestStruct {
643 fn test_function() {}
644 }
645 }
646
647 fn main() {
648 test_mod::TestStruct::test_function<|>
649 }
650 ",
651 r"
652 use test_mod::TestTrait;
653
654 mod test_mod {
655 pub trait TestTrait {
656 fn test_function();
657 }
658 pub struct TestStruct {}
659 impl TestTrait for TestStruct {
660 fn test_function() {}
661 }
662 }
663
664 fn main() {
665 test_mod::TestStruct::test_function
666 }
667 ",
668 );
669 }
670
671 #[test]
672 fn not_applicable_for_imported_trait_for_function() {
673 check_assist_not_applicable(
674 auto_import,
675 r"
676 mod test_mod {
677 pub trait TestTrait {
678 fn test_function();
679 }
680 pub trait TestTrait2 {
681 fn test_function();
682 }
683 pub enum TestEnum {
684 One,
685 Two,
686 }
687 impl TestTrait2 for TestEnum {
688 fn test_function() {}
689 }
690 impl TestTrait for TestEnum {
691 fn test_function() {}
692 }
693 }
694
695 use test_mod::TestTrait2;
696 fn main() {
697 test_mod::TestEnum::test_function<|>;
698 }
699 ",
700 )
701 }
702
703 #[test]
704 fn associated_trait_const() {
705 check_assist(
706 auto_import,
707 r"
708 mod test_mod {
709 pub trait TestTrait {
710 const TEST_CONST: u8;
711 }
712 pub struct TestStruct {}
713 impl TestTrait for TestStruct {
714 const TEST_CONST: u8 = 42;
715 }
716 }
717
718 fn main() {
719 test_mod::TestStruct::TEST_CONST<|>
720 }
721 ",
722 r"
723 use test_mod::TestTrait;
724
725 mod test_mod {
726 pub trait TestTrait {
727 const TEST_CONST: u8;
728 }
729 pub struct TestStruct {}
730 impl TestTrait for TestStruct {
731 const TEST_CONST: u8 = 42;
732 }
733 }
734
735 fn main() {
736 test_mod::TestStruct::TEST_CONST
737 }
738 ",
739 );
740 }
741
742 #[test]
743 fn not_applicable_for_imported_trait_for_const() {
744 check_assist_not_applicable(
745 auto_import,
746 r"
747 mod test_mod {
748 pub trait TestTrait {
749 const TEST_CONST: u8;
750 }
751 pub trait TestTrait2 {
752 const TEST_CONST: f64;
753 }
754 pub enum TestEnum {
755 One,
756 Two,
757 }
758 impl TestTrait2 for TestEnum {
759 const TEST_CONST: f64 = 42.0;
760 }
761 impl TestTrait for TestEnum {
762 const TEST_CONST: u8 = 42;
763 }
764 }
765
766 use test_mod::TestTrait2;
767 fn main() {
768 test_mod::TestEnum::TEST_CONST<|>;
769 }
770 ",
771 )
772 }
773
774 #[test]
775 fn trait_method() {
776 check_assist(
777 auto_import,
778 r"
779 mod test_mod {
780 pub trait TestTrait {
781 fn test_method(&self);
782 }
783 pub struct TestStruct {}
784 impl TestTrait for TestStruct {
785 fn test_method(&self) {}
786 }
787 }
788
789 fn main() {
790 let test_struct = test_mod::TestStruct {};
791 test_struct.test_meth<|>od()
792 }
793 ",
794 r"
795 use test_mod::TestTrait;
796
797 mod test_mod {
798 pub trait TestTrait {
799 fn test_method(&self);
800 }
801 pub struct TestStruct {}
802 impl TestTrait for TestStruct {
803 fn test_method(&self) {}
804 }
805 }
806
807 fn main() {
808 let test_struct = test_mod::TestStruct {};
809 test_struct.test_method()
810 }
811 ",
812 );
813 }
814
815 #[test]
816 fn trait_method_cross_crate() {
817 check_assist(
818 auto_import,
819 r"
820 //- /main.rs crate:main deps:dep
821 fn main() {
822 let test_struct = dep::test_mod::TestStruct {};
823 test_struct.test_meth<|>od()
824 }
825 //- /dep.rs crate:dep
826 pub mod test_mod {
827 pub trait TestTrait {
828 fn test_method(&self);
829 }
830 pub struct TestStruct {}
831 impl TestTrait for TestStruct {
832 fn test_method(&self) {}
833 }
834 }
835 ",
836 r"
837 use dep::test_mod::TestTrait;
838
839 fn main() {
840 let test_struct = dep::test_mod::TestStruct {};
841 test_struct.test_method()
842 }
843 ",
844 );
845 }
846
847 #[test]
848 fn assoc_fn_cross_crate() {
849 check_assist(
850 auto_import,
851 r"
852 //- /main.rs crate:main deps:dep
853 fn main() {
854 dep::test_mod::TestStruct::test_func<|>tion
855 }
856 //- /dep.rs crate:dep
857 pub mod test_mod {
858 pub trait TestTrait {
859 fn test_function();
860 }
861 pub struct TestStruct {}
862 impl TestTrait for TestStruct {
863 fn test_function() {}
864 }
865 }
866 ",
867 r"
868 use dep::test_mod::TestTrait;
869
870 fn main() {
871 dep::test_mod::TestStruct::test_function
872 }
873 ",
874 );
875 }
876
877 #[test]
878 fn assoc_const_cross_crate() {
879 check_assist(
880 auto_import,
881 r"
882 //- /main.rs crate:main deps:dep
883 fn main() {
884 dep::test_mod::TestStruct::CONST<|>
885 }
886 //- /dep.rs crate:dep
887 pub mod test_mod {
888 pub trait TestTrait {
889 const CONST: bool;
890 }
891 pub struct TestStruct {}
892 impl TestTrait for TestStruct {
893 const CONST: bool = true;
894 }
895 }
896 ",
897 r"
898 use dep::test_mod::TestTrait;
899
900 fn main() {
901 dep::test_mod::TestStruct::CONST
902 }
903 ",
904 );
905 }
906
907 #[test]
908 fn assoc_fn_as_method_cross_crate() {
909 check_assist_not_applicable(
910 auto_import,
911 r"
912 //- /main.rs crate:main deps:dep
913 fn main() {
914 let test_struct = dep::test_mod::TestStruct {};
915 test_struct.test_func<|>tion()
916 }
917 //- /dep.rs crate:dep
918 pub mod test_mod {
919 pub trait TestTrait {
920 fn test_function();
921 }
922 pub struct TestStruct {}
923 impl TestTrait for TestStruct {
924 fn test_function() {}
925 }
926 }
927 ",
928 );
929 }
930
931 #[test]
932 fn private_trait_cross_crate() {
933 check_assist_not_applicable(
934 auto_import,
935 r"
936 //- /main.rs crate:main deps:dep
937 fn main() {
938 let test_struct = dep::test_mod::TestStruct {};
939 test_struct.test_meth<|>od()
940 }
941 //- /dep.rs crate:dep
942 pub mod test_mod {
943 trait TestTrait {
944 fn test_method(&self);
945 }
946 pub struct TestStruct {}
947 impl TestTrait for TestStruct {
948 fn test_method(&self) {}
949 }
950 }
951 ",
952 );
953 }
954
955 #[test]
956 fn not_applicable_for_imported_trait_for_method() {
957 check_assist_not_applicable(
958 auto_import,
959 r"
960 mod test_mod {
961 pub trait TestTrait {
962 fn test_method(&self);
963 }
964 pub trait TestTrait2 {
965 fn test_method(&self);
966 }
967 pub enum TestEnum {
968 One,
969 Two,
970 }
971 impl TestTrait2 for TestEnum {
972 fn test_method(&self) {}
973 }
974 impl TestTrait for TestEnum {
975 fn test_method(&self) {}
976 }
977 }
978
979 use test_mod::TestTrait2;
980 fn main() {
981 let one = test_mod::TestEnum::One;
982 one.test<|>_method();
983 }
984 ",
985 )
986 }
987
988 #[test]
989 fn dep_import() {
990 check_assist(
991 auto_import,
992 r"
993//- /lib.rs crate:dep
994pub struct Struct;
995
996//- /main.rs crate:main deps:dep
997fn main() {
998 Struct<|>
999}
1000",
1001 r"use dep::Struct;
1002
1003fn main() {
1004 Struct
1005}
1006",
1007 );
1008 }
1009
1010 #[test]
1011 fn whole_segment() {
1012 // Tests that only imports whose last segment matches the identifier get suggested.
1013 check_assist(
1014 auto_import,
1015 r"
1016//- /lib.rs crate:dep
1017pub mod fmt {
1018 pub trait Display {}
1019}
1020
1021pub fn panic_fmt() {}
1022
1023//- /main.rs crate:main deps:dep
1024struct S;
1025
1026impl f<|>mt::Display for S {}
1027",
1028 r"use dep::fmt;
1029
1030struct S;
1031
1032impl fmt::Display for S {}
1033",
1034 );
1035 }
1036
1037 #[test]
1038 fn macro_generated() {
1039 // Tests that macro-generated items are suggested from external crates.
1040 check_assist(
1041 auto_import,
1042 r"
1043//- /lib.rs crate:dep
1044macro_rules! mac {
1045 () => {
1046 pub struct Cheese;
1047 };
1048}
1049
1050mac!();
1051
1052//- /main.rs crate:main deps:dep
1053fn main() {
1054 Cheese<|>;
1055}
1056",
1057 r"use dep::Cheese;
1058
1059fn main() {
1060 Cheese;
1061}
1062",
1063 );
1064 }
1065
1066 #[test]
1067 fn casing() {
1068 // Tests that differently cased names don't interfere and we only suggest the matching one.
1069 check_assist(
1070 auto_import,
1071 r"
1072//- /lib.rs crate:dep
1073pub struct FMT;
1074pub struct fmt;
1075
1076//- /main.rs crate:main deps:dep
1077fn main() {
1078 FMT<|>;
1079}
1080",
1081 r"use dep::FMT;
1082
1083fn main() {
1084 FMT;
1085}
1086",
1087 );
1088 }
1089}
diff --git a/crates/ra_assists/src/handlers/change_return_type_to_result.rs b/crates/ra_assists/src/handlers/change_return_type_to_result.rs
deleted file mode 100644
index 167e162d8..000000000
--- a/crates/ra_assists/src/handlers/change_return_type_to_result.rs
+++ /dev/null
@@ -1,990 +0,0 @@
1use ra_syntax::{
2 ast::{self, BlockExpr, Expr, LoopBodyOwner},
3 AstNode, SyntaxNode,
4};
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7use test_utils::mark;
8
9// Assist: change_return_type_to_result
10//
11// Change the function's return type to Result.
12//
13// ```
14// fn foo() -> i32<|> { 42i32 }
15// ```
16// ->
17// ```
18// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
19// ```
20pub(crate) fn change_return_type_to_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
22 // FIXME: extend to lambdas as well
23 let fn_def = ret_type.syntax().parent().and_then(ast::Fn::cast)?;
24
25 let type_ref = &ret_type.ty()?;
26 let ret_type_str = type_ref.syntax().text().to_string();
27 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
28 if let Some(ret_type_first_part) = first_part_ret_type {
29 if ret_type_first_part.ends_with("Result") {
30 mark::hit!(change_return_type_to_result_simple_return_type_already_result);
31 return None;
32 }
33 }
34
35 let block_expr = &fn_def.body()?;
36
37 acc.add(
38 AssistId("change_return_type_to_result", AssistKind::RefactorRewrite),
39 "Wrap return type in Result",
40 type_ref.syntax().text_range(),
41 |builder| {
42 let mut tail_return_expr_collector = TailReturnCollector::new();
43 tail_return_expr_collector.collect_jump_exprs(block_expr, false);
44 tail_return_expr_collector.collect_tail_exprs(block_expr);
45
46 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
47 builder.replace_node_and_indent(&ret_expr_arg, format!("Ok({})", ret_expr_arg));
48 }
49
50 match ctx.config.snippet_cap {
51 Some(cap) => {
52 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
53 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
54 }
55 None => builder
56 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
57 }
58 },
59 )
60}
61
62struct TailReturnCollector {
63 exprs_to_wrap: Vec<SyntaxNode>,
64}
65
66impl TailReturnCollector {
67 fn new() -> Self {
68 Self { exprs_to_wrap: vec![] }
69 }
70 /// Collect all`return` expression
71 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
72 let statements = block_expr.statements();
73 for stmt in statements {
74 let expr = match &stmt {
75 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
76 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
77 };
78 if let Some(expr) = &expr {
79 self.handle_exprs(expr, collect_break);
80 }
81 }
82
83 // Browse tail expressions for each block
84 if let Some(expr) = block_expr.expr() {
85 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
86 for last_expr in last_exprs {
87 let last_expr = match last_expr {
88 NodeType::Node(expr) | NodeType::Leaf(expr) => expr,
89 };
90
91 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
92 self.handle_exprs(&last_expr, collect_break);
93 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
94 let expr_stmt = match &expr_stmt {
95 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
96 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
97 };
98 if let Some(expr) = &expr_stmt {
99 self.handle_exprs(expr, collect_break);
100 }
101 }
102 }
103 }
104 }
105 }
106
107 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
108 match expr {
109 Expr::BlockExpr(block_expr) => {
110 self.collect_jump_exprs(&block_expr, collect_break);
111 }
112 Expr::ReturnExpr(ret_expr) => {
113 if let Some(ret_expr_arg) = &ret_expr.expr() {
114 self.exprs_to_wrap.push(ret_expr_arg.syntax().clone());
115 }
116 }
117 Expr::BreakExpr(break_expr) if collect_break => {
118 if let Some(break_expr_arg) = &break_expr.expr() {
119 self.exprs_to_wrap.push(break_expr_arg.syntax().clone());
120 }
121 }
122 Expr::IfExpr(if_expr) => {
123 for block in if_expr.blocks() {
124 self.collect_jump_exprs(&block, collect_break);
125 }
126 }
127 Expr::LoopExpr(loop_expr) => {
128 if let Some(block_expr) = loop_expr.loop_body() {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 }
132 Expr::ForExpr(for_expr) => {
133 if let Some(block_expr) = for_expr.loop_body() {
134 self.collect_jump_exprs(&block_expr, collect_break);
135 }
136 }
137 Expr::WhileExpr(while_expr) => {
138 if let Some(block_expr) = while_expr.loop_body() {
139 self.collect_jump_exprs(&block_expr, collect_break);
140 }
141 }
142 Expr::MatchExpr(match_expr) => {
143 if let Some(arm_list) = match_expr.match_arm_list() {
144 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
145 self.handle_exprs(&expr, collect_break);
146 });
147 }
148 }
149 _ => {}
150 }
151 }
152
153 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
154 if let Some(expr) = block.expr() {
155 self.handle_exprs(&expr, true);
156 self.fetch_tail_exprs(&expr);
157 }
158 }
159
160 fn fetch_tail_exprs(&mut self, expr: &Expr) {
161 if let Some(exprs) = get_tail_expr_from_block(expr) {
162 for node_type in &exprs {
163 match node_type {
164 NodeType::Leaf(expr) => {
165 self.exprs_to_wrap.push(expr.clone());
166 }
167 NodeType::Node(expr) => match &Expr::cast(expr.clone()) {
168 Some(last_expr) => {
169 self.fetch_tail_exprs(last_expr);
170 }
171 None => {
172 self.exprs_to_wrap.push(expr.clone());
173 }
174 },
175 }
176 }
177 }
178 }
179}
180
181#[derive(Debug)]
182enum NodeType {
183 Leaf(SyntaxNode),
184 Node(SyntaxNode),
185}
186
187/// Get a tail expression inside a block
188fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
189 match expr {
190 Expr::IfExpr(if_expr) => {
191 let mut nodes = vec![];
192 for block in if_expr.blocks() {
193 if let Some(block_expr) = block.expr() {
194 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
195 nodes.extend(tail_exprs);
196 }
197 } else if let Some(last_expr) = block.syntax().last_child() {
198 nodes.push(NodeType::Node(last_expr));
199 } else {
200 nodes.push(NodeType::Node(block.syntax().clone()));
201 }
202 }
203 Some(nodes)
204 }
205 Expr::LoopExpr(loop_expr) => {
206 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
207 }
208 Expr::ForExpr(for_expr) => {
209 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
210 }
211 Expr::WhileExpr(while_expr) => {
212 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
213 }
214 Expr::BlockExpr(block_expr) => {
215 block_expr.expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
216 }
217 Expr::MatchExpr(match_expr) => {
218 let arm_list = match_expr.match_arm_list()?;
219 let arms: Vec<NodeType> = arm_list
220 .arms()
221 .filter_map(|match_arm| match_arm.expr())
222 .map(|expr| match expr {
223 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
224 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
225 _ => match expr.syntax().last_child() {
226 Some(last_expr) => NodeType::Node(last_expr),
227 None => NodeType::Node(expr.syntax().clone()),
228 },
229 })
230 .collect();
231
232 Some(arms)
233 }
234 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e.syntax().clone())]),
235 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
236 Expr::CallExpr(call_expr) => Some(vec![NodeType::Leaf(call_expr.syntax().clone())]),
237 Expr::Literal(lit_expr) => Some(vec![NodeType::Leaf(lit_expr.syntax().clone())]),
238 Expr::TupleExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
239 Expr::ArrayExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
240 Expr::ParenExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
241 Expr::PathExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
242 Expr::Label(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
243 Expr::RecordExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
244 Expr::IndexExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
245 Expr::MethodCallExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
246 Expr::AwaitExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
247 Expr::CastExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
248 Expr::RefExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
249 Expr::PrefixExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
250 Expr::RangeExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
251 Expr::BinExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
252 Expr::MacroCall(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
253 Expr::BoxExpr(expr) => Some(vec![NodeType::Leaf(expr.syntax().clone())]),
254 _ => None,
255 }
256}
257
258#[cfg(test)]
259mod tests {
260 use crate::tests::{check_assist, check_assist_not_applicable};
261
262 use super::*;
263
264 #[test]
265 fn change_return_type_to_result_simple() {
266 check_assist(
267 change_return_type_to_result,
268 r#"fn foo() -> i3<|>2 {
269 let test = "test";
270 return 42i32;
271 }"#,
272 r#"fn foo() -> Result<i32, ${0:_}> {
273 let test = "test";
274 return Ok(42i32);
275 }"#,
276 );
277 }
278
279 #[test]
280 fn change_return_type_to_result_simple_return_type() {
281 check_assist(
282 change_return_type_to_result,
283 r#"fn foo() -> i32<|> {
284 let test = "test";
285 return 42i32;
286 }"#,
287 r#"fn foo() -> Result<i32, ${0:_}> {
288 let test = "test";
289 return Ok(42i32);
290 }"#,
291 );
292 }
293
294 #[test]
295 fn change_return_type_to_result_simple_return_type_bad_cursor() {
296 check_assist_not_applicable(
297 change_return_type_to_result,
298 r#"fn foo() -> i32 {
299 let test = "test";<|>
300 return 42i32;
301 }"#,
302 );
303 }
304
305 #[test]
306 fn change_return_type_to_result_simple_return_type_already_result_std() {
307 check_assist_not_applicable(
308 change_return_type_to_result,
309 r#"fn foo() -> std::result::Result<i32<|>, String> {
310 let test = "test";
311 return 42i32;
312 }"#,
313 );
314 }
315
316 #[test]
317 fn change_return_type_to_result_simple_return_type_already_result() {
318 mark::check!(change_return_type_to_result_simple_return_type_already_result);
319 check_assist_not_applicable(
320 change_return_type_to_result,
321 r#"fn foo() -> Result<i32<|>, String> {
322 let test = "test";
323 return 42i32;
324 }"#,
325 );
326 }
327
328 #[test]
329 fn change_return_type_to_result_simple_with_cursor() {
330 check_assist(
331 change_return_type_to_result,
332 r#"fn foo() -> <|>i32 {
333 let test = "test";
334 return 42i32;
335 }"#,
336 r#"fn foo() -> Result<i32, ${0:_}> {
337 let test = "test";
338 return Ok(42i32);
339 }"#,
340 );
341 }
342
343 #[test]
344 fn change_return_type_to_result_simple_with_tail() {
345 check_assist(
346 change_return_type_to_result,
347 r#"fn foo() -><|> i32 {
348 let test = "test";
349 42i32
350 }"#,
351 r#"fn foo() -> Result<i32, ${0:_}> {
352 let test = "test";
353 Ok(42i32)
354 }"#,
355 );
356 }
357
358 #[test]
359 fn change_return_type_to_result_simple_with_tail_only() {
360 check_assist(
361 change_return_type_to_result,
362 r#"fn foo() -> i32<|> {
363 42i32
364 }"#,
365 r#"fn foo() -> Result<i32, ${0:_}> {
366 Ok(42i32)
367 }"#,
368 );
369 }
370 #[test]
371 fn change_return_type_to_result_simple_with_tail_block_like() {
372 check_assist(
373 change_return_type_to_result,
374 r#"fn foo() -> i32<|> {
375 if true {
376 42i32
377 } else {
378 24i32
379 }
380 }"#,
381 r#"fn foo() -> Result<i32, ${0:_}> {
382 if true {
383 Ok(42i32)
384 } else {
385 Ok(24i32)
386 }
387 }"#,
388 );
389 }
390
391 #[test]
392 fn change_return_type_to_result_simple_with_nested_if() {
393 check_assist(
394 change_return_type_to_result,
395 r#"fn foo() -> i32<|> {
396 if true {
397 if false {
398 1
399 } else {
400 2
401 }
402 } else {
403 24i32
404 }
405 }"#,
406 r#"fn foo() -> Result<i32, ${0:_}> {
407 if true {
408 if false {
409 Ok(1)
410 } else {
411 Ok(2)
412 }
413 } else {
414 Ok(24i32)
415 }
416 }"#,
417 );
418 }
419
420 #[test]
421 fn change_return_type_to_result_simple_with_await() {
422 check_assist(
423 change_return_type_to_result,
424 r#"async fn foo() -> i<|>32 {
425 if true {
426 if false {
427 1.await
428 } else {
429 2.await
430 }
431 } else {
432 24i32.await
433 }
434 }"#,
435 r#"async fn foo() -> Result<i32, ${0:_}> {
436 if true {
437 if false {
438 Ok(1.await)
439 } else {
440 Ok(2.await)
441 }
442 } else {
443 Ok(24i32.await)
444 }
445 }"#,
446 );
447 }
448
449 #[test]
450 fn change_return_type_to_result_simple_with_array() {
451 check_assist(
452 change_return_type_to_result,
453 r#"fn foo() -> [i32;<|> 3] {
454 [1, 2, 3]
455 }"#,
456 r#"fn foo() -> Result<[i32; 3], ${0:_}> {
457 Ok([1, 2, 3])
458 }"#,
459 );
460 }
461
462 #[test]
463 fn change_return_type_to_result_simple_with_cast() {
464 check_assist(
465 change_return_type_to_result,
466 r#"fn foo() -<|>> i32 {
467 if true {
468 if false {
469 1 as i32
470 } else {
471 2 as i32
472 }
473 } else {
474 24 as i32
475 }
476 }"#,
477 r#"fn foo() -> Result<i32, ${0:_}> {
478 if true {
479 if false {
480 Ok(1 as i32)
481 } else {
482 Ok(2 as i32)
483 }
484 } else {
485 Ok(24 as i32)
486 }
487 }"#,
488 );
489 }
490
491 #[test]
492 fn change_return_type_to_result_simple_with_tail_block_like_match() {
493 check_assist(
494 change_return_type_to_result,
495 r#"fn foo() -> i32<|> {
496 let my_var = 5;
497 match my_var {
498 5 => 42i32,
499 _ => 24i32,
500 }
501 }"#,
502 r#"fn foo() -> Result<i32, ${0:_}> {
503 let my_var = 5;
504 match my_var {
505 5 => Ok(42i32),
506 _ => Ok(24i32),
507 }
508 }"#,
509 );
510 }
511
512 #[test]
513 fn change_return_type_to_result_simple_with_loop_with_tail() {
514 check_assist(
515 change_return_type_to_result,
516 r#"fn foo() -> i32<|> {
517 let my_var = 5;
518 loop {
519 println!("test");
520 5
521 }
522
523 my_var
524 }"#,
525 r#"fn foo() -> Result<i32, ${0:_}> {
526 let my_var = 5;
527 loop {
528 println!("test");
529 5
530 }
531
532 Ok(my_var)
533 }"#,
534 );
535 }
536
537 #[test]
538 fn change_return_type_to_result_simple_with_loop_in_let_stmt() {
539 check_assist(
540 change_return_type_to_result,
541 r#"fn foo() -> i32<|> {
542 let my_var = let x = loop {
543 break 1;
544 };
545
546 my_var
547 }"#,
548 r#"fn foo() -> Result<i32, ${0:_}> {
549 let my_var = let x = loop {
550 break 1;
551 };
552
553 Ok(my_var)
554 }"#,
555 );
556 }
557
558 #[test]
559 fn change_return_type_to_result_simple_with_tail_block_like_match_return_expr() {
560 check_assist(
561 change_return_type_to_result,
562 r#"fn foo() -> i32<|> {
563 let my_var = 5;
564 let res = match my_var {
565 5 => 42i32,
566 _ => return 24i32,
567 };
568
569 res
570 }"#,
571 r#"fn foo() -> Result<i32, ${0:_}> {
572 let my_var = 5;
573 let res = match my_var {
574 5 => 42i32,
575 _ => return Ok(24i32),
576 };
577
578 Ok(res)
579 }"#,
580 );
581
582 check_assist(
583 change_return_type_to_result,
584 r#"fn foo() -> i32<|> {
585 let my_var = 5;
586 let res = if my_var == 5 {
587 42i32
588 } else {
589 return 24i32;
590 };
591
592 res
593 }"#,
594 r#"fn foo() -> Result<i32, ${0:_}> {
595 let my_var = 5;
596 let res = if my_var == 5 {
597 42i32
598 } else {
599 return Ok(24i32);
600 };
601
602 Ok(res)
603 }"#,
604 );
605 }
606
607 #[test]
608 fn change_return_type_to_result_simple_with_tail_block_like_match_deeper() {
609 check_assist(
610 change_return_type_to_result,
611 r#"fn foo() -> i32<|> {
612 let my_var = 5;
613 match my_var {
614 5 => {
615 if true {
616 42i32
617 } else {
618 25i32
619 }
620 },
621 _ => {
622 let test = "test";
623 if test == "test" {
624 return bar();
625 }
626 53i32
627 },
628 }
629 }"#,
630 r#"fn foo() -> Result<i32, ${0:_}> {
631 let my_var = 5;
632 match my_var {
633 5 => {
634 if true {
635 Ok(42i32)
636 } else {
637 Ok(25i32)
638 }
639 },
640 _ => {
641 let test = "test";
642 if test == "test" {
643 return Ok(bar());
644 }
645 Ok(53i32)
646 },
647 }
648 }"#,
649 );
650 }
651
652 #[test]
653 fn change_return_type_to_result_simple_with_tail_block_like_early_return() {
654 check_assist(
655 change_return_type_to_result,
656 r#"fn foo() -> i<|>32 {
657 let test = "test";
658 if test == "test" {
659 return 24i32;
660 }
661 53i32
662 }"#,
663 r#"fn foo() -> Result<i32, ${0:_}> {
664 let test = "test";
665 if test == "test" {
666 return Ok(24i32);
667 }
668 Ok(53i32)
669 }"#,
670 );
671 }
672
673 #[test]
674 fn change_return_type_to_result_simple_with_closure() {
675 check_assist(
676 change_return_type_to_result,
677 r#"fn foo(the_field: u32) -><|> u32 {
678 let true_closure = || {
679 return true;
680 };
681 if the_field < 5 {
682 let mut i = 0;
683
684
685 if true_closure() {
686 return 99;
687 } else {
688 return 0;
689 }
690 }
691
692 the_field
693 }"#,
694 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
695 let true_closure = || {
696 return true;
697 };
698 if the_field < 5 {
699 let mut i = 0;
700
701
702 if true_closure() {
703 return Ok(99);
704 } else {
705 return Ok(0);
706 }
707 }
708
709 Ok(the_field)
710 }"#,
711 );
712
713 check_assist(
714 change_return_type_to_result,
715 r#"fn foo(the_field: u32) -> u32<|> {
716 let true_closure = || {
717 return true;
718 };
719 if the_field < 5 {
720 let mut i = 0;
721
722
723 if true_closure() {
724 return 99;
725 } else {
726 return 0;
727 }
728 }
729 let t = None;
730
731 t.unwrap_or_else(|| the_field)
732 }"#,
733 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
734 let true_closure = || {
735 return true;
736 };
737 if the_field < 5 {
738 let mut i = 0;
739
740
741 if true_closure() {
742 return Ok(99);
743 } else {
744 return Ok(0);
745 }
746 }
747 let t = None;
748
749 Ok(t.unwrap_or_else(|| the_field))
750 }"#,
751 );
752 }
753
754 #[test]
755 fn change_return_type_to_result_simple_with_weird_forms() {
756 check_assist(
757 change_return_type_to_result,
758 r#"fn foo() -> i32<|> {
759 let test = "test";
760 if test == "test" {
761 return 24i32;
762 }
763 let mut i = 0;
764 loop {
765 if i == 1 {
766 break 55;
767 }
768 i += 1;
769 }
770 }"#,
771 r#"fn foo() -> Result<i32, ${0:_}> {
772 let test = "test";
773 if test == "test" {
774 return Ok(24i32);
775 }
776 let mut i = 0;
777 loop {
778 if i == 1 {
779 break Ok(55);
780 }
781 i += 1;
782 }
783 }"#,
784 );
785
786 check_assist(
787 change_return_type_to_result,
788 r#"fn foo() -> i32<|> {
789 let test = "test";
790 if test == "test" {
791 return 24i32;
792 }
793 let mut i = 0;
794 loop {
795 loop {
796 if i == 1 {
797 break 55;
798 }
799 i += 1;
800 }
801 }
802 }"#,
803 r#"fn foo() -> Result<i32, ${0:_}> {
804 let test = "test";
805 if test == "test" {
806 return Ok(24i32);
807 }
808 let mut i = 0;
809 loop {
810 loop {
811 if i == 1 {
812 break Ok(55);
813 }
814 i += 1;
815 }
816 }
817 }"#,
818 );
819
820 check_assist(
821 change_return_type_to_result,
822 r#"fn foo() -> i3<|>2 {
823 let test = "test";
824 let other = 5;
825 if test == "test" {
826 let res = match other {
827 5 => 43,
828 _ => return 56,
829 };
830 }
831 let mut i = 0;
832 loop {
833 loop {
834 if i == 1 {
835 break 55;
836 }
837 i += 1;
838 }
839 }
840 }"#,
841 r#"fn foo() -> Result<i32, ${0:_}> {
842 let test = "test";
843 let other = 5;
844 if test == "test" {
845 let res = match other {
846 5 => 43,
847 _ => return Ok(56),
848 };
849 }
850 let mut i = 0;
851 loop {
852 loop {
853 if i == 1 {
854 break Ok(55);
855 }
856 i += 1;
857 }
858 }
859 }"#,
860 );
861
862 check_assist(
863 change_return_type_to_result,
864 r#"fn foo(the_field: u32) -> u32<|> {
865 if the_field < 5 {
866 let mut i = 0;
867 loop {
868 if i > 5 {
869 return 55u32;
870 }
871 i += 3;
872 }
873
874 match i {
875 5 => return 99,
876 _ => return 0,
877 };
878 }
879
880 the_field
881 }"#,
882 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
883 if the_field < 5 {
884 let mut i = 0;
885 loop {
886 if i > 5 {
887 return Ok(55u32);
888 }
889 i += 3;
890 }
891
892 match i {
893 5 => return Ok(99),
894 _ => return Ok(0),
895 };
896 }
897
898 Ok(the_field)
899 }"#,
900 );
901
902 check_assist(
903 change_return_type_to_result,
904 r#"fn foo(the_field: u32) -> u3<|>2 {
905 if the_field < 5 {
906 let mut i = 0;
907
908 match i {
909 5 => return 99,
910 _ => return 0,
911 }
912 }
913
914 the_field
915 }"#,
916 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
917 if the_field < 5 {
918 let mut i = 0;
919
920 match i {
921 5 => return Ok(99),
922 _ => return Ok(0),
923 }
924 }
925
926 Ok(the_field)
927 }"#,
928 );
929
930 check_assist(
931 change_return_type_to_result,
932 r#"fn foo(the_field: u32) -> u32<|> {
933 if the_field < 5 {
934 let mut i = 0;
935
936 if i == 5 {
937 return 99
938 } else {
939 return 0
940 }
941 }
942
943 the_field
944 }"#,
945 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
946 if the_field < 5 {
947 let mut i = 0;
948
949 if i == 5 {
950 return Ok(99)
951 } else {
952 return Ok(0)
953 }
954 }
955
956 Ok(the_field)
957 }"#,
958 );
959
960 check_assist(
961 change_return_type_to_result,
962 r#"fn foo(the_field: u32) -> <|>u32 {
963 if the_field < 5 {
964 let mut i = 0;
965
966 if i == 5 {
967 return 99;
968 } else {
969 return 0;
970 }
971 }
972
973 the_field
974 }"#,
975 r#"fn foo(the_field: u32) -> Result<u32, ${0:_}> {
976 if the_field < 5 {
977 let mut i = 0;
978
979 if i == 5 {
980 return Ok(99);
981 } else {
982 return Ok(0);
983 }
984 }
985
986 Ok(the_field)
987 }"#,
988 );
989 }
990}
diff --git a/crates/ra_assists/src/handlers/change_visibility.rs b/crates/ra_assists/src/handlers/change_visibility.rs
deleted file mode 100644
index 724daa93f..000000000
--- a/crates/ra_assists/src/handlers/change_visibility.rs
+++ /dev/null
@@ -1,200 +0,0 @@
1use ra_syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, VISIBILITY},
5 T,
6};
7use test_utils::mark;
8
9use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: change_visibility
12//
13// Adds or changes existing visibility specifier.
14//
15// ```
16// <|>fn frobnicate() {}
17// ```
18// ->
19// ```
20// pub(crate) fn frobnicate() {}
21// ```
22pub(crate) fn change_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 if let Some(vis) = ctx.find_node_at_offset::<ast::Visibility>() {
24 return change_vis(acc, vis);
25 }
26 add_vis(acc, ctx)
27}
28
29fn add_vis(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let item_keyword = ctx.token_at_offset().find(|leaf| {
31 matches!(
32 leaf.kind(),
33 T![const] | T![static] | T![fn] | T![mod] | T![struct] | T![enum] | T![trait]
34 )
35 });
36
37 let (offset, target) = if let Some(keyword) = item_keyword {
38 let parent = keyword.parent();
39 let def_kws = vec![CONST, STATIC, FN, MODULE, STRUCT, ENUM, TRAIT];
40 // Parent is not a definition, can't add visibility
41 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
42 return None;
43 }
44 // Already have visibility, do nothing
45 if parent.children().any(|child| child.kind() == VISIBILITY) {
46 return None;
47 }
48 (vis_offset(&parent), keyword.text_range())
49 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
50 let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
51 if field.name()? != field_name {
52 mark::hit!(change_visibility_field_false_positive);
53 return None;
54 }
55 if field.visibility().is_some() {
56 return None;
57 }
58 (vis_offset(field.syntax()), field_name.syntax().text_range())
59 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
60 if field.visibility().is_some() {
61 return None;
62 }
63 (vis_offset(field.syntax()), field.syntax().text_range())
64 } else {
65 return None;
66 };
67
68 acc.add(
69 AssistId("change_visibility", AssistKind::RefactorRewrite),
70 "Change visibility to pub(crate)",
71 target,
72 |edit| {
73 edit.insert(offset, "pub(crate) ");
74 },
75 )
76}
77
78fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
79 if vis.syntax().text() == "pub" {
80 let target = vis.syntax().text_range();
81 return acc.add(
82 AssistId("change_visibility", AssistKind::RefactorRewrite),
83 "Change Visibility to pub(crate)",
84 target,
85 |edit| {
86 edit.replace(vis.syntax().text_range(), "pub(crate)");
87 },
88 );
89 }
90 if vis.syntax().text() == "pub(crate)" {
91 let target = vis.syntax().text_range();
92 return acc.add(
93 AssistId("change_visibility", AssistKind::RefactorRewrite),
94 "Change visibility to pub",
95 target,
96 |edit| {
97 edit.replace(vis.syntax().text_range(), "pub");
98 },
99 );
100 }
101 None
102}
103
104#[cfg(test)]
105mod tests {
106 use test_utils::mark;
107
108 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
109
110 use super::*;
111
112 #[test]
113 fn change_visibility_adds_pub_crate_to_items() {
114 check_assist(change_visibility, "<|>fn foo() {}", "pub(crate) fn foo() {}");
115 check_assist(change_visibility, "f<|>n foo() {}", "pub(crate) fn foo() {}");
116 check_assist(change_visibility, "<|>struct Foo {}", "pub(crate) struct Foo {}");
117 check_assist(change_visibility, "<|>mod foo {}", "pub(crate) mod foo {}");
118 check_assist(change_visibility, "<|>trait Foo {}", "pub(crate) trait Foo {}");
119 check_assist(change_visibility, "m<|>od {}", "pub(crate) mod {}");
120 check_assist(change_visibility, "unsafe f<|>n foo() {}", "pub(crate) unsafe fn foo() {}");
121 }
122
123 #[test]
124 fn change_visibility_works_with_struct_fields() {
125 check_assist(
126 change_visibility,
127 r"struct S { <|>field: u32 }",
128 r"struct S { pub(crate) field: u32 }",
129 );
130 check_assist(change_visibility, r"struct S ( <|>u32 )", r"struct S ( pub(crate) u32 )");
131 }
132
133 #[test]
134 fn change_visibility_field_false_positive() {
135 mark::check!(change_visibility_field_false_positive);
136 check_assist_not_applicable(
137 change_visibility,
138 r"struct S { field: [(); { let <|>x = ();}] }",
139 )
140 }
141
142 #[test]
143 fn change_visibility_pub_to_pub_crate() {
144 check_assist(change_visibility, "<|>pub fn foo() {}", "pub(crate) fn foo() {}")
145 }
146
147 #[test]
148 fn change_visibility_pub_crate_to_pub() {
149 check_assist(change_visibility, "<|>pub(crate) fn foo() {}", "pub fn foo() {}")
150 }
151
152 #[test]
153 fn change_visibility_const() {
154 check_assist(change_visibility, "<|>const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
155 }
156
157 #[test]
158 fn change_visibility_static() {
159 check_assist(change_visibility, "<|>static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
160 }
161
162 #[test]
163 fn change_visibility_handles_comment_attrs() {
164 check_assist(
165 change_visibility,
166 r"
167 /// docs
168
169 // comments
170
171 #[derive(Debug)]
172 <|>struct Foo;
173 ",
174 r"
175 /// docs
176
177 // comments
178
179 #[derive(Debug)]
180 pub(crate) struct Foo;
181 ",
182 )
183 }
184
185 #[test]
186 fn not_applicable_for_enum_variants() {
187 check_assist_not_applicable(
188 change_visibility,
189 r"mod foo { pub enum Foo {Foo1} }
190 fn main() { foo::Foo::Foo1<|> } ",
191 );
192 }
193
194 #[test]
195 fn change_visibility_target() {
196 check_assist_target(change_visibility, "<|>fn foo() {}", "fn");
197 check_assist_target(change_visibility, "pub(crate)<|> fn foo() {}", "pub(crate)");
198 check_assist_target(change_visibility, "struct S { <|>field: u32 }", "field");
199 }
200}
diff --git a/crates/ra_assists/src/handlers/early_return.rs b/crates/ra_assists/src/handlers/early_return.rs
deleted file mode 100644
index 3650289fd..000000000
--- a/crates/ra_assists/src/handlers/early_return.rs
+++ /dev/null
@@ -1,515 +0,0 @@
1use std::{iter::once, ops::RangeInclusive};
2
3use ra_syntax::{
4 algo::replace_children,
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
10 AstNode,
11 SyntaxKind::{FN, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
12 SyntaxNode,
13};
14
15use crate::{
16 assist_context::{AssistContext, Assists},
17 utils::invert_boolean_expression,
18 AssistId, AssistKind,
19};
20
21// Assist: convert_to_guarded_return
22//
23// Replace a large conditional with a guarded return.
24//
25// ```
26// fn main() {
27// <|>if cond {
28// foo();
29// bar();
30// }
31// }
32// ```
33// ->
34// ```
35// fn main() {
36// if !cond {
37// return;
38// }
39// foo();
40// bar();
41// }
42// ```
43pub(crate) fn convert_to_guarded_return(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
45 if if_expr.else_branch().is_some() {
46 return None;
47 }
48
49 let cond = if_expr.condition()?;
50
51 // Check if there is an IfLet that we can handle.
52 let if_let_pat = match cond.pat() {
53 None => None, // No IfLet, supported.
54 Some(ast::Pat::TupleStructPat(pat)) if pat.args().count() == 1 => {
55 let path = pat.path()?;
56 match path.qualifier() {
57 None => {
58 let bound_ident = pat.args().next().unwrap();
59 Some((path, bound_ident))
60 }
61 Some(_) => return None,
62 }
63 }
64 Some(_) => return None, // Unsupported IfLet.
65 };
66
67 let cond_expr = cond.expr()?;
68 let then_block = if_expr.then_branch()?;
69
70 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::BlockExpr::cast)?;
71
72 if parent_block.expr()? != if_expr.clone().into() {
73 return None;
74 }
75
76 // check for early return and continue
77 let first_in_then_block = then_block.syntax().first_child()?;
78 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
79 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
80 || first_in_then_block
81 .children()
82 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
83 {
84 return None;
85 }
86
87 let parent_container = parent_block.syntax().parent()?;
88
89 let early_expression: ast::Expr = match parent_container.kind() {
90 WHILE_EXPR | LOOP_EXPR => make::expr_continue(),
91 FN => make::expr_return(),
92 _ => return None,
93 };
94
95 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
96 return None;
97 }
98
99 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
100
101 let target = if_expr.syntax().text_range();
102 acc.add(
103 AssistId("convert_to_guarded_return", AssistKind::RefactorRewrite),
104 "Convert to guarded return",
105 target,
106 |edit| {
107 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
108 let new_block = match if_let_pat {
109 None => {
110 // If.
111 let new_expr = {
112 let then_branch =
113 make::block_expr(once(make::expr_stmt(early_expression).into()), None);
114 let cond = invert_boolean_expression(cond_expr);
115 make::expr_if(make::condition(cond, None), then_branch)
116 .indent(if_indent_level)
117 };
118 replace(new_expr.syntax(), &then_block, &parent_block, &if_expr)
119 }
120 Some((path, bound_ident)) => {
121 // If-let.
122 let match_expr = {
123 let happy_arm = {
124 let pat = make::tuple_struct_pat(
125 path,
126 once(make::bind_pat(make::name("it")).into()),
127 );
128 let expr = {
129 let name_ref = make::name_ref("it");
130 let segment = make::path_segment(name_ref);
131 let path = make::path_unqualified(segment);
132 make::expr_path(path)
133 };
134 make::match_arm(once(pat.into()), expr)
135 };
136
137 let sad_arm = make::match_arm(
138 // FIXME: would be cool to use `None` or `Err(_)` if appropriate
139 once(make::placeholder_pat().into()),
140 early_expression,
141 );
142
143 make::expr_match(cond_expr, make::match_arm_list(vec![happy_arm, sad_arm]))
144 };
145
146 let let_stmt = make::let_stmt(
147 make::bind_pat(make::name(&bound_ident.syntax().to_string())).into(),
148 Some(match_expr),
149 );
150 let let_stmt = let_stmt.indent(if_indent_level);
151 replace(let_stmt.syntax(), &then_block, &parent_block, &if_expr)
152 }
153 };
154 edit.replace_ast(parent_block, ast::BlockExpr::cast(new_block).unwrap());
155
156 fn replace(
157 new_expr: &SyntaxNode,
158 then_block: &ast::BlockExpr,
159 parent_block: &ast::BlockExpr,
160 if_expr: &ast::IfExpr,
161 ) -> SyntaxNode {
162 let then_block_items = then_block.dedent(IndentLevel(1));
163 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
164 let end_of_then =
165 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
166 end_of_then.prev_sibling_or_token().unwrap()
167 } else {
168 end_of_then
169 };
170 let mut then_statements = new_expr.children_with_tokens().chain(
171 then_block_items
172 .syntax()
173 .children_with_tokens()
174 .skip(1)
175 .take_while(|i| *i != end_of_then),
176 );
177 replace_children(
178 &parent_block.syntax(),
179 RangeInclusive::new(
180 if_expr.clone().syntax().clone().into(),
181 if_expr.syntax().clone().into(),
182 ),
183 &mut then_statements,
184 )
185 }
186 },
187 )
188}
189
190#[cfg(test)]
191mod tests {
192 use crate::tests::{check_assist, check_assist_not_applicable};
193
194 use super::*;
195
196 #[test]
197 fn convert_inside_fn() {
198 check_assist(
199 convert_to_guarded_return,
200 r#"
201 fn main() {
202 bar();
203 if<|> true {
204 foo();
205
206 //comment
207 bar();
208 }
209 }
210 "#,
211 r#"
212 fn main() {
213 bar();
214 if !true {
215 return;
216 }
217 foo();
218
219 //comment
220 bar();
221 }
222 "#,
223 );
224 }
225
226 #[test]
227 fn convert_let_inside_fn() {
228 check_assist(
229 convert_to_guarded_return,
230 r#"
231 fn main(n: Option<String>) {
232 bar();
233 if<|> let Some(n) = n {
234 foo(n);
235
236 //comment
237 bar();
238 }
239 }
240 "#,
241 r#"
242 fn main(n: Option<String>) {
243 bar();
244 let n = match n {
245 Some(it) => it,
246 _ => return,
247 };
248 foo(n);
249
250 //comment
251 bar();
252 }
253 "#,
254 );
255 }
256
257 #[test]
258 fn convert_if_let_result() {
259 check_assist(
260 convert_to_guarded_return,
261 r#"
262 fn main() {
263 if<|> let Ok(x) = Err(92) {
264 foo(x);
265 }
266 }
267 "#,
268 r#"
269 fn main() {
270 let x = match Err(92) {
271 Ok(it) => it,
272 _ => return,
273 };
274 foo(x);
275 }
276 "#,
277 );
278 }
279
280 #[test]
281 fn convert_let_ok_inside_fn() {
282 check_assist(
283 convert_to_guarded_return,
284 r#"
285 fn main(n: Option<String>) {
286 bar();
287 if<|> let Ok(n) = n {
288 foo(n);
289
290 //comment
291 bar();
292 }
293 }
294 "#,
295 r#"
296 fn main(n: Option<String>) {
297 bar();
298 let n = match n {
299 Ok(it) => it,
300 _ => return,
301 };
302 foo(n);
303
304 //comment
305 bar();
306 }
307 "#,
308 );
309 }
310
311 #[test]
312 fn convert_inside_while() {
313 check_assist(
314 convert_to_guarded_return,
315 r#"
316 fn main() {
317 while true {
318 if<|> true {
319 foo();
320 bar();
321 }
322 }
323 }
324 "#,
325 r#"
326 fn main() {
327 while true {
328 if !true {
329 continue;
330 }
331 foo();
332 bar();
333 }
334 }
335 "#,
336 );
337 }
338
339 #[test]
340 fn convert_let_inside_while() {
341 check_assist(
342 convert_to_guarded_return,
343 r#"
344 fn main() {
345 while true {
346 if<|> let Some(n) = n {
347 foo(n);
348 bar();
349 }
350 }
351 }
352 "#,
353 r#"
354 fn main() {
355 while true {
356 let n = match n {
357 Some(it) => it,
358 _ => continue,
359 };
360 foo(n);
361 bar();
362 }
363 }
364 "#,
365 );
366 }
367
368 #[test]
369 fn convert_inside_loop() {
370 check_assist(
371 convert_to_guarded_return,
372 r#"
373 fn main() {
374 loop {
375 if<|> true {
376 foo();
377 bar();
378 }
379 }
380 }
381 "#,
382 r#"
383 fn main() {
384 loop {
385 if !true {
386 continue;
387 }
388 foo();
389 bar();
390 }
391 }
392 "#,
393 );
394 }
395
396 #[test]
397 fn convert_let_inside_loop() {
398 check_assist(
399 convert_to_guarded_return,
400 r#"
401 fn main() {
402 loop {
403 if<|> let Some(n) = n {
404 foo(n);
405 bar();
406 }
407 }
408 }
409 "#,
410 r#"
411 fn main() {
412 loop {
413 let n = match n {
414 Some(it) => it,
415 _ => continue,
416 };
417 foo(n);
418 bar();
419 }
420 }
421 "#,
422 );
423 }
424
425 #[test]
426 fn ignore_already_converted_if() {
427 check_assist_not_applicable(
428 convert_to_guarded_return,
429 r#"
430 fn main() {
431 if<|> true {
432 return;
433 }
434 }
435 "#,
436 );
437 }
438
439 #[test]
440 fn ignore_already_converted_loop() {
441 check_assist_not_applicable(
442 convert_to_guarded_return,
443 r#"
444 fn main() {
445 loop {
446 if<|> true {
447 continue;
448 }
449 }
450 }
451 "#,
452 );
453 }
454
455 #[test]
456 fn ignore_return() {
457 check_assist_not_applicable(
458 convert_to_guarded_return,
459 r#"
460 fn main() {
461 if<|> true {
462 return
463 }
464 }
465 "#,
466 );
467 }
468
469 #[test]
470 fn ignore_else_branch() {
471 check_assist_not_applicable(
472 convert_to_guarded_return,
473 r#"
474 fn main() {
475 if<|> true {
476 foo();
477 } else {
478 bar()
479 }
480 }
481 "#,
482 );
483 }
484
485 #[test]
486 fn ignore_statements_aftert_if() {
487 check_assist_not_applicable(
488 convert_to_guarded_return,
489 r#"
490 fn main() {
491 if<|> true {
492 foo();
493 }
494 bar();
495 }
496 "#,
497 );
498 }
499
500 #[test]
501 fn ignore_statements_inside_if() {
502 check_assist_not_applicable(
503 convert_to_guarded_return,
504 r#"
505 fn main() {
506 if false {
507 if<|> true {
508 foo();
509 }
510 }
511 }
512 "#,
513 );
514 }
515}
diff --git a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
deleted file mode 100644
index ccec688ca..000000000
--- a/crates/ra_assists/src/handlers/extract_struct_from_enum_variant.rs
+++ /dev/null
@@ -1,321 +0,0 @@
1use hir::{EnumVariant, Module, ModuleDef, Name};
2use ra_db::FileId;
3use ra_fmt::leading_indent;
4use ra_ide_db::{defs::Definition, search::Reference, RootDatabase};
5use ra_syntax::{
6 algo::find_node_at_offset,
7 ast::{self, ArgListOwner, AstNode, NameOwner, VisibilityOwner},
8 SourceFile, SyntaxNode, TextRange, TextSize,
9};
10use rustc_hash::FxHashSet;
11
12use crate::{
13 assist_context::AssistBuilder, utils::insert_use_statement, AssistContext, AssistId,
14 AssistKind, Assists,
15};
16
17// Assist: extract_struct_from_enum_variant
18//
19// Extracts a struct from enum variant.
20//
21// ```
22// enum A { <|>One(u32, u32) }
23// ```
24// ->
25// ```
26// struct One(pub u32, pub u32);
27//
28// enum A { One(One) }
29// ```
30pub(crate) fn extract_struct_from_enum_variant(
31 acc: &mut Assists,
32 ctx: &AssistContext,
33) -> Option<()> {
34 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
35 let field_list = match variant.kind() {
36 ast::StructKind::Tuple(field_list) => field_list,
37 _ => return None,
38 };
39 let variant_name = variant.name()?.to_string();
40 let variant_hir = ctx.sema.to_def(&variant)?;
41 if existing_struct_def(ctx.db(), &variant_name, &variant_hir) {
42 return None;
43 }
44 let enum_ast = variant.parent_enum();
45 let visibility = enum_ast.visibility();
46 let enum_hir = ctx.sema.to_def(&enum_ast)?;
47 let variant_hir_name = variant_hir.name(ctx.db());
48 let enum_module_def = ModuleDef::from(enum_hir);
49 let current_module = enum_hir.module(ctx.db());
50 let target = variant.syntax().text_range();
51 acc.add(
52 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
53 "Extract struct from enum variant",
54 target,
55 |builder| {
56 let definition = Definition::ModuleDef(ModuleDef::EnumVariant(variant_hir));
57 let res = definition.find_usages(&ctx.sema, None);
58 let start_offset = variant.parent_enum().syntax().text_range().start();
59 let mut visited_modules_set = FxHashSet::default();
60 visited_modules_set.insert(current_module);
61 for reference in res {
62 let source_file = ctx.sema.parse(reference.file_range.file_id);
63 update_reference(
64 ctx,
65 builder,
66 reference,
67 &source_file,
68 &enum_module_def,
69 &variant_hir_name,
70 &mut visited_modules_set,
71 );
72 }
73 extract_struct_def(
74 builder,
75 enum_ast.syntax(),
76 &variant_name,
77 &field_list.to_string(),
78 start_offset,
79 ctx.frange.file_id,
80 &visibility,
81 );
82 let list_range = field_list.syntax().text_range();
83 update_variant(builder, &variant_name, ctx.frange.file_id, list_range);
84 },
85 )
86}
87
88fn existing_struct_def(db: &RootDatabase, variant_name: &str, variant: &EnumVariant) -> bool {
89 variant
90 .parent_enum(db)
91 .module(db)
92 .scope(db, None)
93 .into_iter()
94 .any(|(name, _)| name.to_string() == variant_name.to_string())
95}
96
97fn insert_import(
98 ctx: &AssistContext,
99 builder: &mut AssistBuilder,
100 path: &ast::PathExpr,
101 module: &Module,
102 enum_module_def: &ModuleDef,
103 variant_hir_name: &Name,
104) -> Option<()> {
105 let db = ctx.db();
106 let mod_path = module.find_use_path(db, enum_module_def.clone());
107 if let Some(mut mod_path) = mod_path {
108 mod_path.segments.pop();
109 mod_path.segments.push(variant_hir_name.clone());
110 insert_use_statement(path.syntax(), &mod_path, ctx, builder.text_edit_builder());
111 }
112 Some(())
113}
114
115fn extract_struct_def(
116 builder: &mut AssistBuilder,
117 enum_ast: &SyntaxNode,
118 variant_name: &str,
119 variant_list: &str,
120 start_offset: TextSize,
121 file_id: FileId,
122 visibility: &Option<ast::Visibility>,
123) -> Option<()> {
124 let visibility_string = if let Some(visibility) = visibility {
125 format!("{} ", visibility.to_string())
126 } else {
127 "".to_string()
128 };
129 let indent = if let Some(indent) = leading_indent(enum_ast) {
130 indent.to_string()
131 } else {
132 "".to_string()
133 };
134 let struct_def = format!(
135 r#"{}struct {}{};
136
137{}"#,
138 visibility_string,
139 variant_name,
140 list_with_visibility(variant_list),
141 indent
142 );
143 builder.edit_file(file_id);
144 builder.insert(start_offset, struct_def);
145 Some(())
146}
147
148fn update_variant(
149 builder: &mut AssistBuilder,
150 variant_name: &str,
151 file_id: FileId,
152 list_range: TextRange,
153) -> Option<()> {
154 let inside_variant_range = TextRange::new(
155 list_range.start().checked_add(TextSize::from(1))?,
156 list_range.end().checked_sub(TextSize::from(1))?,
157 );
158 builder.edit_file(file_id);
159 builder.replace(inside_variant_range, variant_name);
160 Some(())
161}
162
163fn update_reference(
164 ctx: &AssistContext,
165 builder: &mut AssistBuilder,
166 reference: Reference,
167 source_file: &SourceFile,
168 enum_module_def: &ModuleDef,
169 variant_hir_name: &Name,
170 visited_modules_set: &mut FxHashSet<Module>,
171) -> Option<()> {
172 let path_expr: ast::PathExpr = find_node_at_offset::<ast::PathExpr>(
173 source_file.syntax(),
174 reference.file_range.range.start(),
175 )?;
176 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
177 let list = call.arg_list()?;
178 let segment = path_expr.path()?.segment()?;
179 let module = ctx.sema.scope(&path_expr.syntax()).module()?;
180 let list_range = list.syntax().text_range();
181 let inside_list_range = TextRange::new(
182 list_range.start().checked_add(TextSize::from(1))?,
183 list_range.end().checked_sub(TextSize::from(1))?,
184 );
185 builder.edit_file(reference.file_range.file_id);
186 if !visited_modules_set.contains(&module) {
187 if insert_import(ctx, builder, &path_expr, &module, enum_module_def, variant_hir_name)
188 .is_some()
189 {
190 visited_modules_set.insert(module);
191 }
192 }
193 builder.replace(inside_list_range, format!("{}{}", segment, list));
194 Some(())
195}
196
197fn list_with_visibility(list: &str) -> String {
198 list.split(',')
199 .map(|part| {
200 let index = if part.chars().next().unwrap() == '(' { 1usize } else { 0 };
201 let mut mod_part = part.trim().to_string();
202 mod_part.insert_str(index, "pub ");
203 mod_part
204 })
205 .collect::<Vec<String>>()
206 .join(", ")
207}
208
209#[cfg(test)]
210mod tests {
211
212 use crate::{
213 tests::{check_assist, check_assist_not_applicable},
214 utils::FamousDefs,
215 };
216
217 use super::*;
218
219 #[test]
220 fn test_extract_struct_several_fields() {
221 check_assist(
222 extract_struct_from_enum_variant,
223 "enum A { <|>One(u32, u32) }",
224 r#"struct One(pub u32, pub u32);
225
226enum A { One(One) }"#,
227 );
228 }
229
230 #[test]
231 fn test_extract_struct_one_field() {
232 check_assist(
233 extract_struct_from_enum_variant,
234 "enum A { <|>One(u32) }",
235 r#"struct One(pub u32);
236
237enum A { One(One) }"#,
238 );
239 }
240
241 #[test]
242 fn test_extract_struct_pub_visibility() {
243 check_assist(
244 extract_struct_from_enum_variant,
245 "pub enum A { <|>One(u32, u32) }",
246 r#"pub struct One(pub u32, pub u32);
247
248pub enum A { One(One) }"#,
249 );
250 }
251
252 #[test]
253 fn test_extract_struct_with_complex_imports() {
254 check_assist(
255 extract_struct_from_enum_variant,
256 r#"mod my_mod {
257 fn another_fn() {
258 let m = my_other_mod::MyEnum::MyField(1, 1);
259 }
260
261 pub mod my_other_mod {
262 fn another_fn() {
263 let m = MyEnum::MyField(1, 1);
264 }
265
266 pub enum MyEnum {
267 <|>MyField(u8, u8),
268 }
269 }
270}
271
272fn another_fn() {
273 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
274}"#,
275 r#"use my_mod::my_other_mod::MyField;
276
277mod my_mod {
278 use my_other_mod::MyField;
279
280 fn another_fn() {
281 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
282 }
283
284 pub mod my_other_mod {
285 fn another_fn() {
286 let m = MyEnum::MyField(MyField(1, 1));
287 }
288
289 pub struct MyField(pub u8, pub u8);
290
291 pub enum MyEnum {
292 MyField(MyField),
293 }
294 }
295}
296
297fn another_fn() {
298 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
299}"#,
300 );
301 }
302
303 fn check_not_applicable(ra_fixture: &str) {
304 let fixture =
305 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
306 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
307 }
308
309 #[test]
310 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
311 check_not_applicable("enum A { <|>One }");
312 }
313
314 #[test]
315 fn test_extract_enum_not_applicable_if_struct_exists() {
316 check_not_applicable(
317 r#"struct One;
318 enum A { <|>One(u8) }"#,
319 );
320 }
321}
diff --git a/crates/ra_assists/src/handlers/extract_variable.rs b/crates/ra_assists/src/handlers/extract_variable.rs
deleted file mode 100644
index b925a2884..000000000
--- a/crates/ra_assists/src/handlers/extract_variable.rs
+++ /dev/null
@@ -1,588 +0,0 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 SyntaxKind::{
4 BLOCK_EXPR, BREAK_EXPR, COMMENT, LAMBDA_EXPR, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
5 },
6 SyntaxNode,
7};
8use stdx::format_to;
9use test_utils::mark;
10
11use crate::{AssistContext, AssistId, AssistKind, Assists};
12
13// Assist: extract_variable
14//
15// Extracts subexpression into a variable.
16//
17// ```
18// fn main() {
19// <|>(1 + 2)<|> * 4;
20// }
21// ```
22// ->
23// ```
24// fn main() {
25// let $0var_name = (1 + 2);
26// var_name * 4;
27// }
28// ```
29pub(crate) fn extract_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 if ctx.frange.range.is_empty() {
31 return None;
32 }
33 let node = ctx.covering_element();
34 if node.kind() == COMMENT {
35 mark::hit!(extract_var_in_comment_is_not_applicable);
36 return None;
37 }
38 let to_extract = node.ancestors().find_map(valid_target_expr)?;
39 let anchor = Anchor::from(&to_extract)?;
40 let indent = anchor.syntax().prev_sibling_or_token()?.as_token()?.clone();
41 let target = to_extract.syntax().text_range();
42 acc.add(
43 AssistId("extract_variable", AssistKind::RefactorExtract),
44 "Extract into variable",
45 target,
46 move |edit| {
47 let field_shorthand =
48 match to_extract.syntax().parent().and_then(ast::RecordExprField::cast) {
49 Some(field) => field.name_ref(),
50 None => None,
51 };
52
53 let mut buf = String::new();
54
55 let var_name = match &field_shorthand {
56 Some(it) => it.to_string(),
57 None => "var_name".to_string(),
58 };
59 let expr_range = match &field_shorthand {
60 Some(it) => it.syntax().text_range().cover(to_extract.syntax().text_range()),
61 None => to_extract.syntax().text_range(),
62 };
63
64 if let Anchor::WrapInBlock(_) = anchor {
65 format_to!(buf, "{{ let {} = ", var_name);
66 } else {
67 format_to!(buf, "let {} = ", var_name);
68 };
69 format_to!(buf, "{}", to_extract.syntax());
70
71 if let Anchor::Replace(stmt) = anchor {
72 mark::hit!(test_extract_var_expr_stmt);
73 if stmt.semicolon_token().is_none() {
74 buf.push_str(";");
75 }
76 match ctx.config.snippet_cap {
77 Some(cap) => {
78 let snip = buf
79 .replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
80 edit.replace_snippet(cap, expr_range, snip)
81 }
82 None => edit.replace(expr_range, buf),
83 }
84 return;
85 }
86
87 buf.push_str(";");
88
89 // We want to maintain the indent level,
90 // but we do not want to duplicate possible
91 // extra newlines in the indent block
92 let text = indent.text();
93 if text.starts_with('\n') {
94 buf.push_str("\n");
95 buf.push_str(text.trim_start_matches('\n'));
96 } else {
97 buf.push_str(text);
98 }
99
100 edit.replace(expr_range, var_name.clone());
101 let offset = anchor.syntax().text_range().start();
102 match ctx.config.snippet_cap {
103 Some(cap) => {
104 let snip =
105 buf.replace(&format!("let {}", var_name), &format!("let $0{}", var_name));
106 edit.insert_snippet(cap, offset, snip)
107 }
108 None => edit.insert(offset, buf),
109 }
110
111 if let Anchor::WrapInBlock(_) = anchor {
112 edit.insert(anchor.syntax().text_range().end(), " }");
113 }
114 },
115 )
116}
117
118/// Check whether the node is a valid expression which can be extracted to a variable.
119/// In general that's true for any expression, but in some cases that would produce invalid code.
120fn valid_target_expr(node: SyntaxNode) -> Option<ast::Expr> {
121 match node.kind() {
122 PATH_EXPR | LOOP_EXPR => None,
123 BREAK_EXPR => ast::BreakExpr::cast(node).and_then(|e| e.expr()),
124 RETURN_EXPR => ast::ReturnExpr::cast(node).and_then(|e| e.expr()),
125 BLOCK_EXPR => {
126 ast::BlockExpr::cast(node).filter(|it| it.is_standalone()).map(ast::Expr::from)
127 }
128 _ => ast::Expr::cast(node),
129 }
130}
131
132enum Anchor {
133 Before(SyntaxNode),
134 Replace(ast::ExprStmt),
135 WrapInBlock(SyntaxNode),
136}
137
138impl Anchor {
139 fn from(to_extract: &ast::Expr) -> Option<Anchor> {
140 to_extract.syntax().ancestors().find_map(|node| {
141 if let Some(expr) =
142 node.parent().and_then(ast::BlockExpr::cast).and_then(|it| it.expr())
143 {
144 if expr.syntax() == &node {
145 mark::hit!(test_extract_var_last_expr);
146 return Some(Anchor::Before(node));
147 }
148 }
149
150 if let Some(parent) = node.parent() {
151 if parent.kind() == MATCH_ARM || parent.kind() == LAMBDA_EXPR {
152 return Some(Anchor::WrapInBlock(node));
153 }
154 }
155
156 if let Some(stmt) = ast::Stmt::cast(node.clone()) {
157 if let ast::Stmt::ExprStmt(stmt) = stmt {
158 if stmt.expr().as_ref() == Some(to_extract) {
159 return Some(Anchor::Replace(stmt));
160 }
161 }
162 return Some(Anchor::Before(node));
163 }
164 None
165 })
166 }
167
168 fn syntax(&self) -> &SyntaxNode {
169 match self {
170 Anchor::Before(it) | Anchor::WrapInBlock(it) => it,
171 Anchor::Replace(stmt) => stmt.syntax(),
172 }
173 }
174}
175
176#[cfg(test)]
177mod tests {
178 use test_utils::mark;
179
180 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
181
182 use super::*;
183
184 #[test]
185 fn test_extract_var_simple() {
186 check_assist(
187 extract_variable,
188 r#"
189fn foo() {
190 foo(<|>1 + 1<|>);
191}"#,
192 r#"
193fn foo() {
194 let $0var_name = 1 + 1;
195 foo(var_name);
196}"#,
197 );
198 }
199
200 #[test]
201 fn extract_var_in_comment_is_not_applicable() {
202 mark::check!(extract_var_in_comment_is_not_applicable);
203 check_assist_not_applicable(extract_variable, "fn main() { 1 + /* <|>comment<|> */ 1; }");
204 }
205
206 #[test]
207 fn test_extract_var_expr_stmt() {
208 mark::check!(test_extract_var_expr_stmt);
209 check_assist(
210 extract_variable,
211 r#"
212fn foo() {
213 <|>1 + 1<|>;
214}"#,
215 r#"
216fn foo() {
217 let $0var_name = 1 + 1;
218}"#,
219 );
220 check_assist(
221 extract_variable,
222 "
223fn foo() {
224 <|>{ let x = 0; x }<|>
225 something_else();
226}",
227 "
228fn foo() {
229 let $0var_name = { let x = 0; x };
230 something_else();
231}",
232 );
233 }
234
235 #[test]
236 fn test_extract_var_part_of_expr_stmt() {
237 check_assist(
238 extract_variable,
239 "
240fn foo() {
241 <|>1<|> + 1;
242}",
243 "
244fn foo() {
245 let $0var_name = 1;
246 var_name + 1;
247}",
248 );
249 }
250
251 #[test]
252 fn test_extract_var_last_expr() {
253 mark::check!(test_extract_var_last_expr);
254 check_assist(
255 extract_variable,
256 r#"
257fn foo() {
258 bar(<|>1 + 1<|>)
259}
260"#,
261 r#"
262fn foo() {
263 let $0var_name = 1 + 1;
264 bar(var_name)
265}
266"#,
267 );
268 check_assist(
269 extract_variable,
270 r#"
271fn foo() {
272 <|>bar(1 + 1)<|>
273}
274"#,
275 r#"
276fn foo() {
277 let $0var_name = bar(1 + 1);
278 var_name
279}
280"#,
281 )
282 }
283
284 #[test]
285 fn test_extract_var_in_match_arm_no_block() {
286 check_assist(
287 extract_variable,
288 "
289fn main() {
290 let x = true;
291 let tuple = match x {
292 true => (<|>2 + 2<|>, true)
293 _ => (0, false)
294 };
295}
296",
297 "
298fn main() {
299 let x = true;
300 let tuple = match x {
301 true => { let $0var_name = 2 + 2; (var_name, true) }
302 _ => (0, false)
303 };
304}
305",
306 );
307 }
308
309 #[test]
310 fn test_extract_var_in_match_arm_with_block() {
311 check_assist(
312 extract_variable,
313 "
314fn main() {
315 let x = true;
316 let tuple = match x {
317 true => {
318 let y = 1;
319 (<|>2 + y<|>, true)
320 }
321 _ => (0, false)
322 };
323}
324",
325 "
326fn main() {
327 let x = true;
328 let tuple = match x {
329 true => {
330 let y = 1;
331 let $0var_name = 2 + y;
332 (var_name, true)
333 }
334 _ => (0, false)
335 };
336}
337",
338 );
339 }
340
341 #[test]
342 fn test_extract_var_in_closure_no_block() {
343 check_assist(
344 extract_variable,
345 "
346fn main() {
347 let lambda = |x: u32| <|>x * 2<|>;
348}
349",
350 "
351fn main() {
352 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
353}
354",
355 );
356 }
357
358 #[test]
359 fn test_extract_var_in_closure_with_block() {
360 check_assist(
361 extract_variable,
362 "
363fn main() {
364 let lambda = |x: u32| { <|>x * 2<|> };
365}
366",
367 "
368fn main() {
369 let lambda = |x: u32| { let $0var_name = x * 2; var_name };
370}
371",
372 );
373 }
374
375 #[test]
376 fn test_extract_var_path_simple() {
377 check_assist(
378 extract_variable,
379 "
380fn main() {
381 let o = <|>Some(true)<|>;
382}
383",
384 "
385fn main() {
386 let $0var_name = Some(true);
387 let o = var_name;
388}
389",
390 );
391 }
392
393 #[test]
394 fn test_extract_var_path_method() {
395 check_assist(
396 extract_variable,
397 "
398fn main() {
399 let v = <|>bar.foo()<|>;
400}
401",
402 "
403fn main() {
404 let $0var_name = bar.foo();
405 let v = var_name;
406}
407",
408 );
409 }
410
411 #[test]
412 fn test_extract_var_return() {
413 check_assist(
414 extract_variable,
415 "
416fn foo() -> u32 {
417 <|>return 2 + 2<|>;
418}
419",
420 "
421fn foo() -> u32 {
422 let $0var_name = 2 + 2;
423 return var_name;
424}
425",
426 );
427 }
428
429 #[test]
430 fn test_extract_var_does_not_add_extra_whitespace() {
431 check_assist(
432 extract_variable,
433 "
434fn foo() -> u32 {
435
436
437 <|>return 2 + 2<|>;
438}
439",
440 "
441fn foo() -> u32 {
442
443
444 let $0var_name = 2 + 2;
445 return var_name;
446}
447",
448 );
449
450 check_assist(
451 extract_variable,
452 "
453fn foo() -> u32 {
454
455 <|>return 2 + 2<|>;
456}
457",
458 "
459fn foo() -> u32 {
460
461 let $0var_name = 2 + 2;
462 return var_name;
463}
464",
465 );
466
467 check_assist(
468 extract_variable,
469 "
470fn foo() -> u32 {
471 let foo = 1;
472
473 // bar
474
475
476 <|>return 2 + 2<|>;
477}
478",
479 "
480fn foo() -> u32 {
481 let foo = 1;
482
483 // bar
484
485
486 let $0var_name = 2 + 2;
487 return var_name;
488}
489",
490 );
491 }
492
493 #[test]
494 fn test_extract_var_break() {
495 check_assist(
496 extract_variable,
497 "
498fn main() {
499 let result = loop {
500 <|>break 2 + 2<|>;
501 };
502}
503",
504 "
505fn main() {
506 let result = loop {
507 let $0var_name = 2 + 2;
508 break var_name;
509 };
510}
511",
512 );
513 }
514
515 #[test]
516 fn test_extract_var_for_cast() {
517 check_assist(
518 extract_variable,
519 "
520fn main() {
521 let v = <|>0f32 as u32<|>;
522}
523",
524 "
525fn main() {
526 let $0var_name = 0f32 as u32;
527 let v = var_name;
528}
529",
530 );
531 }
532
533 #[test]
534 fn extract_var_field_shorthand() {
535 check_assist(
536 extract_variable,
537 r#"
538struct S {
539 foo: i32
540}
541
542fn main() {
543 S { foo: <|>1 + 1<|> }
544}
545"#,
546 r#"
547struct S {
548 foo: i32
549}
550
551fn main() {
552 let $0foo = 1 + 1;
553 S { foo }
554}
555"#,
556 )
557 }
558
559 #[test]
560 fn test_extract_var_for_return_not_applicable() {
561 check_assist_not_applicable(extract_variable, "fn foo() { <|>return<|>; } ");
562 }
563
564 #[test]
565 fn test_extract_var_for_break_not_applicable() {
566 check_assist_not_applicable(extract_variable, "fn main() { loop { <|>break<|>; }; }");
567 }
568
569 // FIXME: This is not quite correct, but good enough(tm) for the sorting heuristic
570 #[test]
571 fn extract_var_target() {
572 check_assist_target(extract_variable, "fn foo() -> u32 { <|>return 2 + 2<|>; }", "2 + 2");
573
574 check_assist_target(
575 extract_variable,
576 "
577fn main() {
578 let x = true;
579 let tuple = match x {
580 true => (<|>2 + 2<|>, true)
581 _ => (0, false)
582 };
583}
584",
585 "2 + 2",
586 );
587 }
588}
diff --git a/crates/ra_assists/src/handlers/fill_match_arms.rs b/crates/ra_assists/src/handlers/fill_match_arms.rs
deleted file mode 100644
index 708e1bc6c..000000000
--- a/crates/ra_assists/src/handlers/fill_match_arms.rs
+++ /dev/null
@@ -1,750 +0,0 @@
1use std::iter;
2
3use hir::{Adt, HasSource, ModuleDef, Semantics};
4use itertools::Itertools;
5use ra_ide_db::RootDatabase;
6use ra_syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
7use test_utils::mark;
8
9use crate::{
10 utils::{render_snippet, Cursor, FamousDefs},
11 AssistContext, AssistId, AssistKind, Assists,
12};
13
14// Assist: fill_match_arms
15//
16// Adds missing clauses to a `match` expression.
17//
18// ```
19// enum Action { Move { distance: u32 }, Stop }
20//
21// fn handle(action: Action) {
22// match action {
23// <|>
24// }
25// }
26// ```
27// ->
28// ```
29// enum Action { Move { distance: u32 }, Stop }
30//
31// fn handle(action: Action) {
32// match action {
33// $0Action::Move { distance } => {}
34// Action::Stop => {}
35// }
36// }
37// ```
38pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
40 let match_arm_list = match_expr.match_arm_list()?;
41
42 let expr = match_expr.expr()?;
43
44 let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
45 if arms.len() == 1 {
46 if let Some(Pat::PlaceholderPat(..)) = arms[0].pat() {
47 arms.clear();
48 }
49 }
50
51 let module = ctx.sema.scope(expr.syntax()).module()?;
52
53 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
54 let variants = enum_def.variants(ctx.db());
55
56 let mut variants = variants
57 .into_iter()
58 .filter_map(|variant| build_pat(ctx.db(), module, variant))
59 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
60 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
61 .collect::<Vec<_>>();
62 if Some(enum_def) == FamousDefs(&ctx.sema, module.krate()).core_option_Option() {
63 // Match `Some` variant first.
64 mark::hit!(option_order);
65 variants.reverse()
66 }
67 variants
68 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
69 // Partial fill not currently supported for tuple of enums.
70 if !arms.is_empty() {
71 return None;
72 }
73
74 // We do not currently support filling match arms for a tuple
75 // containing a single enum.
76 if enum_defs.len() < 2 {
77 return None;
78 }
79
80 // When calculating the match arms for a tuple of enums, we want
81 // to create a match arm for each possible combination of enum
82 // values. The `multi_cartesian_product` method transforms
83 // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
84 // where each tuple represents a proposed match arm.
85 enum_defs
86 .into_iter()
87 .map(|enum_def| enum_def.variants(ctx.db()))
88 .multi_cartesian_product()
89 .map(|variants| {
90 let patterns =
91 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
92 ast::Pat::from(make::tuple_pat(patterns))
93 })
94 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
95 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
96 .collect()
97 } else {
98 return None;
99 };
100
101 if missing_arms.is_empty() {
102 return None;
103 }
104
105 let target = match_expr.syntax().text_range();
106 acc.add(
107 AssistId("fill_match_arms", AssistKind::QuickFix),
108 "Fill match arms",
109 target,
110 |builder| {
111 let new_arm_list = match_arm_list.remove_placeholder();
112 let n_old_arms = new_arm_list.arms().count();
113 let new_arm_list = new_arm_list.append_arms(missing_arms);
114 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
115 let old_range = match_arm_list.syntax().text_range();
116 match (first_new_arm, ctx.config.snippet_cap) {
117 (Some(first_new_arm), Some(cap)) => {
118 let extend_lifetime;
119 let cursor = match first_new_arm
120 .syntax()
121 .descendants()
122 .find_map(ast::PlaceholderPat::cast)
123 {
124 Some(it) => {
125 extend_lifetime = it.syntax().clone();
126 Cursor::Replace(&extend_lifetime)
127 }
128 None => Cursor::Before(first_new_arm.syntax()),
129 };
130 let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
131 builder.replace_snippet(cap, old_range, snippet);
132 }
133 _ => builder.replace(old_range, new_arm_list.to_string()),
134 }
135 },
136 )
137}
138
139fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
140 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
141 // Special casee OrPat as separate top-level pats
142 let top_level_pats: Vec<Pat> = match pat {
143 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
144 _ => vec![pat],
145 };
146
147 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var))
148 })
149}
150
151fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
152 let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
153
154 let pat_head = match pat {
155 Pat::BindPat(bind_pat) => {
156 if let Some(p) = bind_pat.pat() {
157 first_node_text(&p)
158 } else {
159 return false;
160 }
161 }
162 pat => first_node_text(pat),
163 };
164
165 let var_head = first_node_text(var);
166
167 pat_head == var_head
168}
169
170fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
171 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
172 Some(Adt::Enum(e)) => Some(e),
173 _ => None,
174 })
175}
176
177fn resolve_tuple_of_enum_def(
178 sema: &Semantics<RootDatabase>,
179 expr: &ast::Expr,
180) -> Option<Vec<hir::Enum>> {
181 sema.type_of_expr(&expr)?
182 .tuple_fields(sema.db)
183 .iter()
184 .map(|ty| {
185 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
186 Some(Adt::Enum(e)) => Some(e),
187 // For now we only handle expansion for a tuple of enums. Here
188 // we map non-enum items to None and rely on `collect` to
189 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
190 _ => None,
191 })
192 })
193 .collect()
194}
195
196fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::EnumVariant) -> Option<ast::Pat> {
197 let path = crate::ast_transform::path_to_ast(module.find_use_path(db, ModuleDef::from(var))?);
198
199 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
200 let pat: ast::Pat = match var.source(db).value.kind() {
201 ast::StructKind::Tuple(field_list) => {
202 let pats =
203 iter::repeat(make::placeholder_pat().into()).take(field_list.fields().count());
204 make::tuple_struct_pat(path, pats).into()
205 }
206 ast::StructKind::Record(field_list) => {
207 let pats = field_list.fields().map(|f| make::bind_pat(f.name().unwrap()).into());
208 make::record_pat(path, pats).into()
209 }
210 ast::StructKind::Unit => make::path_pat(path),
211 };
212
213 Some(pat)
214}
215
216#[cfg(test)]
217mod tests {
218 use test_utils::mark;
219
220 use crate::{
221 tests::{check_assist, check_assist_not_applicable, check_assist_target},
222 utils::FamousDefs,
223 };
224
225 use super::fill_match_arms;
226
227 #[test]
228 fn all_match_arms_provided() {
229 check_assist_not_applicable(
230 fill_match_arms,
231 r#"
232 enum A {
233 As,
234 Bs{x:i32, y:Option<i32>},
235 Cs(i32, Option<i32>),
236 }
237 fn main() {
238 match A::As<|> {
239 A::As,
240 A::Bs{x,y:Some(_)} => {}
241 A::Cs(_, Some(_)) => {}
242 }
243 }
244 "#,
245 );
246 }
247
248 #[test]
249 fn tuple_of_non_enum() {
250 // for now this case is not handled, although it potentially could be
251 // in the future
252 check_assist_not_applicable(
253 fill_match_arms,
254 r#"
255 fn main() {
256 match (0, false)<|> {
257 }
258 }
259 "#,
260 );
261 }
262
263 #[test]
264 fn partial_fill_record_tuple() {
265 check_assist(
266 fill_match_arms,
267 r#"
268 enum A {
269 As,
270 Bs { x: i32, y: Option<i32> },
271 Cs(i32, Option<i32>),
272 }
273 fn main() {
274 match A::As<|> {
275 A::Bs { x, y: Some(_) } => {}
276 A::Cs(_, Some(_)) => {}
277 }
278 }
279 "#,
280 r#"
281 enum A {
282 As,
283 Bs { x: i32, y: Option<i32> },
284 Cs(i32, Option<i32>),
285 }
286 fn main() {
287 match A::As {
288 A::Bs { x, y: Some(_) } => {}
289 A::Cs(_, Some(_)) => {}
290 $0A::As => {}
291 }
292 }
293 "#,
294 );
295 }
296
297 #[test]
298 fn partial_fill_or_pat() {
299 check_assist(
300 fill_match_arms,
301 r#"
302enum A { As, Bs, Cs(Option<i32>) }
303fn main() {
304 match A::As<|> {
305 A::Cs(_) | A::Bs => {}
306 }
307}
308"#,
309 r#"
310enum A { As, Bs, Cs(Option<i32>) }
311fn main() {
312 match A::As {
313 A::Cs(_) | A::Bs => {}
314 $0A::As => {}
315 }
316}
317"#,
318 );
319 }
320
321 #[test]
322 fn partial_fill() {
323 check_assist(
324 fill_match_arms,
325 r#"
326enum A { As, Bs, Cs, Ds(String), Es(B) }
327enum B { Xs, Ys }
328fn main() {
329 match A::As<|> {
330 A::Bs if 0 < 1 => {}
331 A::Ds(_value) => { let x = 1; }
332 A::Es(B::Xs) => (),
333 }
334}
335"#,
336 r#"
337enum A { As, Bs, Cs, Ds(String), Es(B) }
338enum B { Xs, Ys }
339fn main() {
340 match A::As {
341 A::Bs if 0 < 1 => {}
342 A::Ds(_value) => { let x = 1; }
343 A::Es(B::Xs) => (),
344 $0A::As => {}
345 A::Cs => {}
346 }
347}
348"#,
349 );
350 }
351
352 #[test]
353 fn partial_fill_bind_pat() {
354 check_assist(
355 fill_match_arms,
356 r#"
357enum A { As, Bs, Cs(Option<i32>) }
358fn main() {
359 match A::As<|> {
360 A::As(_) => {}
361 a @ A::Bs(_) => {}
362 }
363}
364"#,
365 r#"
366enum A { As, Bs, Cs(Option<i32>) }
367fn main() {
368 match A::As {
369 A::As(_) => {}
370 a @ A::Bs(_) => {}
371 A::Cs(${0:_}) => {}
372 }
373}
374"#,
375 );
376 }
377
378 #[test]
379 fn fill_match_arms_empty_body() {
380 check_assist(
381 fill_match_arms,
382 r#"
383enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
384
385fn main() {
386 let a = A::As;
387 match a<|> {}
388}
389"#,
390 r#"
391enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
392
393fn main() {
394 let a = A::As;
395 match a {
396 $0A::As => {}
397 A::Bs => {}
398 A::Cs(_) => {}
399 A::Ds(_, _) => {}
400 A::Es { x, y } => {}
401 }
402}
403"#,
404 );
405 }
406
407 #[test]
408 fn fill_match_arms_tuple_of_enum() {
409 check_assist(
410 fill_match_arms,
411 r#"
412 enum A { One, Two }
413 enum B { One, Two }
414
415 fn main() {
416 let a = A::One;
417 let b = B::One;
418 match (a<|>, b) {}
419 }
420 "#,
421 r#"
422 enum A { One, Two }
423 enum B { One, Two }
424
425 fn main() {
426 let a = A::One;
427 let b = B::One;
428 match (a, b) {
429 $0(A::One, B::One) => {}
430 (A::One, B::Two) => {}
431 (A::Two, B::One) => {}
432 (A::Two, B::Two) => {}
433 }
434 }
435 "#,
436 );
437 }
438
439 #[test]
440 fn fill_match_arms_tuple_of_enum_ref() {
441 check_assist(
442 fill_match_arms,
443 r#"
444 enum A { One, Two }
445 enum B { One, Two }
446
447 fn main() {
448 let a = A::One;
449 let b = B::One;
450 match (&a<|>, &b) {}
451 }
452 "#,
453 r#"
454 enum A { One, Two }
455 enum B { One, Two }
456
457 fn main() {
458 let a = A::One;
459 let b = B::One;
460 match (&a, &b) {
461 $0(A::One, B::One) => {}
462 (A::One, B::Two) => {}
463 (A::Two, B::One) => {}
464 (A::Two, B::Two) => {}
465 }
466 }
467 "#,
468 );
469 }
470
471 #[test]
472 fn fill_match_arms_tuple_of_enum_partial() {
473 check_assist_not_applicable(
474 fill_match_arms,
475 r#"
476 enum A { One, Two }
477 enum B { One, Two }
478
479 fn main() {
480 let a = A::One;
481 let b = B::One;
482 match (a<|>, b) {
483 (A::Two, B::One) => {}
484 }
485 }
486 "#,
487 );
488 }
489
490 #[test]
491 fn fill_match_arms_tuple_of_enum_not_applicable() {
492 check_assist_not_applicable(
493 fill_match_arms,
494 r#"
495 enum A { One, Two }
496 enum B { One, Two }
497
498 fn main() {
499 let a = A::One;
500 let b = B::One;
501 match (a<|>, b) {
502 (A::Two, B::One) => {}
503 (A::One, B::One) => {}
504 (A::One, B::Two) => {}
505 (A::Two, B::Two) => {}
506 }
507 }
508 "#,
509 );
510 }
511
512 #[test]
513 fn fill_match_arms_single_element_tuple_of_enum() {
514 // For now we don't hande the case of a single element tuple, but
515 // we could handle this in the future if `make::tuple_pat` allowed
516 // creating a tuple with a single pattern.
517 check_assist_not_applicable(
518 fill_match_arms,
519 r#"
520 enum A { One, Two }
521
522 fn main() {
523 let a = A::One;
524 match (a<|>, ) {
525 }
526 }
527 "#,
528 );
529 }
530
531 #[test]
532 fn test_fill_match_arm_refs() {
533 check_assist(
534 fill_match_arms,
535 r#"
536 enum A { As }
537
538 fn foo(a: &A) {
539 match a<|> {
540 }
541 }
542 "#,
543 r#"
544 enum A { As }
545
546 fn foo(a: &A) {
547 match a {
548 $0A::As => {}
549 }
550 }
551 "#,
552 );
553
554 check_assist(
555 fill_match_arms,
556 r#"
557 enum A {
558 Es { x: usize, y: usize }
559 }
560
561 fn foo(a: &mut A) {
562 match a<|> {
563 }
564 }
565 "#,
566 r#"
567 enum A {
568 Es { x: usize, y: usize }
569 }
570
571 fn foo(a: &mut A) {
572 match a {
573 $0A::Es { x, y } => {}
574 }
575 }
576 "#,
577 );
578 }
579
580 #[test]
581 fn fill_match_arms_target() {
582 check_assist_target(
583 fill_match_arms,
584 r#"
585 enum E { X, Y }
586
587 fn main() {
588 match E::X<|> {}
589 }
590 "#,
591 "match E::X {}",
592 );
593 }
594
595 #[test]
596 fn fill_match_arms_trivial_arm() {
597 check_assist(
598 fill_match_arms,
599 r#"
600 enum E { X, Y }
601
602 fn main() {
603 match E::X {
604 <|>_ => {}
605 }
606 }
607 "#,
608 r#"
609 enum E { X, Y }
610
611 fn main() {
612 match E::X {
613 $0E::X => {}
614 E::Y => {}
615 }
616 }
617 "#,
618 );
619 }
620
621 #[test]
622 fn fill_match_arms_qualifies_path() {
623 check_assist(
624 fill_match_arms,
625 r#"
626 mod foo { pub enum E { X, Y } }
627 use foo::E::X;
628
629 fn main() {
630 match X {
631 <|>
632 }
633 }
634 "#,
635 r#"
636 mod foo { pub enum E { X, Y } }
637 use foo::E::X;
638
639 fn main() {
640 match X {
641 $0X => {}
642 foo::E::Y => {}
643 }
644 }
645 "#,
646 );
647 }
648
649 #[test]
650 fn fill_match_arms_preserves_comments() {
651 check_assist(
652 fill_match_arms,
653 r#"
654 enum A { One, Two }
655 fn foo(a: A) {
656 match a {
657 // foo bar baz<|>
658 A::One => {}
659 // This is where the rest should be
660 }
661 }
662 "#,
663 r#"
664 enum A { One, Two }
665 fn foo(a: A) {
666 match a {
667 // foo bar baz
668 A::One => {}
669 // This is where the rest should be
670 $0A::Two => {}
671 }
672 }
673 "#,
674 );
675 }
676
677 #[test]
678 fn fill_match_arms_preserves_comments_empty() {
679 check_assist(
680 fill_match_arms,
681 r#"
682 enum A { One, Two }
683 fn foo(a: A) {
684 match a {
685 // foo bar baz<|>
686 }
687 }
688 "#,
689 r#"
690 enum A { One, Two }
691 fn foo(a: A) {
692 match a {
693 // foo bar baz
694 $0A::One => {}
695 A::Two => {}
696 }
697 }
698 "#,
699 );
700 }
701
702 #[test]
703 fn fill_match_arms_placeholder() {
704 check_assist(
705 fill_match_arms,
706 r#"
707 enum A { One, Two, }
708 fn foo(a: A) {
709 match a<|> {
710 _ => (),
711 }
712 }
713 "#,
714 r#"
715 enum A { One, Two, }
716 fn foo(a: A) {
717 match a {
718 $0A::One => {}
719 A::Two => {}
720 }
721 }
722 "#,
723 );
724 }
725
726 #[test]
727 fn option_order() {
728 mark::check!(option_order);
729 let before = r#"
730fn foo(opt: Option<i32>) {
731 match opt<|> {
732 }
733}
734"#;
735 let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
736
737 check_assist(
738 fill_match_arms,
739 before,
740 r#"
741fn foo(opt: Option<i32>) {
742 match opt {
743 Some(${0:_}) => {}
744 None => {}
745 }
746}
747"#,
748 );
749 }
750}
diff --git a/crates/ra_assists/src/handlers/fix_visibility.rs b/crates/ra_assists/src/handlers/fix_visibility.rs
deleted file mode 100644
index 1aefa79cc..000000000
--- a/crates/ra_assists/src/handlers/fix_visibility.rs
+++ /dev/null
@@ -1,607 +0,0 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ra_db::FileId;
3use ra_syntax::{ast, AstNode, TextRange, TextSize};
4
5use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
6use ast::VisibilityOwner;
7
8// FIXME: this really should be a fix for diagnostic, rather than an assist.
9
10// Assist: fix_visibility
11//
12// Makes inaccessible item public.
13//
14// ```
15// mod m {
16// fn frobnicate() {}
17// }
18// fn main() {
19// m::frobnicate<|>() {}
20// }
21// ```
22// ->
23// ```
24// mod m {
25// $0pub(crate) fn frobnicate() {}
26// }
27// fn main() {
28// m::frobnicate() {}
29// }
30// ```
31pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 add_vis_to_referenced_module_def(acc, ctx)
33 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
34}
35
36fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
37 let path: ast::Path = ctx.find_node_at_offset()?;
38 let path_res = ctx.sema.resolve_path(&path)?;
39 let def = match path_res {
40 PathResolution::Def(def) => def,
41 _ => return None,
42 };
43
44 let current_module = ctx.sema.scope(&path.syntax()).module()?;
45 let target_module = def.module(ctx.db())?;
46
47 let vis = target_module.visibility_of(ctx.db(), &def)?;
48 if vis.is_visible_from(ctx.db(), current_module.into()) {
49 return None;
50 };
51
52 let (offset, current_visibility, target, target_file, target_name) =
53 target_data_for_def(ctx.db(), def)?;
54
55 let missing_visibility =
56 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
57
58 let assist_label = match target_name {
59 None => format!("Change visibility to {}", missing_visibility),
60 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
61 };
62
63 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
64 builder.edit_file(target_file);
65 match ctx.config.snippet_cap {
66 Some(cap) => match current_visibility {
67 Some(current_visibility) => builder.replace_snippet(
68 cap,
69 current_visibility.syntax().text_range(),
70 format!("$0{}", missing_visibility),
71 ),
72 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
73 },
74 None => match current_visibility {
75 Some(current_visibility) => {
76 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
77 }
78 None => builder.insert(offset, format!("{} ", missing_visibility)),
79 },
80 }
81 })
82}
83
84fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
85 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
86 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
87
88 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
89 let visibility = record_field_def.visibility(ctx.db());
90 if visibility.is_visible_from(ctx.db(), current_module.into()) {
91 return None;
92 }
93
94 let parent = record_field_def.parent_def(ctx.db());
95 let parent_name = parent.name(ctx.db());
96 let target_module = parent.module(ctx.db());
97
98 let in_file_source = record_field_def.source(ctx.db());
99 let (offset, current_visibility, target) = match in_file_source.value {
100 hir::FieldSource::Named(it) => {
101 let s = it.syntax();
102 (vis_offset(s), it.visibility(), s.text_range())
103 }
104 hir::FieldSource::Pos(it) => {
105 let s = it.syntax();
106 (vis_offset(s), it.visibility(), s.text_range())
107 }
108 };
109
110 let missing_visibility =
111 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
112 let target_file = in_file_source.file_id.original_file(ctx.db());
113
114 let target_name = record_field_def.name(ctx.db());
115 let assist_label =
116 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
117
118 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
119 builder.edit_file(target_file);
120 match ctx.config.snippet_cap {
121 Some(cap) => match current_visibility {
122 Some(current_visibility) => builder.replace_snippet(
123 cap,
124 dbg!(current_visibility.syntax()).text_range(),
125 format!("$0{}", missing_visibility),
126 ),
127 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
128 },
129 None => match current_visibility {
130 Some(current_visibility) => {
131 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
132 }
133 None => builder.insert(offset, format!("{} ", missing_visibility)),
134 },
135 }
136 })
137}
138
139fn target_data_for_def(
140 db: &dyn HirDatabase,
141 def: hir::ModuleDef,
142) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
143 fn offset_target_and_file_id<S, Ast>(
144 db: &dyn HirDatabase,
145 x: S,
146 ) -> (TextSize, Option<ast::Visibility>, TextRange, FileId)
147 where
148 S: HasSource<Ast = Ast>,
149 Ast: AstNode + ast::VisibilityOwner,
150 {
151 let source = x.source(db);
152 let in_file_syntax = source.syntax();
153 let file_id = in_file_syntax.file_id;
154 let syntax = in_file_syntax.value;
155 let current_visibility = source.value.visibility();
156 (
157 vis_offset(syntax),
158 current_visibility,
159 syntax.text_range(),
160 file_id.original_file(db.upcast()),
161 )
162 }
163
164 let target_name;
165 let (offset, current_visibility, target, target_file) = match def {
166 hir::ModuleDef::Function(f) => {
167 target_name = Some(f.name(db));
168 offset_target_and_file_id(db, f)
169 }
170 hir::ModuleDef::Adt(adt) => {
171 target_name = Some(adt.name(db));
172 match adt {
173 hir::Adt::Struct(s) => offset_target_and_file_id(db, s),
174 hir::Adt::Union(u) => offset_target_and_file_id(db, u),
175 hir::Adt::Enum(e) => offset_target_and_file_id(db, e),
176 }
177 }
178 hir::ModuleDef::Const(c) => {
179 target_name = c.name(db);
180 offset_target_and_file_id(db, c)
181 }
182 hir::ModuleDef::Static(s) => {
183 target_name = s.name(db);
184 offset_target_and_file_id(db, s)
185 }
186 hir::ModuleDef::Trait(t) => {
187 target_name = Some(t.name(db));
188 offset_target_and_file_id(db, t)
189 }
190 hir::ModuleDef::TypeAlias(t) => {
191 target_name = Some(t.name(db));
192 offset_target_and_file_id(db, t)
193 }
194 hir::ModuleDef::Module(m) => {
195 target_name = m.name(db);
196 let in_file_source = m.declaration_source(db)?;
197 let file_id = in_file_source.file_id.original_file(db.upcast());
198 let syntax = in_file_source.value.syntax();
199 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
200 }
201 // Enum variants can't be private, we can't modify builtin types
202 hir::ModuleDef::EnumVariant(_) | hir::ModuleDef::BuiltinType(_) => return None,
203 };
204
205 Some((offset, current_visibility, target, target_file, target_name))
206}
207
208#[cfg(test)]
209mod tests {
210 use crate::tests::{check_assist, check_assist_not_applicable};
211
212 use super::*;
213
214 #[test]
215 fn fix_visibility_of_fn() {
216 check_assist(
217 fix_visibility,
218 r"mod foo { fn foo() {} }
219 fn main() { foo::foo<|>() } ",
220 r"mod foo { $0pub(crate) fn foo() {} }
221 fn main() { foo::foo() } ",
222 );
223 check_assist_not_applicable(
224 fix_visibility,
225 r"mod foo { pub fn foo() {} }
226 fn main() { foo::foo<|>() } ",
227 )
228 }
229
230 #[test]
231 fn fix_visibility_of_adt_in_submodule() {
232 check_assist(
233 fix_visibility,
234 r"mod foo { struct Foo; }
235 fn main() { foo::Foo<|> } ",
236 r"mod foo { $0pub(crate) struct Foo; }
237 fn main() { foo::Foo } ",
238 );
239 check_assist_not_applicable(
240 fix_visibility,
241 r"mod foo { pub struct Foo; }
242 fn main() { foo::Foo<|> } ",
243 );
244 check_assist(
245 fix_visibility,
246 r"mod foo { enum Foo; }
247 fn main() { foo::Foo<|> } ",
248 r"mod foo { $0pub(crate) enum Foo; }
249 fn main() { foo::Foo } ",
250 );
251 check_assist_not_applicable(
252 fix_visibility,
253 r"mod foo { pub enum Foo; }
254 fn main() { foo::Foo<|> } ",
255 );
256 check_assist(
257 fix_visibility,
258 r"mod foo { union Foo; }
259 fn main() { foo::Foo<|> } ",
260 r"mod foo { $0pub(crate) union Foo; }
261 fn main() { foo::Foo } ",
262 );
263 check_assist_not_applicable(
264 fix_visibility,
265 r"mod foo { pub union Foo; }
266 fn main() { foo::Foo<|> } ",
267 );
268 }
269
270 #[test]
271 fn fix_visibility_of_adt_in_other_file() {
272 check_assist(
273 fix_visibility,
274 r"
275//- /main.rs
276mod foo;
277fn main() { foo::Foo<|> }
278
279//- /foo.rs
280struct Foo;
281",
282 r"$0pub(crate) struct Foo;
283",
284 );
285 }
286
287 #[test]
288 fn fix_visibility_of_struct_field() {
289 check_assist(
290 fix_visibility,
291 r"mod foo { pub struct Foo { bar: (), } }
292 fn main() { foo::Foo { <|>bar: () }; } ",
293 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
294 fn main() { foo::Foo { bar: () }; } ",
295 );
296 check_assist(
297 fix_visibility,
298 r"
299//- /lib.rs
300mod foo;
301fn main() { foo::Foo { <|>bar: () }; }
302//- /foo.rs
303pub struct Foo { bar: () }
304",
305 r"pub struct Foo { $0pub(crate) bar: () }
306",
307 );
308 check_assist_not_applicable(
309 fix_visibility,
310 r"mod foo { pub struct Foo { pub bar: (), } }
311 fn main() { foo::Foo { <|>bar: () }; } ",
312 );
313 check_assist_not_applicable(
314 fix_visibility,
315 r"
316//- /lib.rs
317mod foo;
318fn main() { foo::Foo { <|>bar: () }; }
319//- /foo.rs
320pub struct Foo { pub bar: () }
321",
322 );
323 }
324
325 #[test]
326 fn fix_visibility_of_enum_variant_field() {
327 check_assist(
328 fix_visibility,
329 r"mod foo { pub enum Foo { Bar { bar: () } } }
330 fn main() { foo::Foo::Bar { <|>bar: () }; } ",
331 r"mod foo { pub enum Foo { Bar { $0pub(crate) bar: () } } }
332 fn main() { foo::Foo::Bar { bar: () }; } ",
333 );
334 check_assist(
335 fix_visibility,
336 r"
337//- /lib.rs
338mod foo;
339fn main() { foo::Foo::Bar { <|>bar: () }; }
340//- /foo.rs
341pub enum Foo { Bar { bar: () } }
342",
343 r"pub enum Foo { Bar { $0pub(crate) bar: () } }
344",
345 );
346 check_assist_not_applicable(
347 fix_visibility,
348 r"mod foo { pub struct Foo { pub bar: (), } }
349 fn main() { foo::Foo { <|>bar: () }; } ",
350 );
351 check_assist_not_applicable(
352 fix_visibility,
353 r"
354//- /lib.rs
355mod foo;
356fn main() { foo::Foo { <|>bar: () }; }
357//- /foo.rs
358pub struct Foo { pub bar: () }
359",
360 );
361 }
362
363 #[test]
364 #[ignore]
365 // FIXME reenable this test when `Semantics::resolve_record_field` works with union fields
366 fn fix_visibility_of_union_field() {
367 check_assist(
368 fix_visibility,
369 r"mod foo { pub union Foo { bar: (), } }
370 fn main() { foo::Foo { <|>bar: () }; } ",
371 r"mod foo { pub union Foo { $0pub(crate) bar: (), } }
372 fn main() { foo::Foo { bar: () }; } ",
373 );
374 check_assist(
375 fix_visibility,
376 r"
377//- /lib.rs
378mod foo;
379fn main() { foo::Foo { <|>bar: () }; }
380//- /foo.rs
381pub union Foo { bar: () }
382",
383 r"pub union Foo { $0pub(crate) bar: () }
384",
385 );
386 check_assist_not_applicable(
387 fix_visibility,
388 r"mod foo { pub union Foo { pub bar: (), } }
389 fn main() { foo::Foo { <|>bar: () }; } ",
390 );
391 check_assist_not_applicable(
392 fix_visibility,
393 r"
394//- /lib.rs
395mod foo;
396fn main() { foo::Foo { <|>bar: () }; }
397//- /foo.rs
398pub union Foo { pub bar: () }
399",
400 );
401 }
402
403 #[test]
404 fn fix_visibility_of_const() {
405 check_assist(
406 fix_visibility,
407 r"mod foo { const FOO: () = (); }
408 fn main() { foo::FOO<|> } ",
409 r"mod foo { $0pub(crate) const FOO: () = (); }
410 fn main() { foo::FOO } ",
411 );
412 check_assist_not_applicable(
413 fix_visibility,
414 r"mod foo { pub const FOO: () = (); }
415 fn main() { foo::FOO<|> } ",
416 );
417 }
418
419 #[test]
420 fn fix_visibility_of_static() {
421 check_assist(
422 fix_visibility,
423 r"mod foo { static FOO: () = (); }
424 fn main() { foo::FOO<|> } ",
425 r"mod foo { $0pub(crate) static FOO: () = (); }
426 fn main() { foo::FOO } ",
427 );
428 check_assist_not_applicable(
429 fix_visibility,
430 r"mod foo { pub static FOO: () = (); }
431 fn main() { foo::FOO<|> } ",
432 );
433 }
434
435 #[test]
436 fn fix_visibility_of_trait() {
437 check_assist(
438 fix_visibility,
439 r"mod foo { trait Foo { fn foo(&self) {} } }
440 fn main() { let x: &dyn foo::<|>Foo; } ",
441 r"mod foo { $0pub(crate) trait Foo { fn foo(&self) {} } }
442 fn main() { let x: &dyn foo::Foo; } ",
443 );
444 check_assist_not_applicable(
445 fix_visibility,
446 r"mod foo { pub trait Foo { fn foo(&self) {} } }
447 fn main() { let x: &dyn foo::Foo<|>; } ",
448 );
449 }
450
451 #[test]
452 fn fix_visibility_of_type_alias() {
453 check_assist(
454 fix_visibility,
455 r"mod foo { type Foo = (); }
456 fn main() { let x: foo::Foo<|>; } ",
457 r"mod foo { $0pub(crate) type Foo = (); }
458 fn main() { let x: foo::Foo; } ",
459 );
460 check_assist_not_applicable(
461 fix_visibility,
462 r"mod foo { pub type Foo = (); }
463 fn main() { let x: foo::Foo<|>; } ",
464 );
465 }
466
467 #[test]
468 fn fix_visibility_of_module() {
469 check_assist(
470 fix_visibility,
471 r"mod foo { mod bar { fn bar() {} } }
472 fn main() { foo::bar<|>::bar(); } ",
473 r"mod foo { $0pub(crate) mod bar { fn bar() {} } }
474 fn main() { foo::bar::bar(); } ",
475 );
476
477 check_assist(
478 fix_visibility,
479 r"
480//- /main.rs
481mod foo;
482fn main() { foo::bar<|>::baz(); }
483
484//- /foo.rs
485mod bar {
486 pub fn baz() {}
487}
488",
489 r"$0pub(crate) mod bar {
490 pub fn baz() {}
491}
492",
493 );
494
495 check_assist_not_applicable(
496 fix_visibility,
497 r"mod foo { pub mod bar { pub fn bar() {} } }
498 fn main() { foo::bar<|>::bar(); } ",
499 );
500 }
501
502 #[test]
503 fn fix_visibility_of_inline_module_in_other_file() {
504 check_assist(
505 fix_visibility,
506 r"
507//- /main.rs
508mod foo;
509fn main() { foo::bar<|>::baz(); }
510
511//- /foo.rs
512mod bar;
513//- /foo/bar.rs
514pub fn baz() {}
515",
516 r"$0pub(crate) mod bar;
517",
518 );
519 }
520
521 #[test]
522 fn fix_visibility_of_module_declaration_in_other_file() {
523 check_assist(
524 fix_visibility,
525 r"
526//- /main.rs
527mod foo;
528fn main() { foo::bar<|>>::baz(); }
529
530//- /foo.rs
531mod bar {
532 pub fn baz() {}
533}
534",
535 r"$0pub(crate) mod bar {
536 pub fn baz() {}
537}
538",
539 );
540 }
541
542 #[test]
543 fn adds_pub_when_target_is_in_another_crate() {
544 check_assist(
545 fix_visibility,
546 r"
547//- /main.rs crate:a deps:foo
548foo::Bar<|>
549//- /lib.rs crate:foo
550struct Bar;
551",
552 r"$0pub struct Bar;
553",
554 )
555 }
556
557 #[test]
558 fn replaces_pub_crate_with_pub() {
559 check_assist(
560 fix_visibility,
561 r"
562//- /main.rs crate:a deps:foo
563foo::Bar<|>
564//- /lib.rs crate:foo
565pub(crate) struct Bar;
566",
567 r"$0pub struct Bar;
568",
569 );
570 check_assist(
571 fix_visibility,
572 r"
573//- /main.rs crate:a deps:foo
574fn main() {
575 foo::Foo { <|>bar: () };
576}
577//- /lib.rs crate:foo
578pub struct Foo { pub(crate) bar: () }
579",
580 r"pub struct Foo { $0pub bar: () }
581",
582 );
583 }
584
585 #[test]
586 #[ignore]
587 // FIXME handle reexports properly
588 fn fix_visibility_of_reexport() {
589 check_assist(
590 fix_visibility,
591 r"
592 mod foo {
593 use bar::Baz;
594 mod bar { pub(super) struct Baz; }
595 }
596 foo::Baz<|>
597 ",
598 r"
599 mod foo {
600 $0pub(crate) use bar::Baz;
601 mod bar { pub(super) struct Baz; }
602 }
603 foo::Baz
604 ",
605 )
606 }
607}
diff --git a/crates/ra_assists/src/handlers/flip_binexpr.rs b/crates/ra_assists/src/handlers/flip_binexpr.rs
deleted file mode 100644
index 3cd532650..000000000
--- a/crates/ra_assists/src/handlers/flip_binexpr.rs
+++ /dev/null
@@ -1,142 +0,0 @@
1use ra_syntax::ast::{AstNode, BinExpr, BinOp};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: flip_binexpr
6//
7// Flips operands of a binary expression.
8//
9// ```
10// fn main() {
11// let _ = 90 +<|> 2;
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// let _ = 2 + 90;
18// }
19// ```
20pub(crate) fn flip_binexpr(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let expr = ctx.find_node_at_offset::<BinExpr>()?;
22 let lhs = expr.lhs()?.syntax().clone();
23 let rhs = expr.rhs()?.syntax().clone();
24 let op_range = expr.op_token()?.text_range();
25 // The assist should be applied only if the cursor is on the operator
26 let cursor_in_range = op_range.contains_range(ctx.frange.range);
27 if !cursor_in_range {
28 return None;
29 }
30 let action: FlipAction = expr.op_kind()?.into();
31 // The assist should not be applied for certain operators
32 if let FlipAction::DontFlip = action {
33 return None;
34 }
35
36 acc.add(
37 AssistId("flip_binexpr", AssistKind::RefactorRewrite),
38 "Flip binary expression",
39 op_range,
40 |edit| {
41 if let FlipAction::FlipAndReplaceOp(new_op) = action {
42 edit.replace(op_range, new_op);
43 }
44 edit.replace(lhs.text_range(), rhs.text());
45 edit.replace(rhs.text_range(), lhs.text());
46 },
47 )
48}
49
50enum FlipAction {
51 // Flip the expression
52 Flip,
53 // Flip the expression and replace the operator with this string
54 FlipAndReplaceOp(&'static str),
55 // Do not flip the expression
56 DontFlip,
57}
58
59impl From<BinOp> for FlipAction {
60 fn from(op_kind: BinOp) -> Self {
61 match op_kind {
62 kind if kind.is_assignment() => FlipAction::DontFlip,
63 BinOp::GreaterTest => FlipAction::FlipAndReplaceOp("<"),
64 BinOp::GreaterEqualTest => FlipAction::FlipAndReplaceOp("<="),
65 BinOp::LesserTest => FlipAction::FlipAndReplaceOp(">"),
66 BinOp::LesserEqualTest => FlipAction::FlipAndReplaceOp(">="),
67 _ => FlipAction::Flip,
68 }
69 }
70}
71
72#[cfg(test)]
73mod tests {
74 use super::*;
75
76 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
77
78 #[test]
79 fn flip_binexpr_target_is_the_op() {
80 check_assist_target(flip_binexpr, "fn f() { let res = 1 ==<|> 2; }", "==")
81 }
82
83 #[test]
84 fn flip_binexpr_not_applicable_for_assignment() {
85 check_assist_not_applicable(flip_binexpr, "fn f() { let mut _x = 1; _x +=<|> 2 }")
86 }
87
88 #[test]
89 fn flip_binexpr_works_for_eq() {
90 check_assist(
91 flip_binexpr,
92 "fn f() { let res = 1 ==<|> 2; }",
93 "fn f() { let res = 2 == 1; }",
94 )
95 }
96
97 #[test]
98 fn flip_binexpr_works_for_gt() {
99 check_assist(flip_binexpr, "fn f() { let res = 1 ><|> 2; }", "fn f() { let res = 2 < 1; }")
100 }
101
102 #[test]
103 fn flip_binexpr_works_for_lteq() {
104 check_assist(
105 flip_binexpr,
106 "fn f() { let res = 1 <=<|> 2; }",
107 "fn f() { let res = 2 >= 1; }",
108 )
109 }
110
111 #[test]
112 fn flip_binexpr_works_for_complex_expr() {
113 check_assist(
114 flip_binexpr,
115 "fn f() { let res = (1 + 1) ==<|> (2 + 2); }",
116 "fn f() { let res = (2 + 2) == (1 + 1); }",
117 )
118 }
119
120 #[test]
121 fn flip_binexpr_works_inside_match() {
122 check_assist(
123 flip_binexpr,
124 r#"
125 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
126 match other.downcast_ref::<Self>() {
127 None => false,
128 Some(it) => it ==<|> self,
129 }
130 }
131 "#,
132 r#"
133 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
134 match other.downcast_ref::<Self>() {
135 None => false,
136 Some(it) => self == it,
137 }
138 }
139 "#,
140 )
141 }
142}
diff --git a/crates/ra_assists/src/handlers/flip_comma.rs b/crates/ra_assists/src/handlers/flip_comma.rs
deleted file mode 100644
index 55a971dc7..000000000
--- a/crates/ra_assists/src/handlers/flip_comma.rs
+++ /dev/null
@@ -1,84 +0,0 @@
1use ra_syntax::{algo::non_trivia_sibling, Direction, T};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: flip_comma
6//
7// Flips two comma-separated items.
8//
9// ```
10// fn main() {
11// ((1, 2),<|> (3, 4));
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// ((3, 4), (1, 2));
18// }
19// ```
20pub(crate) fn flip_comma(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let comma = ctx.find_token_at_offset(T![,])?;
22 let prev = non_trivia_sibling(comma.clone().into(), Direction::Prev)?;
23 let next = non_trivia_sibling(comma.clone().into(), Direction::Next)?;
24
25 // Don't apply a "flip" in case of a last comma
26 // that typically comes before punctuation
27 if next.kind().is_punct() {
28 return None;
29 }
30
31 acc.add(
32 AssistId("flip_comma", AssistKind::RefactorRewrite),
33 "Flip comma",
34 comma.text_range(),
35 |edit| {
36 edit.replace(prev.text_range(), next.to_string());
37 edit.replace(next.text_range(), prev.to_string());
38 },
39 )
40}
41
42#[cfg(test)]
43mod tests {
44 use super::*;
45
46 use crate::tests::{check_assist, check_assist_target};
47
48 #[test]
49 fn flip_comma_works_for_function_parameters() {
50 check_assist(
51 flip_comma,
52 "fn foo(x: i32,<|> y: Result<(), ()>) {}",
53 "fn foo(y: Result<(), ()>, x: i32) {}",
54 )
55 }
56
57 #[test]
58 fn flip_comma_target() {
59 check_assist_target(flip_comma, "fn foo(x: i32,<|> y: Result<(), ()>) {}", ",")
60 }
61
62 #[test]
63 #[should_panic]
64 fn flip_comma_before_punct() {
65 // See https://github.com/rust-analyzer/rust-analyzer/issues/1619
66 // "Flip comma" assist shouldn't be applicable to the last comma in enum or struct
67 // declaration body.
68 check_assist_target(
69 flip_comma,
70 "pub enum Test { \
71 A,<|> \
72 }",
73 ",",
74 );
75
76 check_assist_target(
77 flip_comma,
78 "pub struct Test { \
79 foo: usize,<|> \
80 }",
81 ",",
82 );
83 }
84}
diff --git a/crates/ra_assists/src/handlers/flip_trait_bound.rs b/crates/ra_assists/src/handlers/flip_trait_bound.rs
deleted file mode 100644
index 1234f4d29..000000000
--- a/crates/ra_assists/src/handlers/flip_trait_bound.rs
+++ /dev/null
@@ -1,121 +0,0 @@
1use ra_syntax::{
2 algo::non_trivia_sibling,
3 ast::{self, AstNode},
4 Direction, T,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: flip_trait_bound
10//
11// Flips two trait bounds.
12//
13// ```
14// fn foo<T: Clone +<|> Copy>() { }
15// ```
16// ->
17// ```
18// fn foo<T: Copy + Clone>() { }
19// ```
20pub(crate) fn flip_trait_bound(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 // We want to replicate the behavior of `flip_binexpr` by only suggesting
22 // the assist when the cursor is on a `+`
23 let plus = ctx.find_token_at_offset(T![+])?;
24
25 // Make sure we're in a `TypeBoundList`
26 if ast::TypeBoundList::cast(plus.parent()).is_none() {
27 return None;
28 }
29
30 let (before, after) = (
31 non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
32 non_trivia_sibling(plus.clone().into(), Direction::Next)?,
33 );
34
35 let target = plus.text_range();
36 acc.add(
37 AssistId("flip_trait_bound", AssistKind::RefactorRewrite),
38 "Flip trait bounds",
39 target,
40 |edit| {
41 edit.replace(before.text_range(), after.to_string());
42 edit.replace(after.text_range(), before.to_string());
43 },
44 )
45}
46
47#[cfg(test)]
48mod tests {
49 use super::*;
50
51 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
52
53 #[test]
54 fn flip_trait_bound_assist_available() {
55 check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+")
56 }
57
58 #[test]
59 fn flip_trait_bound_not_applicable_for_single_trait_bound() {
60 check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }")
61 }
62
63 #[test]
64 fn flip_trait_bound_works_for_struct() {
65 check_assist(
66 flip_trait_bound,
67 "struct S<T> where T: A <|>+ B { }",
68 "struct S<T> where T: B + A { }",
69 )
70 }
71
72 #[test]
73 fn flip_trait_bound_works_for_trait_impl() {
74 check_assist(
75 flip_trait_bound,
76 "impl X for S<T> where T: A +<|> B { }",
77 "impl X for S<T> where T: B + A { }",
78 )
79 }
80
81 #[test]
82 fn flip_trait_bound_works_for_fn() {
83 check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B + A>(t: T) { }")
84 }
85
86 #[test]
87 fn flip_trait_bound_works_for_fn_where_clause() {
88 check_assist(
89 flip_trait_bound,
90 "fn f<T>(t: T) where T: A +<|> B { }",
91 "fn f<T>(t: T) where T: B + A { }",
92 )
93 }
94
95 #[test]
96 fn flip_trait_bound_works_for_lifetime() {
97 check_assist(
98 flip_trait_bound,
99 "fn f<T>(t: T) where T: A <|>+ 'static { }",
100 "fn f<T>(t: T) where T: 'static + A { }",
101 )
102 }
103
104 #[test]
105 fn flip_trait_bound_works_for_complex_bounds() {
106 check_assist(
107 flip_trait_bound,
108 "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
109 "struct S<T> where T: b_mod::B<T> + A<T> + C<T> { }",
110 )
111 }
112
113 #[test]
114 fn flip_trait_bound_works_for_long_bounds() {
115 check_assist(
116 flip_trait_bound,
117 "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
118 "struct S<T> where T: A + B + C + D + E + G + F + H + I + J { }",
119 )
120 }
121}
diff --git a/crates/ra_assists/src/handlers/generate_derive.rs b/crates/ra_assists/src/handlers/generate_derive.rs
deleted file mode 100644
index 90ece9fab..000000000
--- a/crates/ra_assists/src/handlers/generate_derive.rs
+++ /dev/null
@@ -1,132 +0,0 @@
1use ra_syntax::{
2 ast::{self, AstNode, AttrsOwner},
3 SyntaxKind::{COMMENT, WHITESPACE},
4 TextSize,
5};
6
7use crate::{AssistContext, AssistId, AssistKind, Assists};
8
9// Assist: generate_derive
10//
11// Adds a new `#[derive()]` clause to a struct or enum.
12//
13// ```
14// struct Point {
15// x: u32,
16// y: u32,<|>
17// }
18// ```
19// ->
20// ```
21// #[derive($0)]
22// struct Point {
23// x: u32,
24// y: u32,
25// }
26// ```
27pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
28 let cap = ctx.config.snippet_cap?;
29 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
30 let node_start = derive_insertion_offset(&nominal)?;
31 let target = nominal.syntax().text_range();
32 acc.add(
33 AssistId("generate_derive", AssistKind::Generate),
34 "Add `#[derive]`",
35 target,
36 |builder| {
37 let derive_attr = nominal
38 .attrs()
39 .filter_map(|x| x.as_simple_call())
40 .filter(|(name, _arg)| name == "derive")
41 .map(|(_name, arg)| arg)
42 .next();
43 match derive_attr {
44 None => {
45 builder.insert_snippet(cap, node_start, "#[derive($0)]\n");
46 }
47 Some(tt) => {
48 // Just move the cursor.
49 builder.insert_snippet(
50 cap,
51 tt.syntax().text_range().end() - TextSize::of(')'),
52 "$0",
53 )
54 }
55 };
56 },
57 )
58}
59
60// Insert `derive` after doc comments.
61fn derive_insertion_offset(nominal: &ast::AdtDef) -> Option<TextSize> {
62 let non_ws_child = nominal
63 .syntax()
64 .children_with_tokens()
65 .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
66 Some(non_ws_child.text_range().start())
67}
68
69#[cfg(test)]
70mod tests {
71 use crate::tests::{check_assist, check_assist_target};
72
73 use super::*;
74
75 #[test]
76 fn add_derive_new() {
77 check_assist(
78 generate_derive,
79 "struct Foo { a: i32, <|>}",
80 "#[derive($0)]\nstruct Foo { a: i32, }",
81 );
82 check_assist(
83 generate_derive,
84 "struct Foo { <|> a: i32, }",
85 "#[derive($0)]\nstruct Foo { a: i32, }",
86 );
87 }
88
89 #[test]
90 fn add_derive_existing() {
91 check_assist(
92 generate_derive,
93 "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
94 "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
95 );
96 }
97
98 #[test]
99 fn add_derive_new_with_doc_comment() {
100 check_assist(
101 generate_derive,
102 "
103/// `Foo` is a pretty important struct.
104/// It does stuff.
105struct Foo { a: i32<|>, }
106 ",
107 "
108/// `Foo` is a pretty important struct.
109/// It does stuff.
110#[derive($0)]
111struct Foo { a: i32, }
112 ",
113 );
114 }
115
116 #[test]
117 fn add_derive_target() {
118 check_assist_target(
119 generate_derive,
120 "
121struct SomeThingIrrelevant;
122/// `Foo` is a pretty important struct.
123/// It does stuff.
124struct Foo { a: i32<|>, }
125struct EvenMoreIrrelevant;
126 ",
127 "/// `Foo` is a pretty important struct.
128/// It does stuff.
129struct Foo { a: i32, }",
130 );
131 }
132}
diff --git a/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs b/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs
deleted file mode 100644
index 9da23640a..000000000
--- a/crates/ra_assists/src/handlers/generate_from_impl_for_enum.rs
+++ /dev/null
@@ -1,200 +0,0 @@
1use ra_ide_db::RootDatabase;
2use ra_syntax::ast::{self, AstNode, NameOwner};
3use test_utils::mark;
4
5use crate::{utils::FamousDefs, AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: generate_from_impl_for_enum
8//
9// Adds a From impl for an enum variant with one tuple field.
10//
11// ```
12// enum A { <|>One(u32) }
13// ```
14// ->
15// ```
16// enum A { One(u32) }
17//
18// impl From<u32> for A {
19// fn from(v: u32) -> Self {
20// A::One(v)
21// }
22// }
23// ```
24pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
26 let variant_name = variant.name()?;
27 let enum_name = variant.parent_enum().name()?;
28 let field_list = match variant.kind() {
29 ast::StructKind::Tuple(field_list) => field_list,
30 _ => return None,
31 };
32 if field_list.fields().count() != 1 {
33 return None;
34 }
35 let field_type = field_list.fields().next()?.ty()?;
36 let path = match field_type {
37 ast::TypeRef::PathType(it) => it,
38 _ => return None,
39 };
40
41 if existing_from_impl(&ctx.sema, &variant).is_some() {
42 mark::hit!(test_add_from_impl_already_exists);
43 return None;
44 }
45
46 let target = variant.syntax().text_range();
47 acc.add(
48 AssistId("generate_from_impl_for_enum", AssistKind::Generate),
49 "Generate `From` impl for this enum variant",
50 target,
51 |edit| {
52 let start_offset = variant.parent_enum().syntax().text_range().end();
53 let buf = format!(
54 r#"
55
56impl From<{0}> for {1} {{
57 fn from(v: {0}) -> Self {{
58 {1}::{2}(v)
59 }}
60}}"#,
61 path.syntax(),
62 enum_name,
63 variant_name
64 );
65 edit.insert(start_offset, buf);
66 },
67 )
68}
69
70fn existing_from_impl(
71 sema: &'_ hir::Semantics<'_, RootDatabase>,
72 variant: &ast::Variant,
73) -> Option<()> {
74 let variant = sema.to_def(variant)?;
75 let enum_ = variant.parent_enum(sema.db);
76 let krate = enum_.module(sema.db).krate();
77
78 let from_trait = FamousDefs(sema, krate).core_convert_From()?;
79
80 let enum_type = enum_.ty(sema.db);
81
82 let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
83
84 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
85 Some(())
86 } else {
87 None
88 }
89}
90
91#[cfg(test)]
92mod tests {
93 use test_utils::mark;
94
95 use crate::tests::{check_assist, check_assist_not_applicable};
96
97 use super::*;
98
99 #[test]
100 fn test_generate_from_impl_for_enum() {
101 check_assist(
102 generate_from_impl_for_enum,
103 "enum A { <|>One(u32) }",
104 r#"enum A { One(u32) }
105
106impl From<u32> for A {
107 fn from(v: u32) -> Self {
108 A::One(v)
109 }
110}"#,
111 );
112 }
113
114 #[test]
115 fn test_generate_from_impl_for_enum_complicated_path() {
116 check_assist(
117 generate_from_impl_for_enum,
118 r#"enum A { <|>One(foo::bar::baz::Boo) }"#,
119 r#"enum A { One(foo::bar::baz::Boo) }
120
121impl From<foo::bar::baz::Boo> for A {
122 fn from(v: foo::bar::baz::Boo) -> Self {
123 A::One(v)
124 }
125}"#,
126 );
127 }
128
129 fn check_not_applicable(ra_fixture: &str) {
130 let fixture =
131 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
132 check_assist_not_applicable(generate_from_impl_for_enum, &fixture)
133 }
134
135 #[test]
136 fn test_add_from_impl_no_element() {
137 check_not_applicable("enum A { <|>One }");
138 }
139
140 #[test]
141 fn test_add_from_impl_more_than_one_element_in_tuple() {
142 check_not_applicable("enum A { <|>One(u32, String) }");
143 }
144
145 #[test]
146 fn test_add_from_impl_struct_variant() {
147 check_not_applicable("enum A { <|>One { x: u32 } }");
148 }
149
150 #[test]
151 fn test_add_from_impl_already_exists() {
152 mark::check!(test_add_from_impl_already_exists);
153 check_not_applicable(
154 r#"
155enum A { <|>One(u32), }
156
157impl From<u32> for A {
158 fn from(v: u32) -> Self {
159 A::One(v)
160 }
161}
162"#,
163 );
164 }
165
166 #[test]
167 fn test_add_from_impl_different_variant_impl_exists() {
168 check_assist(
169 generate_from_impl_for_enum,
170 r#"enum A { <|>One(u32), Two(String), }
171
172impl From<String> for A {
173 fn from(v: String) -> Self {
174 A::Two(v)
175 }
176}
177
178pub trait From<T> {
179 fn from(T) -> Self;
180}"#,
181 r#"enum A { One(u32), Two(String), }
182
183impl From<u32> for A {
184 fn from(v: u32) -> Self {
185 A::One(v)
186 }
187}
188
189impl From<String> for A {
190 fn from(v: String) -> Self {
191 A::Two(v)
192 }
193}
194
195pub trait From<T> {
196 fn from(T) -> Self;
197}"#,
198 );
199 }
200}
diff --git a/crates/ra_assists/src/handlers/generate_function.rs b/crates/ra_assists/src/handlers/generate_function.rs
deleted file mode 100644
index 56510861d..000000000
--- a/crates/ra_assists/src/handlers/generate_function.rs
+++ /dev/null
@@ -1,1058 +0,0 @@
1use hir::HirDisplay;
2use ra_db::FileId;
3use ra_syntax::{
4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make, ArgListOwner, AstNode, ModuleItemOwner,
8 },
9 SyntaxKind, SyntaxNode, TextSize,
10};
11use rustc_hash::{FxHashMap, FxHashSet};
12
13use crate::{
14 assist_config::SnippetCap,
15 utils::{render_snippet, Cursor},
16 AssistContext, AssistId, AssistKind, Assists,
17};
18
19// Assist: generate_function
20//
21// Adds a stub function with a signature matching the function under the cursor.
22//
23// ```
24// struct Baz;
25// fn baz() -> Baz { Baz }
26// fn foo() {
27// bar<|>("", baz());
28// }
29//
30// ```
31// ->
32// ```
33// struct Baz;
34// fn baz() -> Baz { Baz }
35// fn foo() {
36// bar("", baz());
37// }
38//
39// fn bar(arg: &str, baz: Baz) {
40// ${0:todo!()}
41// }
42//
43// ```
44pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
45 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
46 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
47 let path = path_expr.path()?;
48
49 if ctx.sema.resolve_path(&path).is_some() {
50 // The function call already resolves, no need to add a function
51 return None;
52 }
53
54 let target_module = match path.qualifier() {
55 Some(qualifier) => match ctx.sema.resolve_path(&qualifier) {
56 Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module),
57 _ => return None,
58 },
59 None => None,
60 };
61
62 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
63
64 let target = call.syntax().text_range();
65 acc.add(
66 AssistId("generate_function", AssistKind::Generate),
67 format!("Generate `{}` function", function_builder.fn_name),
68 target,
69 |builder| {
70 let function_template = function_builder.render();
71 builder.edit_file(function_template.file);
72 let new_fn = function_template.to_string(ctx.config.snippet_cap);
73 match ctx.config.snippet_cap {
74 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
75 None => builder.insert(function_template.insert_offset, new_fn),
76 }
77 },
78 )
79}
80
81struct FunctionTemplate {
82 insert_offset: TextSize,
83 placeholder_expr: ast::MacroCall,
84 leading_ws: String,
85 fn_def: ast::Fn,
86 trailing_ws: String,
87 file: FileId,
88}
89
90impl FunctionTemplate {
91 fn to_string(&self, cap: Option<SnippetCap>) -> String {
92 let f = match cap {
93 Some(cap) => render_snippet(
94 cap,
95 self.fn_def.syntax(),
96 Cursor::Replace(self.placeholder_expr.syntax()),
97 ),
98 None => self.fn_def.to_string(),
99 };
100 format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
101 }
102}
103
104struct FunctionBuilder {
105 target: GeneratedFunctionTarget,
106 fn_name: ast::Name,
107 type_params: Option<ast::GenericParamList>,
108 params: ast::ParamList,
109 file: FileId,
110 needs_pub: bool,
111}
112
113impl FunctionBuilder {
114 /// Prepares a generated function that matches `call`.
115 /// The function is generated in `target_module` or next to `call`
116 fn from_call(
117 ctx: &AssistContext,
118 call: &ast::CallExpr,
119 path: &ast::Path,
120 target_module: Option<hir::Module>,
121 ) -> Option<Self> {
122 let mut file = ctx.frange.file_id;
123 let target = match &target_module {
124 Some(target_module) => {
125 let module_source = target_module.definition_source(ctx.db());
126 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
127 file = in_file;
128 target
129 }
130 None => next_space_for_fn_after_call_site(&call)?,
131 };
132 let needs_pub = target_module.is_some();
133 let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?;
134 let fn_name = fn_name(&path)?;
135 let (type_params, params) = fn_args(ctx, target_module, &call)?;
136
137 Some(Self { target, fn_name, type_params, params, file, needs_pub })
138 }
139
140 fn render(self) -> FunctionTemplate {
141 let placeholder_expr = make::expr_todo();
142 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
143 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
144 let mut fn_def =
145 make::fn_def(visibility, self.fn_name, self.type_params, self.params, fn_body);
146 let leading_ws;
147 let trailing_ws;
148
149 let insert_offset = match self.target {
150 GeneratedFunctionTarget::BehindItem(it) => {
151 let indent = IndentLevel::from_node(&it);
152 leading_ws = format!("\n\n{}", indent);
153 fn_def = fn_def.indent(indent);
154 trailing_ws = String::new();
155 it.text_range().end()
156 }
157 GeneratedFunctionTarget::InEmptyItemList(it) => {
158 let indent = IndentLevel::from_node(it.syntax());
159 leading_ws = format!("\n{}", indent + 1);
160 fn_def = fn_def.indent(indent + 1);
161 trailing_ws = format!("\n{}", indent);
162 it.syntax().text_range().start() + TextSize::of('{')
163 }
164 };
165
166 let placeholder_expr =
167 fn_def.syntax().descendants().find_map(ast::MacroCall::cast).unwrap();
168 FunctionTemplate {
169 insert_offset,
170 placeholder_expr,
171 leading_ws,
172 fn_def,
173 trailing_ws,
174 file: self.file,
175 }
176 }
177}
178
179enum GeneratedFunctionTarget {
180 BehindItem(SyntaxNode),
181 InEmptyItemList(ast::ItemList),
182}
183
184impl GeneratedFunctionTarget {
185 fn syntax(&self) -> &SyntaxNode {
186 match self {
187 GeneratedFunctionTarget::BehindItem(it) => it,
188 GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(),
189 }
190 }
191}
192
193fn fn_name(call: &ast::Path) -> Option<ast::Name> {
194 let name = call.segment()?.syntax().to_string();
195 Some(make::name(&name))
196}
197
198/// Computes the type variables and arguments required for the generated function
199fn fn_args(
200 ctx: &AssistContext,
201 target_module: hir::Module,
202 call: &ast::CallExpr,
203) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
204 let mut arg_names = Vec::new();
205 let mut arg_types = Vec::new();
206 for arg in call.arg_list()?.args() {
207 arg_names.push(match fn_arg_name(&arg) {
208 Some(name) => name,
209 None => String::from("arg"),
210 });
211 arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
212 Some(ty) => ty,
213 None => String::from("()"),
214 });
215 }
216 deduplicate_arg_names(&mut arg_names);
217 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty));
218 Some((None, make::param_list(params)))
219}
220
221/// Makes duplicate argument names unique by appending incrementing numbers.
222///
223/// ```
224/// let mut names: Vec<String> =
225/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
226/// deduplicate_arg_names(&mut names);
227/// let expected: Vec<String> =
228/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
229/// assert_eq!(names, expected);
230/// ```
231fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
232 let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
233 *m.entry(name).or_insert(0) += 1;
234 m
235 });
236 let duplicate_arg_names: FxHashSet<String> = arg_name_counts
237 .into_iter()
238 .filter(|(_, count)| *count >= 2)
239 .map(|(name, _)| name.clone())
240 .collect();
241
242 let mut counter_per_name = FxHashMap::default();
243 for arg_name in arg_names.iter_mut() {
244 if duplicate_arg_names.contains(arg_name) {
245 let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
246 arg_name.push('_');
247 arg_name.push_str(&counter.to_string());
248 *counter += 1;
249 }
250 }
251}
252
253fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
254 match fn_arg {
255 ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
256 _ => Some(
257 fn_arg
258 .syntax()
259 .descendants()
260 .filter(|d| ast::NameRef::can_cast(d.kind()))
261 .last()?
262 .to_string(),
263 ),
264 }
265}
266
267fn fn_arg_type(
268 ctx: &AssistContext,
269 target_module: hir::Module,
270 fn_arg: &ast::Expr,
271) -> Option<String> {
272 let ty = ctx.sema.type_of_expr(fn_arg)?;
273 if ty.is_unknown() {
274 return None;
275 }
276
277 if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
278 Some(rendered)
279 } else {
280 None
281 }
282}
283
284/// Returns the position inside the current mod or file
285/// directly after the current block
286/// We want to write the generated function directly after
287/// fns, impls or macro calls, but inside mods
288fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
289 let mut ancestors = expr.syntax().ancestors().peekable();
290 let mut last_ancestor: Option<SyntaxNode> = None;
291 while let Some(next_ancestor) = ancestors.next() {
292 match next_ancestor.kind() {
293 SyntaxKind::SOURCE_FILE => {
294 break;
295 }
296 SyntaxKind::ITEM_LIST => {
297 if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
298 break;
299 }
300 }
301 _ => {}
302 }
303 last_ancestor = Some(next_ancestor);
304 }
305 last_ancestor.map(GeneratedFunctionTarget::BehindItem)
306}
307
308fn next_space_for_fn_in_module(
309 db: &dyn hir::db::AstDatabase,
310 module_source: &hir::InFile<hir::ModuleSource>,
311) -> Option<(FileId, GeneratedFunctionTarget)> {
312 let file = module_source.file_id.original_file(db);
313 let assist_item = match &module_source.value {
314 hir::ModuleSource::SourceFile(it) => {
315 if let Some(last_item) = it.items().last() {
316 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
317 } else {
318 GeneratedFunctionTarget::BehindItem(it.syntax().clone())
319 }
320 }
321 hir::ModuleSource::Module(it) => {
322 if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
323 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
324 } else {
325 GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
326 }
327 }
328 };
329 Some((file, assist_item))
330}
331
332#[cfg(test)]
333mod tests {
334 use crate::tests::{check_assist, check_assist_not_applicable};
335
336 use super::*;
337
338 #[test]
339 fn add_function_with_no_args() {
340 check_assist(
341 generate_function,
342 r"
343fn foo() {
344 bar<|>();
345}
346",
347 r"
348fn foo() {
349 bar();
350}
351
352fn bar() {
353 ${0:todo!()}
354}
355",
356 )
357 }
358
359 #[test]
360 fn add_function_from_method() {
361 // This ensures that the function is correctly generated
362 // in the next outer mod or file
363 check_assist(
364 generate_function,
365 r"
366impl Foo {
367 fn foo() {
368 bar<|>();
369 }
370}
371",
372 r"
373impl Foo {
374 fn foo() {
375 bar();
376 }
377}
378
379fn bar() {
380 ${0:todo!()}
381}
382",
383 )
384 }
385
386 #[test]
387 fn add_function_directly_after_current_block() {
388 // The new fn should not be created at the end of the file or module
389 check_assist(
390 generate_function,
391 r"
392fn foo1() {
393 bar<|>();
394}
395
396fn foo2() {}
397",
398 r"
399fn foo1() {
400 bar();
401}
402
403fn bar() {
404 ${0:todo!()}
405}
406
407fn foo2() {}
408",
409 )
410 }
411
412 #[test]
413 fn add_function_with_no_args_in_same_module() {
414 check_assist(
415 generate_function,
416 r"
417mod baz {
418 fn foo() {
419 bar<|>();
420 }
421}
422",
423 r"
424mod baz {
425 fn foo() {
426 bar();
427 }
428
429 fn bar() {
430 ${0:todo!()}
431 }
432}
433",
434 )
435 }
436
437 #[test]
438 fn add_function_with_function_call_arg() {
439 check_assist(
440 generate_function,
441 r"
442struct Baz;
443fn baz() -> Baz { todo!() }
444fn foo() {
445 bar<|>(baz());
446}
447",
448 r"
449struct Baz;
450fn baz() -> Baz { todo!() }
451fn foo() {
452 bar(baz());
453}
454
455fn bar(baz: Baz) {
456 ${0:todo!()}
457}
458",
459 );
460 }
461
462 #[test]
463 fn add_function_with_method_call_arg() {
464 check_assist(
465 generate_function,
466 r"
467struct Baz;
468impl Baz {
469 fn foo(&self) -> Baz {
470 ba<|>r(self.baz())
471 }
472 fn baz(&self) -> Baz {
473 Baz
474 }
475}
476",
477 r"
478struct Baz;
479impl Baz {
480 fn foo(&self) -> Baz {
481 bar(self.baz())
482 }
483 fn baz(&self) -> Baz {
484 Baz
485 }
486}
487
488fn bar(baz: Baz) {
489 ${0:todo!()}
490}
491",
492 )
493 }
494
495 #[test]
496 fn add_function_with_string_literal_arg() {
497 check_assist(
498 generate_function,
499 r#"
500fn foo() {
501 <|>bar("bar")
502}
503"#,
504 r#"
505fn foo() {
506 bar("bar")
507}
508
509fn bar(arg: &str) {
510 ${0:todo!()}
511}
512"#,
513 )
514 }
515
516 #[test]
517 fn add_function_with_char_literal_arg() {
518 check_assist(
519 generate_function,
520 r#"
521fn foo() {
522 <|>bar('x')
523}
524"#,
525 r#"
526fn foo() {
527 bar('x')
528}
529
530fn bar(arg: char) {
531 ${0:todo!()}
532}
533"#,
534 )
535 }
536
537 #[test]
538 fn add_function_with_int_literal_arg() {
539 check_assist(
540 generate_function,
541 r"
542fn foo() {
543 <|>bar(42)
544}
545",
546 r"
547fn foo() {
548 bar(42)
549}
550
551fn bar(arg: i32) {
552 ${0:todo!()}
553}
554",
555 )
556 }
557
558 #[test]
559 fn add_function_with_cast_int_literal_arg() {
560 check_assist(
561 generate_function,
562 r"
563fn foo() {
564 <|>bar(42 as u8)
565}
566",
567 r"
568fn foo() {
569 bar(42 as u8)
570}
571
572fn bar(arg: u8) {
573 ${0:todo!()}
574}
575",
576 )
577 }
578
579 #[test]
580 fn name_of_cast_variable_is_used() {
581 // Ensures that the name of the cast type isn't used
582 // in the generated function signature.
583 check_assist(
584 generate_function,
585 r"
586fn foo() {
587 let x = 42;
588 bar<|>(x as u8)
589}
590",
591 r"
592fn foo() {
593 let x = 42;
594 bar(x as u8)
595}
596
597fn bar(x: u8) {
598 ${0:todo!()}
599}
600",
601 )
602 }
603
604 #[test]
605 fn add_function_with_variable_arg() {
606 check_assist(
607 generate_function,
608 r"
609fn foo() {
610 let worble = ();
611 <|>bar(worble)
612}
613",
614 r"
615fn foo() {
616 let worble = ();
617 bar(worble)
618}
619
620fn bar(worble: ()) {
621 ${0:todo!()}
622}
623",
624 )
625 }
626
627 #[test]
628 fn add_function_with_impl_trait_arg() {
629 check_assist(
630 generate_function,
631 r"
632trait Foo {}
633fn foo() -> impl Foo {
634 todo!()
635}
636fn baz() {
637 <|>bar(foo())
638}
639",
640 r"
641trait Foo {}
642fn foo() -> impl Foo {
643 todo!()
644}
645fn baz() {
646 bar(foo())
647}
648
649fn bar(foo: impl Foo) {
650 ${0:todo!()}
651}
652",
653 )
654 }
655
656 #[test]
657 fn borrowed_arg() {
658 check_assist(
659 generate_function,
660 r"
661struct Baz;
662fn baz() -> Baz { todo!() }
663
664fn foo() {
665 bar<|>(&baz())
666}
667",
668 r"
669struct Baz;
670fn baz() -> Baz { todo!() }
671
672fn foo() {
673 bar(&baz())
674}
675
676fn bar(baz: &Baz) {
677 ${0:todo!()}
678}
679",
680 )
681 }
682
683 #[test]
684 fn add_function_with_qualified_path_arg() {
685 check_assist(
686 generate_function,
687 r"
688mod Baz {
689 pub struct Bof;
690 pub fn baz() -> Bof { Bof }
691}
692fn foo() {
693 <|>bar(Baz::baz())
694}
695",
696 r"
697mod Baz {
698 pub struct Bof;
699 pub fn baz() -> Bof { Bof }
700}
701fn foo() {
702 bar(Baz::baz())
703}
704
705fn bar(baz: Baz::Bof) {
706 ${0:todo!()}
707}
708",
709 )
710 }
711
712 #[test]
713 #[ignore]
714 // FIXME fix printing the generics of a `Ty` to make this test pass
715 fn add_function_with_generic_arg() {
716 check_assist(
717 generate_function,
718 r"
719fn foo<T>(t: T) {
720 <|>bar(t)
721}
722",
723 r"
724fn foo<T>(t: T) {
725 bar(t)
726}
727
728fn bar<T>(t: T) {
729 ${0:todo!()}
730}
731",
732 )
733 }
734
735 #[test]
736 #[ignore]
737 // FIXME Fix function type printing to make this test pass
738 fn add_function_with_fn_arg() {
739 check_assist(
740 generate_function,
741 r"
742struct Baz;
743impl Baz {
744 fn new() -> Self { Baz }
745}
746fn foo() {
747 <|>bar(Baz::new);
748}
749",
750 r"
751struct Baz;
752impl Baz {
753 fn new() -> Self { Baz }
754}
755fn foo() {
756 bar(Baz::new);
757}
758
759fn bar(arg: fn() -> Baz) {
760 ${0:todo!()}
761}
762",
763 )
764 }
765
766 #[test]
767 #[ignore]
768 // FIXME Fix closure type printing to make this test pass
769 fn add_function_with_closure_arg() {
770 check_assist(
771 generate_function,
772 r"
773fn foo() {
774 let closure = |x: i64| x - 1;
775 <|>bar(closure)
776}
777",
778 r"
779fn foo() {
780 let closure = |x: i64| x - 1;
781 bar(closure)
782}
783
784fn bar(closure: impl Fn(i64) -> i64) {
785 ${0:todo!()}
786}
787",
788 )
789 }
790
791 #[test]
792 fn unresolveable_types_default_to_unit() {
793 check_assist(
794 generate_function,
795 r"
796fn foo() {
797 <|>bar(baz)
798}
799",
800 r"
801fn foo() {
802 bar(baz)
803}
804
805fn bar(baz: ()) {
806 ${0:todo!()}
807}
808",
809 )
810 }
811
812 #[test]
813 fn arg_names_dont_overlap() {
814 check_assist(
815 generate_function,
816 r"
817struct Baz;
818fn baz() -> Baz { Baz }
819fn foo() {
820 <|>bar(baz(), baz())
821}
822",
823 r"
824struct Baz;
825fn baz() -> Baz { Baz }
826fn foo() {
827 bar(baz(), baz())
828}
829
830fn bar(baz_1: Baz, baz_2: Baz) {
831 ${0:todo!()}
832}
833",
834 )
835 }
836
837 #[test]
838 fn arg_name_counters_start_at_1_per_name() {
839 check_assist(
840 generate_function,
841 r#"
842struct Baz;
843fn baz() -> Baz { Baz }
844fn foo() {
845 <|>bar(baz(), baz(), "foo", "bar")
846}
847"#,
848 r#"
849struct Baz;
850fn baz() -> Baz { Baz }
851fn foo() {
852 bar(baz(), baz(), "foo", "bar")
853}
854
855fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) {
856 ${0:todo!()}
857}
858"#,
859 )
860 }
861
862 #[test]
863 fn add_function_in_module() {
864 check_assist(
865 generate_function,
866 r"
867mod bar {}
868
869fn foo() {
870 bar::my_fn<|>()
871}
872",
873 r"
874mod bar {
875 pub(crate) fn my_fn() {
876 ${0:todo!()}
877 }
878}
879
880fn foo() {
881 bar::my_fn()
882}
883",
884 )
885 }
886
887 #[test]
888 #[ignore]
889 // Ignored until local imports are supported.
890 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165
891 fn qualified_path_uses_correct_scope() {
892 check_assist(
893 generate_function,
894 "
895mod foo {
896 pub struct Foo;
897}
898fn bar() {
899 use foo::Foo;
900 let foo = Foo;
901 baz<|>(foo)
902}
903",
904 "
905mod foo {
906 pub struct Foo;
907}
908fn bar() {
909 use foo::Foo;
910 let foo = Foo;
911 baz(foo)
912}
913
914fn baz(foo: foo::Foo) {
915 ${0:todo!()}
916}
917",
918 )
919 }
920
921 #[test]
922 fn add_function_in_module_containing_other_items() {
923 check_assist(
924 generate_function,
925 r"
926mod bar {
927 fn something_else() {}
928}
929
930fn foo() {
931 bar::my_fn<|>()
932}
933",
934 r"
935mod bar {
936 fn something_else() {}
937
938 pub(crate) fn my_fn() {
939 ${0:todo!()}
940 }
941}
942
943fn foo() {
944 bar::my_fn()
945}
946",
947 )
948 }
949
950 #[test]
951 fn add_function_in_nested_module() {
952 check_assist(
953 generate_function,
954 r"
955mod bar {
956 mod baz {}
957}
958
959fn foo() {
960 bar::baz::my_fn<|>()
961}
962",
963 r"
964mod bar {
965 mod baz {
966 pub(crate) fn my_fn() {
967 ${0:todo!()}
968 }
969 }
970}
971
972fn foo() {
973 bar::baz::my_fn()
974}
975",
976 )
977 }
978
979 #[test]
980 fn add_function_in_another_file() {
981 check_assist(
982 generate_function,
983 r"
984//- /main.rs
985mod foo;
986
987fn main() {
988 foo::bar<|>()
989}
990//- /foo.rs
991",
992 r"
993
994
995pub(crate) fn bar() {
996 ${0:todo!()}
997}",
998 )
999 }
1000
1001 #[test]
1002 fn add_function_not_applicable_if_function_already_exists() {
1003 check_assist_not_applicable(
1004 generate_function,
1005 r"
1006fn foo() {
1007 bar<|>();
1008}
1009
1010fn bar() {}
1011",
1012 )
1013 }
1014
1015 #[test]
1016 fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
1017 check_assist_not_applicable(
1018 // bar is resolved, but baz isn't.
1019 // The assist is only active if the cursor is on an unresolved path,
1020 // but the assist should only be offered if the path is a function call.
1021 generate_function,
1022 r"
1023fn foo() {
1024 bar(b<|>az);
1025}
1026
1027fn bar(baz: ()) {}
1028",
1029 )
1030 }
1031
1032 #[test]
1033 #[ignore]
1034 fn create_method_with_no_args() {
1035 check_assist(
1036 generate_function,
1037 r"
1038struct Foo;
1039impl Foo {
1040 fn foo(&self) {
1041 self.bar()<|>;
1042 }
1043}
1044 ",
1045 r"
1046struct Foo;
1047impl Foo {
1048 fn foo(&self) {
1049 self.bar();
1050 }
1051 fn bar(&self) {
1052 todo!();
1053 }
1054}
1055 ",
1056 )
1057 }
1058}
diff --git a/crates/ra_assists/src/handlers/generate_impl.rs b/crates/ra_assists/src/handlers/generate_impl.rs
deleted file mode 100644
index d9b87c9c0..000000000
--- a/crates/ra_assists/src/handlers/generate_impl.rs
+++ /dev/null
@@ -1,109 +0,0 @@
1use ra_syntax::ast::{self, AstNode, GenericParamsOwner, NameOwner};
2use stdx::{format_to, SepBy};
3
4use crate::{AssistContext, AssistId, AssistKind, Assists};
5
6// Assist: generate_impl
7//
8// Adds a new inherent impl for a type.
9//
10// ```
11// struct Ctx<T: Clone> {
12// data: T,<|>
13// }
14// ```
15// ->
16// ```
17// struct Ctx<T: Clone> {
18// data: T,
19// }
20//
21// impl<T: Clone> Ctx<T> {
22// $0
23// }
24// ```
25pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
27 let name = nominal.name()?;
28 let target = nominal.syntax().text_range();
29 acc.add(
30 AssistId("generate_impl", AssistKind::Generate),
31 format!("Generate impl for `{}`", name),
32 target,
33 |edit| {
34 let type_params = nominal.generic_param_list();
35 let start_offset = nominal.syntax().text_range().end();
36 let mut buf = String::new();
37 buf.push_str("\n\nimpl");
38 if let Some(type_params) = &type_params {
39 format_to!(buf, "{}", type_params.syntax());
40 }
41 buf.push_str(" ");
42 buf.push_str(name.text().as_str());
43 if let Some(type_params) = type_params {
44 let lifetime_params = type_params
45 .lifetime_params()
46 .filter_map(|it| it.lifetime_token())
47 .map(|it| it.text().clone());
48 let type_params = type_params
49 .type_params()
50 .filter_map(|it| it.name())
51 .map(|it| it.text().clone());
52
53 let generic_params = lifetime_params.chain(type_params).sep_by(", ");
54 format_to!(buf, "<{}>", generic_params)
55 }
56 match ctx.config.snippet_cap {
57 Some(cap) => {
58 buf.push_str(" {\n $0\n}");
59 edit.insert_snippet(cap, start_offset, buf);
60 }
61 None => {
62 buf.push_str(" {\n}");
63 edit.insert(start_offset, buf);
64 }
65 }
66 },
67 )
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::tests::{check_assist, check_assist_target};
73
74 use super::*;
75
76 #[test]
77 fn test_add_impl() {
78 check_assist(
79 generate_impl,
80 "struct Foo {<|>}\n",
81 "struct Foo {}\n\nimpl Foo {\n $0\n}\n",
82 );
83 check_assist(
84 generate_impl,
85 "struct Foo<T: Clone> {<|>}",
86 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
87 );
88 check_assist(
89 generate_impl,
90 "struct Foo<'a, T: Foo<'a>> {<|>}",
91 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
92 );
93 }
94
95 #[test]
96 fn add_impl_target() {
97 check_assist_target(
98 generate_impl,
99 "
100struct SomeThingIrrelevant;
101/// Has a lifetime parameter
102struct Foo<'a, T: Foo<'a>> {<|>}
103struct EvenMoreIrrelevant;
104",
105 "/// Has a lifetime parameter
106struct Foo<'a, T: Foo<'a>> {}",
107 );
108 }
109}
diff --git a/crates/ra_assists/src/handlers/generate_new.rs b/crates/ra_assists/src/handlers/generate_new.rs
deleted file mode 100644
index b84aa24b6..000000000
--- a/crates/ra_assists/src/handlers/generate_new.rs
+++ /dev/null
@@ -1,420 +0,0 @@
1use hir::Adt;
2use ra_syntax::{
3 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
4 T,
5};
6use stdx::{format_to, SepBy};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10// Assist: generate_new
11//
12// Adds a new inherent impl for a type.
13//
14// ```
15// struct Ctx<T: Clone> {
16// data: T,<|>
17// }
18// ```
19// ->
20// ```
21// struct Ctx<T: Clone> {
22// data: T,
23// }
24//
25// impl<T: Clone> Ctx<T> {
26// fn $0new(data: T) -> Self { Self { data } }
27// }
28//
29// ```
30pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
32
33 // We want to only apply this to non-union structs with named fields
34 let field_list = match strukt.kind() {
35 StructKind::Record(named) => named,
36 _ => return None,
37 };
38
39 // Return early if we've found an existing new fn
40 let impl_def = find_struct_impl(&ctx, &strukt)?;
41
42 let target = strukt.syntax().text_range();
43 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
44 let mut buf = String::with_capacity(512);
45
46 if impl_def.is_some() {
47 buf.push('\n');
48 }
49
50 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
51
52 let params = field_list
53 .fields()
54 .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
55 .sep_by(", ");
56 let fields = field_list.fields().filter_map(|f| f.name()).sep_by(", ");
57
58 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
59
60 let start_offset = impl_def
61 .and_then(|impl_def| {
62 buf.push('\n');
63 let start = impl_def
64 .syntax()
65 .descendants_with_tokens()
66 .find(|t| t.kind() == T!['{'])?
67 .text_range()
68 .end();
69
70 Some(start)
71 })
72 .unwrap_or_else(|| {
73 buf = generate_impl_text(&strukt, &buf);
74 strukt.syntax().text_range().end()
75 });
76
77 match ctx.config.snippet_cap {
78 None => builder.insert(start_offset, buf),
79 Some(cap) => {
80 buf = buf.replace("fn new", "fn $0new");
81 builder.insert_snippet(cap, start_offset, buf);
82 }
83 }
84 })
85}
86
87// Generates the surrounding `impl Type { <code> }` including type and lifetime
88// parameters
89fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
90 let type_params = strukt.generic_param_list();
91 let mut buf = String::with_capacity(code.len());
92 buf.push_str("\n\nimpl");
93 if let Some(type_params) = &type_params {
94 format_to!(buf, "{}", type_params.syntax());
95 }
96 buf.push_str(" ");
97 buf.push_str(strukt.name().unwrap().text().as_str());
98 if let Some(type_params) = type_params {
99 let lifetime_params = type_params
100 .lifetime_params()
101 .filter_map(|it| it.lifetime_token())
102 .map(|it| it.text().clone());
103 let type_params =
104 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
105 format_to!(buf, "<{}>", lifetime_params.chain(type_params).sep_by(", "))
106 }
107
108 format_to!(buf, " {{\n{}\n}}\n", code);
109
110 buf
111}
112
113// Uses a syntax-driven approach to find any impl blocks for the struct that
114// exist within the module/file
115//
116// Returns `None` if we've found an existing `new` fn
117//
118// FIXME: change the new fn checking to a more semantic approach when that's more
119// viable (e.g. we process proc macros, etc)
120fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
121 let db = ctx.db();
122 let module = strukt.syntax().ancestors().find(|node| {
123 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
124 })?;
125
126 let struct_def = ctx.sema.to_def(strukt)?;
127
128 let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
129 let blk = ctx.sema.to_def(&impl_blk)?;
130
131 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
132 // (we currently use the wrong type parameter)
133 // also we wouldn't want to use e.g. `impl S<u32>`
134 let same_ty = match blk.target_ty(db).as_adt() {
135 Some(def) => def == Adt::Struct(struct_def),
136 None => false,
137 };
138 let not_trait_impl = blk.target_trait(db).is_none();
139
140 if !(same_ty && not_trait_impl) {
141 None
142 } else {
143 Some(impl_blk)
144 }
145 });
146
147 if let Some(ref impl_blk) = block {
148 if has_new_fn(impl_blk) {
149 return None;
150 }
151 }
152
153 Some(block)
154}
155
156fn has_new_fn(imp: &ast::Impl) -> bool {
157 if let Some(il) = imp.assoc_item_list() {
158 for item in il.assoc_items() {
159 if let ast::AssocItem::Fn(f) = item {
160 if let Some(name) = f.name() {
161 if name.text().eq_ignore_ascii_case("new") {
162 return true;
163 }
164 }
165 }
166 }
167 }
168
169 false
170}
171
172#[cfg(test)]
173mod tests {
174 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
175
176 use super::*;
177
178 #[test]
179 #[rustfmt::skip]
180 fn test_generate_new() {
181 // Check output of generation
182 check_assist(
183 generate_new,
184"struct Foo {<|>}",
185"struct Foo {}
186
187impl Foo {
188 fn $0new() -> Self { Self { } }
189}
190",
191 );
192 check_assist(
193 generate_new,
194"struct Foo<T: Clone> {<|>}",
195"struct Foo<T: Clone> {}
196
197impl<T: Clone> Foo<T> {
198 fn $0new() -> Self { Self { } }
199}
200",
201 );
202 check_assist(
203 generate_new,
204"struct Foo<'a, T: Foo<'a>> {<|>}",
205"struct Foo<'a, T: Foo<'a>> {}
206
207impl<'a, T: Foo<'a>> Foo<'a, T> {
208 fn $0new() -> Self { Self { } }
209}
210",
211 );
212 check_assist(
213 generate_new,
214"struct Foo { baz: String <|>}",
215"struct Foo { baz: String }
216
217impl Foo {
218 fn $0new(baz: String) -> Self { Self { baz } }
219}
220",
221 );
222 check_assist(
223 generate_new,
224"struct Foo { baz: String, qux: Vec<i32> <|>}",
225"struct Foo { baz: String, qux: Vec<i32> }
226
227impl Foo {
228 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
229}
230",
231 );
232
233 // Check that visibility modifiers don't get brought in for fields
234 check_assist(
235 generate_new,
236"struct Foo { pub baz: String, pub qux: Vec<i32> <|>}",
237"struct Foo { pub baz: String, pub qux: Vec<i32> }
238
239impl Foo {
240 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
241}
242",
243 );
244
245 // Check that it reuses existing impls
246 check_assist(
247 generate_new,
248"struct Foo {<|>}
249
250impl Foo {}
251",
252"struct Foo {}
253
254impl Foo {
255 fn $0new() -> Self { Self { } }
256}
257",
258 );
259 check_assist(
260 generate_new,
261"struct Foo {<|>}
262
263impl Foo {
264 fn qux(&self) {}
265}
266",
267"struct Foo {}
268
269impl Foo {
270 fn $0new() -> Self { Self { } }
271
272 fn qux(&self) {}
273}
274",
275 );
276
277 check_assist(
278 generate_new,
279"struct Foo {<|>}
280
281impl Foo {
282 fn qux(&self) {}
283 fn baz() -> i32 {
284 5
285 }
286}
287",
288"struct Foo {}
289
290impl Foo {
291 fn $0new() -> Self { Self { } }
292
293 fn qux(&self) {}
294 fn baz() -> i32 {
295 5
296 }
297}
298",
299 );
300
301 // Check visibility of new fn based on struct
302 check_assist(
303 generate_new,
304"pub struct Foo {<|>}",
305"pub struct Foo {}
306
307impl Foo {
308 pub fn $0new() -> Self { Self { } }
309}
310",
311 );
312 check_assist(
313 generate_new,
314"pub(crate) struct Foo {<|>}",
315"pub(crate) struct Foo {}
316
317impl Foo {
318 pub(crate) fn $0new() -> Self { Self { } }
319}
320",
321 );
322 }
323
324 #[test]
325 fn generate_new_not_applicable_if_fn_exists() {
326 check_assist_not_applicable(
327 generate_new,
328 "
329struct Foo {<|>}
330
331impl Foo {
332 fn new() -> Self {
333 Self
334 }
335}",
336 );
337
338 check_assist_not_applicable(
339 generate_new,
340 "
341struct Foo {<|>}
342
343impl Foo {
344 fn New() -> Self {
345 Self
346 }
347}",
348 );
349 }
350
351 #[test]
352 fn generate_new_target() {
353 check_assist_target(
354 generate_new,
355 "
356struct SomeThingIrrelevant;
357/// Has a lifetime parameter
358struct Foo<'a, T: Foo<'a>> {<|>}
359struct EvenMoreIrrelevant;
360",
361 "/// Has a lifetime parameter
362struct Foo<'a, T: Foo<'a>> {}",
363 );
364 }
365
366 #[test]
367 fn test_unrelated_new() {
368 check_assist(
369 generate_new,
370 r##"
371pub struct AstId<N: AstNode> {
372 file_id: HirFileId,
373 file_ast_id: FileAstId<N>,
374}
375
376impl<N: AstNode> AstId<N> {
377 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
378 AstId { file_id, file_ast_id }
379 }
380}
381
382pub struct Source<T> {
383 pub file_id: HirFileId,<|>
384 pub ast: T,
385}
386
387impl<T> Source<T> {
388 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
389 Source { file_id: self.file_id, ast: f(self.ast) }
390 }
391}
392"##,
393 r##"
394pub struct AstId<N: AstNode> {
395 file_id: HirFileId,
396 file_ast_id: FileAstId<N>,
397}
398
399impl<N: AstNode> AstId<N> {
400 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
401 AstId { file_id, file_ast_id }
402 }
403}
404
405pub struct Source<T> {
406 pub file_id: HirFileId,
407 pub ast: T,
408}
409
410impl<T> Source<T> {
411 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
412
413 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
414 Source { file_id: self.file_id, ast: f(self.ast) }
415 }
416}
417"##,
418 );
419 }
420}
diff --git a/crates/ra_assists/src/handlers/inline_local_variable.rs b/crates/ra_assists/src/handlers/inline_local_variable.rs
deleted file mode 100644
index 2fdfabaf5..000000000
--- a/crates/ra_assists/src/handlers/inline_local_variable.rs
+++ /dev/null
@@ -1,695 +0,0 @@
1use ra_ide_db::defs::Definition;
2use ra_syntax::{
3 ast::{self, AstNode, AstToken},
4 TextRange,
5};
6use test_utils::mark;
7
8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId, AssistKind,
11};
12
13// Assist: inline_local_variable
14//
15// Inlines local variable.
16//
17// ```
18// fn main() {
19// let x<|> = 1 + 2;
20// x * 4;
21// }
22// ```
23// ->
24// ```
25// fn main() {
26// (1 + 2) * 4;
27// }
28// ```
29pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
31 let bind_pat = match let_stmt.pat()? {
32 ast::Pat::BindPat(pat) => pat,
33 _ => return None,
34 };
35 if bind_pat.mut_token().is_some() {
36 mark::hit!(test_not_inline_mut_variable);
37 return None;
38 }
39 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
40 mark::hit!(not_applicable_outside_of_bind_pat);
41 return None;
42 }
43 let initializer_expr = let_stmt.initializer()?;
44
45 let def = ctx.sema.to_def(&bind_pat)?;
46 let def = Definition::Local(def);
47 let refs = def.find_usages(&ctx.sema, None);
48 if refs.is_empty() {
49 mark::hit!(test_not_applicable_if_variable_unused);
50 return None;
51 };
52
53 let delete_range = if let Some(whitespace) = let_stmt
54 .syntax()
55 .next_sibling_or_token()
56 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
57 {
58 TextRange::new(
59 let_stmt.syntax().text_range().start(),
60 whitespace.syntax().text_range().end(),
61 )
62 } else {
63 let_stmt.syntax().text_range()
64 };
65
66 let mut wrap_in_parens = vec![true; refs.len()];
67
68 for (i, desc) in refs.iter().enumerate() {
69 let usage_node = ctx
70 .covering_node_for_range(desc.file_range.range)
71 .ancestors()
72 .find_map(ast::PathExpr::cast)?;
73 let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
74 let usage_parent = match usage_parent_option {
75 Some(u) => u,
76 None => {
77 wrap_in_parens[i] = false;
78 continue;
79 }
80 };
81
82 wrap_in_parens[i] = match (&initializer_expr, usage_parent) {
83 (ast::Expr::CallExpr(_), _)
84 | (ast::Expr::IndexExpr(_), _)
85 | (ast::Expr::MethodCallExpr(_), _)
86 | (ast::Expr::FieldExpr(_), _)
87 | (ast::Expr::TryExpr(_), _)
88 | (ast::Expr::RefExpr(_), _)
89 | (ast::Expr::Literal(_), _)
90 | (ast::Expr::TupleExpr(_), _)
91 | (ast::Expr::ArrayExpr(_), _)
92 | (ast::Expr::ParenExpr(_), _)
93 | (ast::Expr::PathExpr(_), _)
94 | (ast::Expr::BlockExpr(_), _)
95 | (ast::Expr::EffectExpr(_), _)
96 | (_, ast::Expr::CallExpr(_))
97 | (_, ast::Expr::TupleExpr(_))
98 | (_, ast::Expr::ArrayExpr(_))
99 | (_, ast::Expr::ParenExpr(_))
100 | (_, ast::Expr::ForExpr(_))
101 | (_, ast::Expr::WhileExpr(_))
102 | (_, ast::Expr::BreakExpr(_))
103 | (_, ast::Expr::ReturnExpr(_))
104 | (_, ast::Expr::MatchExpr(_)) => false,
105 _ => true,
106 };
107 }
108
109 let init_str = initializer_expr.syntax().text().to_string();
110 let init_in_paren = format!("({})", &init_str);
111
112 let target = bind_pat.syntax().text_range();
113 acc.add(
114 AssistId("inline_local_variable", AssistKind::RefactorInline),
115 "Inline variable",
116 target,
117 move |builder| {
118 builder.delete(delete_range);
119 for (desc, should_wrap) in refs.iter().zip(wrap_in_parens) {
120 let replacement =
121 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
122 builder.replace(desc.file_range.range, replacement)
123 }
124 },
125 )
126}
127
128#[cfg(test)]
129mod tests {
130 use test_utils::mark;
131
132 use crate::tests::{check_assist, check_assist_not_applicable};
133
134 use super::*;
135
136 #[test]
137 fn test_inline_let_bind_literal_expr() {
138 check_assist(
139 inline_local_variable,
140 r"
141fn bar(a: usize) {}
142fn foo() {
143 let a<|> = 1;
144 a + 1;
145 if a > 10 {
146 }
147
148 while a > 10 {
149
150 }
151 let b = a * 10;
152 bar(a);
153}",
154 r"
155fn bar(a: usize) {}
156fn foo() {
157 1 + 1;
158 if 1 > 10 {
159 }
160
161 while 1 > 10 {
162
163 }
164 let b = 1 * 10;
165 bar(1);
166}",
167 );
168 }
169
170 #[test]
171 fn test_inline_let_bind_bin_expr() {
172 check_assist(
173 inline_local_variable,
174 r"
175fn bar(a: usize) {}
176fn foo() {
177 let a<|> = 1 + 1;
178 a + 1;
179 if a > 10 {
180 }
181
182 while a > 10 {
183
184 }
185 let b = a * 10;
186 bar(a);
187}",
188 r"
189fn bar(a: usize) {}
190fn foo() {
191 (1 + 1) + 1;
192 if (1 + 1) > 10 {
193 }
194
195 while (1 + 1) > 10 {
196
197 }
198 let b = (1 + 1) * 10;
199 bar(1 + 1);
200}",
201 );
202 }
203
204 #[test]
205 fn test_inline_let_bind_function_call_expr() {
206 check_assist(
207 inline_local_variable,
208 r"
209fn bar(a: usize) {}
210fn foo() {
211 let a<|> = bar(1);
212 a + 1;
213 if a > 10 {
214 }
215
216 while a > 10 {
217
218 }
219 let b = a * 10;
220 bar(a);
221}",
222 r"
223fn bar(a: usize) {}
224fn foo() {
225 bar(1) + 1;
226 if bar(1) > 10 {
227 }
228
229 while bar(1) > 10 {
230
231 }
232 let b = bar(1) * 10;
233 bar(bar(1));
234}",
235 );
236 }
237
238 #[test]
239 fn test_inline_let_bind_cast_expr() {
240 check_assist(
241 inline_local_variable,
242 r"
243fn bar(a: usize): usize { a }
244fn foo() {
245 let a<|> = bar(1) as u64;
246 a + 1;
247 if a > 10 {
248 }
249
250 while a > 10 {
251
252 }
253 let b = a * 10;
254 bar(a);
255}",
256 r"
257fn bar(a: usize): usize { a }
258fn foo() {
259 (bar(1) as u64) + 1;
260 if (bar(1) as u64) > 10 {
261 }
262
263 while (bar(1) as u64) > 10 {
264
265 }
266 let b = (bar(1) as u64) * 10;
267 bar(bar(1) as u64);
268}",
269 );
270 }
271
272 #[test]
273 fn test_inline_let_bind_block_expr() {
274 check_assist(
275 inline_local_variable,
276 r"
277fn foo() {
278 let a<|> = { 10 + 1 };
279 a + 1;
280 if a > 10 {
281 }
282
283 while a > 10 {
284
285 }
286 let b = a * 10;
287 bar(a);
288}",
289 r"
290fn foo() {
291 { 10 + 1 } + 1;
292 if { 10 + 1 } > 10 {
293 }
294
295 while { 10 + 1 } > 10 {
296
297 }
298 let b = { 10 + 1 } * 10;
299 bar({ 10 + 1 });
300}",
301 );
302 }
303
304 #[test]
305 fn test_inline_let_bind_paren_expr() {
306 check_assist(
307 inline_local_variable,
308 r"
309fn foo() {
310 let a<|> = ( 10 + 1 );
311 a + 1;
312 if a > 10 {
313 }
314
315 while a > 10 {
316
317 }
318 let b = a * 10;
319 bar(a);
320}",
321 r"
322fn foo() {
323 ( 10 + 1 ) + 1;
324 if ( 10 + 1 ) > 10 {
325 }
326
327 while ( 10 + 1 ) > 10 {
328
329 }
330 let b = ( 10 + 1 ) * 10;
331 bar(( 10 + 1 ));
332}",
333 );
334 }
335
336 #[test]
337 fn test_not_inline_mut_variable() {
338 mark::check!(test_not_inline_mut_variable);
339 check_assist_not_applicable(
340 inline_local_variable,
341 r"
342fn foo() {
343 let mut a<|> = 1 + 1;
344 a + 1;
345}",
346 );
347 }
348
349 #[test]
350 fn test_call_expr() {
351 check_assist(
352 inline_local_variable,
353 r"
354fn foo() {
355 let a<|> = bar(10 + 1);
356 let b = a * 10;
357 let c = a as usize;
358}",
359 r"
360fn foo() {
361 let b = bar(10 + 1) * 10;
362 let c = bar(10 + 1) as usize;
363}",
364 );
365 }
366
367 #[test]
368 fn test_index_expr() {
369 check_assist(
370 inline_local_variable,
371 r"
372fn foo() {
373 let x = vec![1, 2, 3];
374 let a<|> = x[0];
375 let b = a * 10;
376 let c = a as usize;
377}",
378 r"
379fn foo() {
380 let x = vec![1, 2, 3];
381 let b = x[0] * 10;
382 let c = x[0] as usize;
383}",
384 );
385 }
386
387 #[test]
388 fn test_method_call_expr() {
389 check_assist(
390 inline_local_variable,
391 r"
392fn foo() {
393 let bar = vec![1];
394 let a<|> = bar.len();
395 let b = a * 10;
396 let c = a as usize;
397}",
398 r"
399fn foo() {
400 let bar = vec![1];
401 let b = bar.len() * 10;
402 let c = bar.len() as usize;
403}",
404 );
405 }
406
407 #[test]
408 fn test_field_expr() {
409 check_assist(
410 inline_local_variable,
411 r"
412struct Bar {
413 foo: usize
414}
415
416fn foo() {
417 let bar = Bar { foo: 1 };
418 let a<|> = bar.foo;
419 let b = a * 10;
420 let c = a as usize;
421}",
422 r"
423struct Bar {
424 foo: usize
425}
426
427fn foo() {
428 let bar = Bar { foo: 1 };
429 let b = bar.foo * 10;
430 let c = bar.foo as usize;
431}",
432 );
433 }
434
435 #[test]
436 fn test_try_expr() {
437 check_assist(
438 inline_local_variable,
439 r"
440fn foo() -> Option<usize> {
441 let bar = Some(1);
442 let a<|> = bar?;
443 let b = a * 10;
444 let c = a as usize;
445 None
446}",
447 r"
448fn foo() -> Option<usize> {
449 let bar = Some(1);
450 let b = bar? * 10;
451 let c = bar? as usize;
452 None
453}",
454 );
455 }
456
457 #[test]
458 fn test_ref_expr() {
459 check_assist(
460 inline_local_variable,
461 r"
462fn foo() {
463 let bar = 10;
464 let a<|> = &bar;
465 let b = a * 10;
466}",
467 r"
468fn foo() {
469 let bar = 10;
470 let b = &bar * 10;
471}",
472 );
473 }
474
475 #[test]
476 fn test_tuple_expr() {
477 check_assist(
478 inline_local_variable,
479 r"
480fn foo() {
481 let a<|> = (10, 20);
482 let b = a[0];
483}",
484 r"
485fn foo() {
486 let b = (10, 20)[0];
487}",
488 );
489 }
490
491 #[test]
492 fn test_array_expr() {
493 check_assist(
494 inline_local_variable,
495 r"
496fn foo() {
497 let a<|> = [1, 2, 3];
498 let b = a.len();
499}",
500 r"
501fn foo() {
502 let b = [1, 2, 3].len();
503}",
504 );
505 }
506
507 #[test]
508 fn test_paren() {
509 check_assist(
510 inline_local_variable,
511 r"
512fn foo() {
513 let a<|> = (10 + 20);
514 let b = a * 10;
515 let c = a as usize;
516}",
517 r"
518fn foo() {
519 let b = (10 + 20) * 10;
520 let c = (10 + 20) as usize;
521}",
522 );
523 }
524
525 #[test]
526 fn test_path_expr() {
527 check_assist(
528 inline_local_variable,
529 r"
530fn foo() {
531 let d = 10;
532 let a<|> = d;
533 let b = a * 10;
534 let c = a as usize;
535}",
536 r"
537fn foo() {
538 let d = 10;
539 let b = d * 10;
540 let c = d as usize;
541}",
542 );
543 }
544
545 #[test]
546 fn test_block_expr() {
547 check_assist(
548 inline_local_variable,
549 r"
550fn foo() {
551 let a<|> = { 10 };
552 let b = a * 10;
553 let c = a as usize;
554}",
555 r"
556fn foo() {
557 let b = { 10 } * 10;
558 let c = { 10 } as usize;
559}",
560 );
561 }
562
563 #[test]
564 fn test_used_in_different_expr1() {
565 check_assist(
566 inline_local_variable,
567 r"
568fn foo() {
569 let a<|> = 10 + 20;
570 let b = a * 10;
571 let c = (a, 20);
572 let d = [a, 10];
573 let e = (a);
574}",
575 r"
576fn foo() {
577 let b = (10 + 20) * 10;
578 let c = (10 + 20, 20);
579 let d = [10 + 20, 10];
580 let e = (10 + 20);
581}",
582 );
583 }
584
585 #[test]
586 fn test_used_in_for_expr() {
587 check_assist(
588 inline_local_variable,
589 r"
590fn foo() {
591 let a<|> = vec![10, 20];
592 for i in a {}
593}",
594 r"
595fn foo() {
596 for i in vec![10, 20] {}
597}",
598 );
599 }
600
601 #[test]
602 fn test_used_in_while_expr() {
603 check_assist(
604 inline_local_variable,
605 r"
606fn foo() {
607 let a<|> = 1 > 0;
608 while a {}
609}",
610 r"
611fn foo() {
612 while 1 > 0 {}
613}",
614 );
615 }
616
617 #[test]
618 fn test_used_in_break_expr() {
619 check_assist(
620 inline_local_variable,
621 r"
622fn foo() {
623 let a<|> = 1 + 1;
624 loop {
625 break a;
626 }
627}",
628 r"
629fn foo() {
630 loop {
631 break 1 + 1;
632 }
633}",
634 );
635 }
636
637 #[test]
638 fn test_used_in_return_expr() {
639 check_assist(
640 inline_local_variable,
641 r"
642fn foo() {
643 let a<|> = 1 > 0;
644 return a;
645}",
646 r"
647fn foo() {
648 return 1 > 0;
649}",
650 );
651 }
652
653 #[test]
654 fn test_used_in_match_expr() {
655 check_assist(
656 inline_local_variable,
657 r"
658fn foo() {
659 let a<|> = 1 > 0;
660 match a {}
661}",
662 r"
663fn foo() {
664 match 1 > 0 {}
665}",
666 );
667 }
668
669 #[test]
670 fn test_not_applicable_if_variable_unused() {
671 mark::check!(test_not_applicable_if_variable_unused);
672 check_assist_not_applicable(
673 inline_local_variable,
674 r"
675fn foo() {
676 let <|>a = 0;
677}
678 ",
679 )
680 }
681
682 #[test]
683 fn not_applicable_outside_of_bind_pat() {
684 mark::check!(not_applicable_outside_of_bind_pat);
685 check_assist_not_applicable(
686 inline_local_variable,
687 r"
688fn main() {
689 let x = <|>1 + 2;
690 x * 4;
691}
692",
693 )
694 }
695}
diff --git a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs b/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
deleted file mode 100644
index c3134f64d..000000000
--- a/crates/ra_assists/src/handlers/introduce_named_lifetime.rs
+++ /dev/null
@@ -1,318 +0,0 @@
1use ra_syntax::{
2 ast::{self, GenericParamsOwner, NameOwner},
3 AstNode, SyntaxKind, TextRange, TextSize,
4};
5use rustc_hash::FxHashSet;
6
7use crate::{assist_context::AssistBuilder, AssistContext, AssistId, AssistKind, Assists};
8
9static ASSIST_NAME: &str = "introduce_named_lifetime";
10static ASSIST_LABEL: &str = "Introduce named lifetime";
11
12// Assist: introduce_named_lifetime
13//
14// Change an anonymous lifetime to a named lifetime.
15//
16// ```
17// impl Cursor<'_<|>> {
18// fn node(self) -> &SyntaxNode {
19// match self {
20// Cursor::Replace(node) | Cursor::Before(node) => node,
21// }
22// }
23// }
24// ```
25// ->
26// ```
27// impl<'a> Cursor<'a> {
28// fn node(self) -> &SyntaxNode {
29// match self {
30// Cursor::Replace(node) | Cursor::Before(node) => node,
31// }
32// }
33// }
34// ```
35// FIXME: How can we handle renaming any one of multiple anonymous lifetimes?
36// FIXME: should also add support for the case fun(f: &Foo) -> &<|>Foo
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime_token = ctx
39 .find_token_at_offset(SyntaxKind::LIFETIME)
40 .filter(|lifetime| lifetime.text() == "'_")?;
41 if let Some(fn_def) = lifetime_token.ancestors().find_map(ast::Fn::cast) {
42 generate_fn_def_assist(acc, &fn_def, lifetime_token.text_range())
43 } else if let Some(impl_def) = lifetime_token.ancestors().find_map(ast::Impl::cast) {
44 generate_impl_def_assist(acc, &impl_def, lifetime_token.text_range())
45 } else {
46 None
47 }
48}
49
50/// Generate the assist for the fn def case
51fn generate_fn_def_assist(
52 acc: &mut Assists,
53 fn_def: &ast::Fn,
54 lifetime_loc: TextRange,
55) -> Option<()> {
56 let param_list: ast::ParamList = fn_def.param_list()?;
57 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?;
58 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
59 let self_param =
60 // use the self if it's a reference and has no explicit lifetime
61 param_list.self_param().filter(|p| p.lifetime_token().is_none() && p.amp_token().is_some());
62 // compute the location which implicitly has the same lifetime as the anonymous lifetime
63 let loc_needing_lifetime = if let Some(self_param) = self_param {
64 // if we have a self reference, use that
65 Some(self_param.self_token()?.text_range().start())
66 } else {
67 // otherwise, if there's a single reference parameter without a named liftime, use that
68 let fn_params_without_lifetime: Vec<_> = param_list
69 .params()
70 .filter_map(|param| match param.ty() {
71 Some(ast::TypeRef::ReferenceType(ascribed_type))
72 if ascribed_type.lifetime_token() == None =>
73 {
74 Some(ascribed_type.amp_token()?.text_range().end())
75 }
76 _ => None,
77 })
78 .collect();
79 match fn_params_without_lifetime.len() {
80 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
81 0 => None,
82 // multiple unnnamed is invalid. assist is not applicable
83 _ => return None,
84 }
85 };
86 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
87 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
88 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
89 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
90 })
91}
92
93/// Generate the assist for the impl def case
94fn generate_impl_def_assist(
95 acc: &mut Assists,
96 impl_def: &ast::Impl,
97 lifetime_loc: TextRange,
98) -> Option<()> {
99 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
100 let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
101 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
102 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
103 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
104 })
105}
106
107/// Given a type parameter list, generate a unique lifetime parameter name
108/// which is not in the list
109fn generate_unique_lifetime_param_name(
110 existing_type_param_list: &Option<ast::GenericParamList>,
111) -> Option<char> {
112 match existing_type_param_list {
113 Some(type_params) => {
114 let used_lifetime_params: FxHashSet<_> = type_params
115 .lifetime_params()
116 .map(|p| p.syntax().text().to_string()[1..].to_owned())
117 .collect();
118 (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
119 }
120 None => Some('a'),
121 }
122}
123
124/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
125/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
126fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
127 type_params_owner: &TypeParamsOwner,
128 builder: &mut AssistBuilder,
129 new_type_params_loc: TextSize,
130 new_lifetime_param: char,
131) {
132 match type_params_owner.generic_param_list() {
133 // add the new lifetime parameter to an existing type param list
134 Some(type_params) => {
135 builder.insert(
136 (u32::from(type_params.syntax().text_range().end()) - 1).into(),
137 format!(", '{}", new_lifetime_param),
138 );
139 }
140 // create a new type param list containing only the new lifetime parameter
141 None => {
142 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
143 }
144 }
145}
146
147#[cfg(test)]
148mod tests {
149 use super::*;
150 use crate::tests::{check_assist, check_assist_not_applicable};
151
152 #[test]
153 fn test_example_case() {
154 check_assist(
155 introduce_named_lifetime,
156 r#"impl Cursor<'_<|>> {
157 fn node(self) -> &SyntaxNode {
158 match self {
159 Cursor::Replace(node) | Cursor::Before(node) => node,
160 }
161 }
162 }"#,
163 r#"impl<'a> Cursor<'a> {
164 fn node(self) -> &SyntaxNode {
165 match self {
166 Cursor::Replace(node) | Cursor::Before(node) => node,
167 }
168 }
169 }"#,
170 );
171 }
172
173 #[test]
174 fn test_example_case_simplified() {
175 check_assist(
176 introduce_named_lifetime,
177 r#"impl Cursor<'_<|>> {"#,
178 r#"impl<'a> Cursor<'a> {"#,
179 );
180 }
181
182 #[test]
183 fn test_example_case_cursor_after_tick() {
184 check_assist(
185 introduce_named_lifetime,
186 r#"impl Cursor<'<|>_> {"#,
187 r#"impl<'a> Cursor<'a> {"#,
188 );
189 }
190
191 #[test]
192 fn test_impl_with_other_type_param() {
193 check_assist(
194 introduce_named_lifetime,
195 "impl<I> fmt::Display for SepByBuilder<'_<|>, I>
196 where
197 I: Iterator,
198 I::Item: fmt::Display,
199 {",
200 "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
201 where
202 I: Iterator,
203 I::Item: fmt::Display,
204 {",
205 )
206 }
207
208 #[test]
209 fn test_example_case_cursor_before_tick() {
210 check_assist(
211 introduce_named_lifetime,
212 r#"impl Cursor<<|>'_> {"#,
213 r#"impl<'a> Cursor<'a> {"#,
214 );
215 }
216
217 #[test]
218 fn test_not_applicable_cursor_position() {
219 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_><|> {"#);
220 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<|><'_> {"#);
221 }
222
223 #[test]
224 fn test_not_applicable_lifetime_already_name() {
225 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a<|>> {"#);
226 check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a<|>>"#);
227 }
228
229 #[test]
230 fn test_with_type_parameter() {
231 check_assist(
232 introduce_named_lifetime,
233 r#"impl<T> Cursor<T, '_<|>>"#,
234 r#"impl<T, 'a> Cursor<T, 'a>"#,
235 );
236 }
237
238 #[test]
239 fn test_with_existing_lifetime_name_conflict() {
240 check_assist(
241 introduce_named_lifetime,
242 r#"impl<'a, 'b> Cursor<'a, 'b, '_<|>>"#,
243 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
244 );
245 }
246
247 #[test]
248 fn test_function_return_value_anon_lifetime_param() {
249 check_assist(
250 introduce_named_lifetime,
251 r#"fn my_fun() -> X<'_<|>>"#,
252 r#"fn my_fun<'a>() -> X<'a>"#,
253 );
254 }
255
256 #[test]
257 fn test_function_return_value_anon_reference_lifetime() {
258 check_assist(
259 introduce_named_lifetime,
260 r#"fn my_fun() -> &'_<|> X"#,
261 r#"fn my_fun<'a>() -> &'a X"#,
262 );
263 }
264
265 #[test]
266 fn test_function_param_anon_lifetime() {
267 check_assist(
268 introduce_named_lifetime,
269 r#"fn my_fun(x: X<'_<|>>)"#,
270 r#"fn my_fun<'a>(x: X<'a>)"#,
271 );
272 }
273
274 #[test]
275 fn test_function_add_lifetime_to_params() {
276 check_assist(
277 introduce_named_lifetime,
278 r#"fn my_fun(f: &Foo) -> X<'_<|>>"#,
279 r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
280 );
281 }
282
283 #[test]
284 fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
285 check_assist(
286 introduce_named_lifetime,
287 r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
288 r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
289 );
290 }
291
292 #[test]
293 fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
294 // this is not permitted under lifetime elision rules
295 check_assist_not_applicable(
296 introduce_named_lifetime,
297 r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_<|>>"#,
298 );
299 }
300
301 #[test]
302 fn test_function_add_lifetime_to_self_ref_param() {
303 check_assist(
304 introduce_named_lifetime,
305 r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
306 r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
307 );
308 }
309
310 #[test]
311 fn test_function_add_lifetime_to_param_with_non_ref_self() {
312 check_assist(
313 introduce_named_lifetime,
314 r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_<|>>"#,
315 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
316 );
317 }
318}
diff --git a/crates/ra_assists/src/handlers/invert_if.rs b/crates/ra_assists/src/handlers/invert_if.rs
deleted file mode 100644
index bbe3f3643..000000000
--- a/crates/ra_assists/src/handlers/invert_if.rs
+++ /dev/null
@@ -1,109 +0,0 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 T,
4};
5
6use crate::{
7 assist_context::{AssistContext, Assists},
8 utils::invert_boolean_expression,
9 AssistId, AssistKind,
10};
11
12// Assist: invert_if
13//
14// Apply invert_if
15// This transforms if expressions of the form `if !x {A} else {B}` into `if x {B} else {A}`
16// This also works with `!=`. This assist can only be applied with the cursor
17// on `if`.
18//
19// ```
20// fn main() {
21// if<|> !y { A } else { B }
22// }
23// ```
24// ->
25// ```
26// fn main() {
27// if y { B } else { A }
28// }
29// ```
30
31pub(crate) fn invert_if(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let if_keyword = ctx.find_token_at_offset(T![if])?;
33 let expr = ast::IfExpr::cast(if_keyword.parent())?;
34 let if_range = if_keyword.text_range();
35 let cursor_in_range = if_range.contains_range(ctx.frange.range);
36 if !cursor_in_range {
37 return None;
38 }
39
40 // This assist should not apply for if-let.
41 if expr.condition()?.pat().is_some() {
42 return None;
43 }
44
45 let cond = expr.condition()?.expr()?;
46 let then_node = expr.then_branch()?.syntax().clone();
47 let else_block = match expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
51
52 let cond_range = cond.syntax().text_range();
53 let flip_cond = invert_boolean_expression(cond);
54 let else_node = else_block.syntax();
55 let else_range = else_node.text_range();
56 let then_range = then_node.text_range();
57 acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
58 edit.replace(cond_range, flip_cond.syntax().text());
59 edit.replace(else_range, then_node.text());
60 edit.replace(then_range, else_node.text());
61 })
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 use crate::tests::{check_assist, check_assist_not_applicable};
69
70 #[test]
71 fn invert_if_remove_inequality() {
72 check_assist(
73 invert_if,
74 "fn f() { i<|>f x != 3 { 1 } else { 3 + 2 } }",
75 "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
76 )
77 }
78
79 #[test]
80 fn invert_if_remove_not() {
81 check_assist(
82 invert_if,
83 "fn f() { <|>if !cond { 3 * 2 } else { 1 } }",
84 "fn f() { if cond { 1 } else { 3 * 2 } }",
85 )
86 }
87
88 #[test]
89 fn invert_if_general_case() {
90 check_assist(
91 invert_if,
92 "fn f() { i<|>f cond { 3 * 2 } else { 1 } }",
93 "fn f() { if !cond { 1 } else { 3 * 2 } }",
94 )
95 }
96
97 #[test]
98 fn invert_if_doesnt_apply_with_cursor_not_on_if() {
99 check_assist_not_applicable(invert_if, "fn f() { if !<|>cond { 3 * 2 } else { 1 } }")
100 }
101
102 #[test]
103 fn invert_if_doesnt_apply_with_if_let() {
104 check_assist_not_applicable(
105 invert_if,
106 "fn f() { i<|>f let Some(_) = Some(1) { 1 } else { 0 } }",
107 )
108 }
109}
diff --git a/crates/ra_assists/src/handlers/merge_imports.rs b/crates/ra_assists/src/handlers/merge_imports.rs
deleted file mode 100644
index c775fe25c..000000000
--- a/crates/ra_assists/src/handlers/merge_imports.rs
+++ /dev/null
@@ -1,294 +0,0 @@
1use std::iter::successors;
2
3use ra_syntax::{
4 algo::{neighbor, skip_trivia_token, SyntaxRewriter},
5 ast::{self, edit::AstNodeEdit, make},
6 AstNode, Direction, InsertPosition, SyntaxElement, T,
7};
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: merge_imports
15//
16// Merges two imports with a common prefix.
17//
18// ```
19// use std::<|>fmt::Formatter;
20// use std::io;
21// ```
22// ->
23// ```
24// use std::{fmt::Formatter, io};
25// ```
26pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let tree: ast::UseTree = ctx.find_node_at_offset()?;
28 let mut rewriter = SyntaxRewriter::default();
29 let mut offset = ctx.offset();
30
31 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
32 let (merged, to_delete) = next_prev()
33 .filter_map(|dir| neighbor(&use_item, dir))
34 .filter_map(|it| Some((it.clone(), it.use_tree()?)))
35 .find_map(|(use_item, use_tree)| {
36 Some((try_merge_trees(&tree, &use_tree)?, use_item))
37 })?;
38
39 rewriter.replace_ast(&tree, &merged);
40 rewriter += to_delete.remove();
41
42 if to_delete.syntax().text_range().end() < offset {
43 offset -= to_delete.syntax().text_range().len();
44 }
45 } else {
46 let (merged, to_delete) = next_prev()
47 .filter_map(|dir| neighbor(&tree, dir))
48 .find_map(|use_tree| Some((try_merge_trees(&tree, &use_tree)?, use_tree.clone())))?;
49
50 rewriter.replace_ast(&tree, &merged);
51 rewriter += to_delete.remove();
52
53 if to_delete.syntax().text_range().end() < offset {
54 offset -= to_delete.syntax().text_range().len();
55 }
56 };
57
58 let target = tree.syntax().text_range();
59 acc.add(
60 AssistId("merge_imports", AssistKind::RefactorRewrite),
61 "Merge imports",
62 target,
63 |builder| {
64 builder.rewrite(rewriter);
65 },
66 )
67}
68
69fn next_prev() -> impl Iterator<Item = Direction> {
70 [Direction::Next, Direction::Prev].iter().copied()
71}
72
73fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
74 let lhs_path = old.path()?;
75 let rhs_path = new.path()?;
76
77 let (lhs_prefix, rhs_prefix) = common_prefix(&lhs_path, &rhs_path)?;
78
79 let lhs = old.split_prefix(&lhs_prefix);
80 let rhs = new.split_prefix(&rhs_prefix);
81
82 let should_insert_comma = lhs
83 .use_tree_list()?
84 .r_curly_token()
85 .and_then(|it| skip_trivia_token(it.prev_token()?, Direction::Prev))
86 .map(|it| it.kind() != T![,])
87 .unwrap_or(true);
88
89 let mut to_insert: Vec<SyntaxElement> = Vec::new();
90 if should_insert_comma {
91 to_insert.push(make::token(T![,]).into());
92 to_insert.push(make::tokens::single_space().into());
93 }
94 to_insert.extend(
95 rhs.use_tree_list()?
96 .syntax()
97 .children_with_tokens()
98 .filter(|it| it.kind() != T!['{'] && it.kind() != T!['}']),
99 );
100 let use_tree_list = lhs.use_tree_list()?;
101 let pos = InsertPosition::Before(use_tree_list.r_curly_token()?.into());
102 let use_tree_list = use_tree_list.insert_children(pos, to_insert);
103 Some(lhs.with_use_tree_list(use_tree_list))
104}
105
106fn common_prefix(lhs: &ast::Path, rhs: &ast::Path) -> Option<(ast::Path, ast::Path)> {
107 let mut res = None;
108 let mut lhs_curr = first_path(&lhs);
109 let mut rhs_curr = first_path(&rhs);
110 loop {
111 match (lhs_curr.segment(), rhs_curr.segment()) {
112 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
113 _ => break,
114 }
115 res = Some((lhs_curr.clone(), rhs_curr.clone()));
116
117 match (lhs_curr.parent_path(), rhs_curr.parent_path()) {
118 (Some(lhs), Some(rhs)) => {
119 lhs_curr = lhs;
120 rhs_curr = rhs;
121 }
122 _ => break,
123 }
124 }
125
126 res
127}
128
129fn first_path(path: &ast::Path) -> ast::Path {
130 successors(Some(path.clone()), |it| it.qualifier()).last().unwrap()
131}
132
133#[cfg(test)]
134mod tests {
135 use crate::tests::{check_assist, check_assist_not_applicable};
136
137 use super::*;
138
139 #[test]
140 fn test_merge_first() {
141 check_assist(
142 merge_imports,
143 r"
144use std::fmt<|>::Debug;
145use std::fmt::Display;
146",
147 r"
148use std::fmt::{Debug, Display};
149",
150 )
151 }
152
153 #[test]
154 fn test_merge_second() {
155 check_assist(
156 merge_imports,
157 r"
158use std::fmt::Debug;
159use std::fmt<|>::Display;
160",
161 r"
162use std::fmt::{Display, Debug};
163",
164 );
165 }
166
167 #[test]
168 fn test_merge_nested() {
169 check_assist(
170 merge_imports,
171 r"
172use std::{fmt<|>::Debug, fmt::Display};
173",
174 r"
175use std::{fmt::{Debug, Display}};
176",
177 );
178 check_assist(
179 merge_imports,
180 r"
181use std::{fmt::Debug, fmt<|>::Display};
182",
183 r"
184use std::{fmt::{Display, Debug}};
185",
186 );
187 }
188
189 #[test]
190 fn test_merge_single_wildcard_diff_prefixes() {
191 check_assist(
192 merge_imports,
193 r"
194use std<|>::cell::*;
195use std::str;
196",
197 r"
198use std::{cell::*, str};
199",
200 )
201 }
202
203 #[test]
204 fn test_merge_both_wildcard_diff_prefixes() {
205 check_assist(
206 merge_imports,
207 r"
208use std<|>::cell::*;
209use std::str::*;
210",
211 r"
212use std::{cell::*, str::*};
213",
214 )
215 }
216
217 #[test]
218 fn removes_just_enough_whitespace() {
219 check_assist(
220 merge_imports,
221 r"
222use foo<|>::bar;
223use foo::baz;
224
225/// Doc comment
226",
227 r"
228use foo::{bar, baz};
229
230/// Doc comment
231",
232 );
233 }
234
235 #[test]
236 fn works_with_trailing_comma() {
237 check_assist(
238 merge_imports,
239 r"
240use {
241 foo<|>::bar,
242 foo::baz,
243};
244",
245 r"
246use {
247 foo::{bar, baz},
248};
249",
250 );
251 check_assist(
252 merge_imports,
253 r"
254use {
255 foo::baz,
256 foo<|>::bar,
257};
258",
259 r"
260use {
261 foo::{bar, baz},
262};
263",
264 );
265 }
266
267 #[test]
268 fn test_double_comma() {
269 check_assist(
270 merge_imports,
271 r"
272use foo::bar::baz;
273use foo::<|>{
274 FooBar,
275};
276",
277 r"
278use foo::{
279 FooBar,
280bar::baz};
281",
282 )
283 }
284
285 #[test]
286 fn test_empty_use() {
287 check_assist_not_applicable(
288 merge_imports,
289 r"
290use std::<|>
291fn main() {}",
292 );
293 }
294}
diff --git a/crates/ra_assists/src/handlers/merge_match_arms.rs b/crates/ra_assists/src/handlers/merge_match_arms.rs
deleted file mode 100644
index 186a1f618..000000000
--- a/crates/ra_assists/src/handlers/merge_match_arms.rs
+++ /dev/null
@@ -1,248 +0,0 @@
1use std::iter::successors;
2
3use ra_syntax::{
4 algo::neighbor,
5 ast::{self, AstNode},
6 Direction,
7};
8
9use crate::{AssistContext, AssistId, AssistKind, Assists, TextRange};
10
11// Assist: merge_match_arms
12//
13// Merges identical match arms.
14//
15// ```
16// enum Action { Move { distance: u32 }, Stop }
17//
18// fn handle(action: Action) {
19// match action {
20// <|>Action::Move(..) => foo(),
21// Action::Stop => foo(),
22// }
23// }
24// ```
25// ->
26// ```
27// enum Action { Move { distance: u32 }, Stop }
28//
29// fn handle(action: Action) {
30// match action {
31// Action::Move(..) | Action::Stop => foo(),
32// }
33// }
34// ```
35pub(crate) fn merge_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let current_arm = ctx.find_node_at_offset::<ast::MatchArm>()?;
37 // Don't try to handle arms with guards for now - can add support for this later
38 if current_arm.guard().is_some() {
39 return None;
40 }
41 let current_expr = current_arm.expr()?;
42 let current_text_range = current_arm.syntax().text_range();
43
44 // We check if the following match arms match this one. We could, but don't,
45 // compare to the previous match arm as well.
46 let arms_to_merge = successors(Some(current_arm), |it| neighbor(it, Direction::Next))
47 .take_while(|arm| {
48 if arm.guard().is_some() {
49 return false;
50 }
51 match arm.expr() {
52 Some(expr) => expr.syntax().text() == current_expr.syntax().text(),
53 None => false,
54 }
55 })
56 .collect::<Vec<_>>();
57
58 if arms_to_merge.len() <= 1 {
59 return None;
60 }
61
62 acc.add(
63 AssistId("merge_match_arms", AssistKind::RefactorRewrite),
64 "Merge match arms",
65 current_text_range,
66 |edit| {
67 let pats = if arms_to_merge.iter().any(contains_placeholder) {
68 "_".into()
69 } else {
70 arms_to_merge
71 .iter()
72 .filter_map(ast::MatchArm::pat)
73 .map(|x| x.syntax().to_string())
74 .collect::<Vec<String>>()
75 .join(" | ")
76 };
77
78 let arm = format!("{} => {}", pats, current_expr.syntax().text());
79
80 let start = arms_to_merge.first().unwrap().syntax().text_range().start();
81 let end = arms_to_merge.last().unwrap().syntax().text_range().end();
82
83 edit.replace(TextRange::new(start, end), arm);
84 },
85 )
86}
87
88fn contains_placeholder(a: &ast::MatchArm) -> bool {
89 matches!(a.pat(), Some(ast::Pat::PlaceholderPat(..)))
90}
91
92#[cfg(test)]
93mod tests {
94 use crate::tests::{check_assist, check_assist_not_applicable};
95
96 use super::*;
97
98 #[test]
99 fn merge_match_arms_single_patterns() {
100 check_assist(
101 merge_match_arms,
102 r#"
103 #[derive(Debug)]
104 enum X { A, B, C }
105
106 fn main() {
107 let x = X::A;
108 let y = match x {
109 X::A => { 1i32<|> }
110 X::B => { 1i32 }
111 X::C => { 2i32 }
112 }
113 }
114 "#,
115 r#"
116 #[derive(Debug)]
117 enum X { A, B, C }
118
119 fn main() {
120 let x = X::A;
121 let y = match x {
122 X::A | X::B => { 1i32 }
123 X::C => { 2i32 }
124 }
125 }
126 "#,
127 );
128 }
129
130 #[test]
131 fn merge_match_arms_multiple_patterns() {
132 check_assist(
133 merge_match_arms,
134 r#"
135 #[derive(Debug)]
136 enum X { A, B, C, D, E }
137
138 fn main() {
139 let x = X::A;
140 let y = match x {
141 X::A | X::B => {<|> 1i32 },
142 X::C | X::D => { 1i32 },
143 X::E => { 2i32 },
144 }
145 }
146 "#,
147 r#"
148 #[derive(Debug)]
149 enum X { A, B, C, D, E }
150
151 fn main() {
152 let x = X::A;
153 let y = match x {
154 X::A | X::B | X::C | X::D => { 1i32 },
155 X::E => { 2i32 },
156 }
157 }
158 "#,
159 );
160 }
161
162 #[test]
163 fn merge_match_arms_placeholder_pattern() {
164 check_assist(
165 merge_match_arms,
166 r#"
167 #[derive(Debug)]
168 enum X { A, B, C, D, E }
169
170 fn main() {
171 let x = X::A;
172 let y = match x {
173 X::A => { 1i32 },
174 X::B => { 2i<|>32 },
175 _ => { 2i32 }
176 }
177 }
178 "#,
179 r#"
180 #[derive(Debug)]
181 enum X { A, B, C, D, E }
182
183 fn main() {
184 let x = X::A;
185 let y = match x {
186 X::A => { 1i32 },
187 _ => { 2i32 }
188 }
189 }
190 "#,
191 );
192 }
193
194 #[test]
195 fn merges_all_subsequent_arms() {
196 check_assist(
197 merge_match_arms,
198 r#"
199 enum X { A, B, C, D, E }
200
201 fn main() {
202 match X::A {
203 X::A<|> => 92,
204 X::B => 92,
205 X::C => 92,
206 X::D => 62,
207 _ => panic!(),
208 }
209 }
210 "#,
211 r#"
212 enum X { A, B, C, D, E }
213
214 fn main() {
215 match X::A {
216 X::A | X::B | X::C => 92,
217 X::D => 62,
218 _ => panic!(),
219 }
220 }
221 "#,
222 )
223 }
224
225 #[test]
226 fn merge_match_arms_rejects_guards() {
227 check_assist_not_applicable(
228 merge_match_arms,
229 r#"
230 #[derive(Debug)]
231 enum X {
232 A(i32),
233 B,
234 C
235 }
236
237 fn main() {
238 let x = X::A;
239 let y = match x {
240 X::A(a) if a > 5 => { <|>1i32 },
241 X::B => { 1i32 },
242 X::C => { 2i32 }
243 }
244 }
245 "#,
246 );
247 }
248}
diff --git a/crates/ra_assists/src/handlers/move_bounds.rs b/crates/ra_assists/src/handlers/move_bounds.rs
deleted file mode 100644
index 6d394443e..000000000
--- a/crates/ra_assists/src/handlers/move_bounds.rs
+++ /dev/null
@@ -1,152 +0,0 @@
1use ra_syntax::{
2 ast::{self, edit::AstNodeEdit, make, AstNode, NameOwner, TypeBoundsOwner},
3 match_ast,
4 SyntaxKind::*,
5 T,
6};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10// Assist: move_bounds_to_where_clause
11//
12// Moves inline type bounds to a where clause.
13//
14// ```
15// fn apply<T, U, <|>F: FnOnce(T) -> U>(f: F, x: T) -> U {
16// f(x)
17// }
18// ```
19// ->
20// ```
21// fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
22// f(x)
23// }
24// ```
25pub(crate) fn move_bounds_to_where_clause(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let type_param_list = ctx.find_node_at_offset::<ast::GenericParamList>()?;
27
28 let mut type_params = type_param_list.type_params();
29 if type_params.all(|p| p.type_bound_list().is_none()) {
30 return None;
31 }
32
33 let parent = type_param_list.syntax().parent()?;
34 if parent.children_with_tokens().any(|it| it.kind() == WHERE_CLAUSE) {
35 return None;
36 }
37
38 let anchor = match_ast! {
39 match parent {
40 ast::Fn(it) => it.body()?.syntax().clone().into(),
41 ast::Trait(it) => it.assoc_item_list()?.syntax().clone().into(),
42 ast::Impl(it) => it.assoc_item_list()?.syntax().clone().into(),
43 ast::Enum(it) => it.variant_list()?.syntax().clone().into(),
44 ast::Struct(it) => {
45 it.syntax().children_with_tokens()
46 .find(|it| it.kind() == RECORD_FIELD_LIST || it.kind() == T![;])?
47 },
48 _ => return None
49 }
50 };
51
52 let target = type_param_list.syntax().text_range();
53 acc.add(
54 AssistId("move_bounds_to_where_clause", AssistKind::RefactorRewrite),
55 "Move to where clause",
56 target,
57 |edit| {
58 let new_params = type_param_list
59 .type_params()
60 .filter(|it| it.type_bound_list().is_some())
61 .map(|type_param| {
62 let without_bounds = type_param.remove_bounds();
63 (type_param, without_bounds)
64 });
65
66 let new_type_param_list = type_param_list.replace_descendants(new_params);
67 edit.replace_ast(type_param_list.clone(), new_type_param_list);
68
69 let where_clause = {
70 let predicates = type_param_list.type_params().filter_map(build_predicate);
71 make::where_clause(predicates)
72 };
73
74 let to_insert = match anchor.prev_sibling_or_token() {
75 Some(ref elem) if elem.kind() == WHITESPACE => {
76 format!("{} ", where_clause.syntax())
77 }
78 _ => format!(" {}", where_clause.syntax()),
79 };
80 edit.insert(anchor.text_range().start(), to_insert);
81 },
82 )
83}
84
85fn build_predicate(param: ast::TypeParam) -> Option<ast::WherePred> {
86 let path = {
87 let name_ref = make::name_ref(&param.name()?.syntax().to_string());
88 let segment = make::path_segment(name_ref);
89 make::path_unqualified(segment)
90 };
91 let predicate = make::where_pred(path, param.type_bound_list()?.bounds());
92 Some(predicate)
93}
94
95#[cfg(test)]
96mod tests {
97 use super::*;
98
99 use crate::tests::check_assist;
100
101 #[test]
102 fn move_bounds_to_where_clause_fn() {
103 check_assist(
104 move_bounds_to_where_clause,
105 r#"
106 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
107 "#,
108 r#"
109 fn foo<T, F>() where T: u32, F: FnOnce(T) -> T {}
110 "#,
111 );
112 }
113
114 #[test]
115 fn move_bounds_to_where_clause_impl() {
116 check_assist(
117 move_bounds_to_where_clause,
118 r#"
119 impl<U: u32, <|>T> A<U, T> {}
120 "#,
121 r#"
122 impl<U, T> A<U, T> where U: u32 {}
123 "#,
124 );
125 }
126
127 #[test]
128 fn move_bounds_to_where_clause_struct() {
129 check_assist(
130 move_bounds_to_where_clause,
131 r#"
132 struct A<<|>T: Iterator<Item = u32>> {}
133 "#,
134 r#"
135 struct A<T> where T: Iterator<Item = u32> {}
136 "#,
137 );
138 }
139
140 #[test]
141 fn move_bounds_to_where_clause_tuple_struct() {
142 check_assist(
143 move_bounds_to_where_clause,
144 r#"
145 struct Pair<<|>T: u32>(T, T);
146 "#,
147 r#"
148 struct Pair<T>(T, T) where T: u32;
149 "#,
150 );
151 }
152}
diff --git a/crates/ra_assists/src/handlers/move_guard.rs b/crates/ra_assists/src/handlers/move_guard.rs
deleted file mode 100644
index 4060d34c6..000000000
--- a/crates/ra_assists/src/handlers/move_guard.rs
+++ /dev/null
@@ -1,303 +0,0 @@
1use ra_syntax::{
2 ast::{AstNode, IfExpr, MatchArm},
3 SyntaxKind::WHITESPACE,
4};
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: move_guard_to_arm_body
9//
10// Moves match guard into match arm body.
11//
12// ```
13// enum Action { Move { distance: u32 }, Stop }
14//
15// fn handle(action: Action) {
16// match action {
17// Action::Move { distance } <|>if distance > 10 => foo(),
18// _ => (),
19// }
20// }
21// ```
22// ->
23// ```
24// enum Action { Move { distance: u32 }, Stop }
25//
26// fn handle(action: Action) {
27// match action {
28// Action::Move { distance } => if distance > 10 { foo() },
29// _ => (),
30// }
31// }
32// ```
33pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
35 let guard = match_arm.guard()?;
36 let space_before_guard = guard.syntax().prev_sibling_or_token();
37
38 let guard_conditions = guard.expr()?;
39 let arm_expr = match_arm.expr()?;
40 let buf = format!("if {} {{ {} }}", guard_conditions.syntax().text(), arm_expr.syntax().text());
41
42 let target = guard.syntax().text_range();
43 acc.add(
44 AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
45 "Move guard to arm body",
46 target,
47 |edit| {
48 match space_before_guard {
49 Some(element) if element.kind() == WHITESPACE => {
50 edit.delete(element.text_range());
51 }
52 _ => (),
53 };
54
55 edit.delete(guard.syntax().text_range());
56 edit.replace_node_and_indent(arm_expr.syntax(), buf);
57 },
58 )
59}
60
61// Assist: move_arm_cond_to_match_guard
62//
63// Moves if expression from match arm body into a guard.
64//
65// ```
66// enum Action { Move { distance: u32 }, Stop }
67//
68// fn handle(action: Action) {
69// match action {
70// Action::Move { distance } => <|>if distance > 10 { foo() },
71// _ => (),
72// }
73// }
74// ```
75// ->
76// ```
77// enum Action { Move { distance: u32 }, Stop }
78//
79// fn handle(action: Action) {
80// match action {
81// Action::Move { distance } if distance > 10 => foo(),
82// _ => (),
83// }
84// }
85// ```
86pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
87 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
88 let match_pat = match_arm.pat()?;
89
90 let arm_body = match_arm.expr()?;
91 let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone())?;
92 let cond = if_expr.condition()?;
93 let then_block = if_expr.then_branch()?;
94
95 // Not support if with else branch
96 if if_expr.else_branch().is_some() {
97 return None;
98 }
99 // Not support moving if let to arm guard
100 if cond.pat().is_some() {
101 return None;
102 }
103
104 let buf = format!(" if {}", cond.syntax().text());
105
106 let target = if_expr.syntax().text_range();
107 acc.add(
108 AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
109 "Move condition to match guard",
110 target,
111 |edit| {
112 let then_only_expr = then_block.statements().next().is_none();
113
114 match &then_block.expr() {
115 Some(then_expr) if then_only_expr => {
116 edit.replace(if_expr.syntax().text_range(), then_expr.syntax().text())
117 }
118 _ => edit.replace(if_expr.syntax().text_range(), then_block.syntax().text()),
119 }
120
121 edit.insert(match_pat.syntax().text_range().end(), buf);
122 },
123 )
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129
130 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
131
132 #[test]
133 fn move_guard_to_arm_body_target() {
134 check_assist_target(
135 move_guard_to_arm_body,
136 r#"
137 fn f() {
138 let t = 'a';
139 let chars = "abcd";
140 match t {
141 '\r' <|>if chars.clone().next() == Some('\n') => false,
142 _ => true
143 }
144 }
145 "#,
146 r#"if chars.clone().next() == Some('\n')"#,
147 );
148 }
149
150 #[test]
151 fn move_guard_to_arm_body_works() {
152 check_assist(
153 move_guard_to_arm_body,
154 r#"
155 fn f() {
156 let t = 'a';
157 let chars = "abcd";
158 match t {
159 '\r' <|>if chars.clone().next() == Some('\n') => false,
160 _ => true
161 }
162 }
163 "#,
164 r#"
165 fn f() {
166 let t = 'a';
167 let chars = "abcd";
168 match t {
169 '\r' => if chars.clone().next() == Some('\n') { false },
170 _ => true
171 }
172 }
173 "#,
174 );
175 }
176
177 #[test]
178 fn move_guard_to_arm_body_works_complex_match() {
179 check_assist(
180 move_guard_to_arm_body,
181 r#"
182 fn f() {
183 match x {
184 <|>y @ 4 | y @ 5 if y > 5 => true,
185 _ => false
186 }
187 }
188 "#,
189 r#"
190 fn f() {
191 match x {
192 y @ 4 | y @ 5 => if y > 5 { true },
193 _ => false
194 }
195 }
196 "#,
197 );
198 }
199
200 #[test]
201 fn move_arm_cond_to_match_guard_works() {
202 check_assist(
203 move_arm_cond_to_match_guard,
204 r#"
205 fn f() {
206 let t = 'a';
207 let chars = "abcd";
208 match t {
209 '\r' => if chars.clone().next() == Some('\n') { <|>false },
210 _ => true
211 }
212 }
213 "#,
214 r#"
215 fn f() {
216 let t = 'a';
217 let chars = "abcd";
218 match t {
219 '\r' if chars.clone().next() == Some('\n') => false,
220 _ => true
221 }
222 }
223 "#,
224 );
225 }
226
227 #[test]
228 fn move_arm_cond_to_match_guard_if_let_not_works() {
229 check_assist_not_applicable(
230 move_arm_cond_to_match_guard,
231 r#"
232 fn f() {
233 let t = 'a';
234 let chars = "abcd";
235 match t {
236 '\r' => if let Some(_) = chars.clone().next() { <|>false },
237 _ => true
238 }
239 }
240 "#,
241 );
242 }
243
244 #[test]
245 fn move_arm_cond_to_match_guard_if_empty_body_works() {
246 check_assist(
247 move_arm_cond_to_match_guard,
248 r#"
249 fn f() {
250 let t = 'a';
251 let chars = "abcd";
252 match t {
253 '\r' => if chars.clone().next().is_some() { <|> },
254 _ => true
255 }
256 }
257 "#,
258 r#"
259 fn f() {
260 let t = 'a';
261 let chars = "abcd";
262 match t {
263 '\r' if chars.clone().next().is_some() => { },
264 _ => true
265 }
266 }
267 "#,
268 );
269 }
270
271 #[test]
272 fn move_arm_cond_to_match_guard_if_multiline_body_works() {
273 check_assist(
274 move_arm_cond_to_match_guard,
275 r#"
276 fn f() {
277 let mut t = 'a';
278 let chars = "abcd";
279 match t {
280 '\r' => if chars.clone().next().is_some() {
281 t = 'e';<|>
282 false
283 },
284 _ => true
285 }
286 }
287 "#,
288 r#"
289 fn f() {
290 let mut t = 'a';
291 let chars = "abcd";
292 match t {
293 '\r' if chars.clone().next().is_some() => {
294 t = 'e';
295 false
296 },
297 _ => true
298 }
299 }
300 "#,
301 );
302 }
303}
diff --git a/crates/ra_assists/src/handlers/raw_string.rs b/crates/ra_assists/src/handlers/raw_string.rs
deleted file mode 100644
index 4e8a0c2db..000000000
--- a/crates/ra_assists/src/handlers/raw_string.rs
+++ /dev/null
@@ -1,504 +0,0 @@
1use std::borrow::Cow;
2
3use ra_syntax::{
4 ast::{self, HasQuotes, HasStringValue},
5 AstToken,
6 SyntaxKind::{RAW_STRING, STRING},
7 TextRange, TextSize,
8};
9use test_utils::mark;
10
11use crate::{AssistContext, AssistId, AssistKind, Assists};
12
13// Assist: make_raw_string
14//
15// Adds `r#` to a plain string literal.
16//
17// ```
18// fn main() {
19// "Hello,<|> World!";
20// }
21// ```
22// ->
23// ```
24// fn main() {
25// r#"Hello, World!"#;
26// }
27// ```
28pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let token = ctx.find_token_at_offset(STRING).and_then(ast::String::cast)?;
30 let value = token.value()?;
31 let target = token.syntax().text_range();
32 acc.add(
33 AssistId("make_raw_string", AssistKind::RefactorRewrite),
34 "Rewrite as raw string",
35 target,
36 |edit| {
37 let hashes = "#".repeat(required_hashes(&value).max(1));
38 if matches!(value, Cow::Borrowed(_)) {
39 // Avoid replacing the whole string to better position the cursor.
40 edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
41 edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
42 } else {
43 edit.replace(
44 token.syntax().text_range(),
45 format!("r{}\"{}\"{}", hashes, value, hashes),
46 );
47 }
48 },
49 )
50}
51
52// Assist: make_usual_string
53//
54// Turns a raw string into a plain string.
55//
56// ```
57// fn main() {
58// r#"Hello,<|> "World!""#;
59// }
60// ```
61// ->
62// ```
63// fn main() {
64// "Hello, \"World!\"";
65// }
66// ```
67pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
68 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
69 let value = token.value()?;
70 let target = token.syntax().text_range();
71 acc.add(
72 AssistId("make_usual_string", AssistKind::RefactorRewrite),
73 "Rewrite as regular string",
74 target,
75 |edit| {
76 // parse inside string to escape `"`
77 let escaped = value.escape_default().to_string();
78 if let Some(offsets) = token.quote_offsets() {
79 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
80 edit.replace(offsets.quotes.0, "\"");
81 edit.replace(offsets.quotes.1, "\"");
82 return;
83 }
84 }
85
86 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
87 },
88 )
89}
90
91// Assist: add_hash
92//
93// Adds a hash to a raw string literal.
94//
95// ```
96// fn main() {
97// r#"Hello,<|> World!"#;
98// }
99// ```
100// ->
101// ```
102// fn main() {
103// r##"Hello, World!"##;
104// }
105// ```
106pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
107 let token = ctx.find_token_at_offset(RAW_STRING)?;
108 let target = token.text_range();
109 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
110 edit.insert(token.text_range().start() + TextSize::of('r'), "#");
111 edit.insert(token.text_range().end(), "#");
112 })
113}
114
115// Assist: remove_hash
116//
117// Removes a hash from a raw string literal.
118//
119// ```
120// fn main() {
121// r#"Hello,<|> World!"#;
122// }
123// ```
124// ->
125// ```
126// fn main() {
127// r"Hello, World!";
128// }
129// ```
130pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
131 let token = ctx.find_token_at_offset(RAW_STRING).and_then(ast::RawString::cast)?;
132
133 let text = token.text().as_str();
134 if !text.starts_with("r#") && text.ends_with('#') {
135 return None;
136 }
137
138 let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
139
140 let text_range = token.syntax().text_range();
141 let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
142
143 if existing_hashes == required_hashes(internal_text) {
144 mark::hit!(cant_remove_required_hash);
145 return None;
146 }
147
148 acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
149 edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
150 edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
151 })
152}
153
154fn required_hashes(s: &str) -> usize {
155 let mut res = 0usize;
156 for idx in s.match_indices('"').map(|(i, _)| i) {
157 let (_, sub) = s.split_at(idx + 1);
158 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
159 res = res.max(n_hashes + 1)
160 }
161 res
162}
163
164#[test]
165fn test_required_hashes() {
166 assert_eq!(0, required_hashes("abc"));
167 assert_eq!(0, required_hashes("###"));
168 assert_eq!(1, required_hashes("\""));
169 assert_eq!(2, required_hashes("\"#abc"));
170 assert_eq!(0, required_hashes("#abc"));
171 assert_eq!(3, required_hashes("#ab\"##c"));
172 assert_eq!(5, required_hashes("#ab\"##\"####c"));
173}
174
175#[cfg(test)]
176mod test {
177 use test_utils::mark;
178
179 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
180
181 use super::*;
182
183 #[test]
184 fn make_raw_string_target() {
185 check_assist_target(
186 make_raw_string,
187 r#"
188 fn f() {
189 let s = <|>"random\nstring";
190 }
191 "#,
192 r#""random\nstring""#,
193 );
194 }
195
196 #[test]
197 fn make_raw_string_works() {
198 check_assist(
199 make_raw_string,
200 r#"
201fn f() {
202 let s = <|>"random\nstring";
203}
204"#,
205 r##"
206fn f() {
207 let s = r#"random
208string"#;
209}
210"##,
211 )
212 }
213
214 #[test]
215 fn make_raw_string_works_inside_macros() {
216 check_assist(
217 make_raw_string,
218 r#"
219 fn f() {
220 format!(<|>"x = {}", 92)
221 }
222 "#,
223 r##"
224 fn f() {
225 format!(r#"x = {}"#, 92)
226 }
227 "##,
228 )
229 }
230
231 #[test]
232 fn make_raw_string_hashes_inside_works() {
233 check_assist(
234 make_raw_string,
235 r###"
236fn f() {
237 let s = <|>"#random##\nstring";
238}
239"###,
240 r####"
241fn f() {
242 let s = r#"#random##
243string"#;
244}
245"####,
246 )
247 }
248
249 #[test]
250 fn make_raw_string_closing_hashes_inside_works() {
251 check_assist(
252 make_raw_string,
253 r###"
254fn f() {
255 let s = <|>"#random\"##\nstring";
256}
257"###,
258 r####"
259fn f() {
260 let s = r###"#random"##
261string"###;
262}
263"####,
264 )
265 }
266
267 #[test]
268 fn make_raw_string_nothing_to_unescape_works() {
269 check_assist(
270 make_raw_string,
271 r#"
272 fn f() {
273 let s = <|>"random string";
274 }
275 "#,
276 r##"
277 fn f() {
278 let s = r#"random string"#;
279 }
280 "##,
281 )
282 }
283
284 #[test]
285 fn make_raw_string_not_works_on_partial_string() {
286 check_assist_not_applicable(
287 make_raw_string,
288 r#"
289 fn f() {
290 let s = "foo<|>
291 }
292 "#,
293 )
294 }
295
296 #[test]
297 fn make_usual_string_not_works_on_partial_string() {
298 check_assist_not_applicable(
299 make_usual_string,
300 r#"
301 fn main() {
302 let s = r#"bar<|>
303 }
304 "#,
305 )
306 }
307
308 #[test]
309 fn add_hash_target() {
310 check_assist_target(
311 add_hash,
312 r#"
313 fn f() {
314 let s = <|>r"random string";
315 }
316 "#,
317 r#"r"random string""#,
318 );
319 }
320
321 #[test]
322 fn add_hash_works() {
323 check_assist(
324 add_hash,
325 r#"
326 fn f() {
327 let s = <|>r"random string";
328 }
329 "#,
330 r##"
331 fn f() {
332 let s = r#"random string"#;
333 }
334 "##,
335 )
336 }
337
338 #[test]
339 fn add_more_hash_works() {
340 check_assist(
341 add_hash,
342 r##"
343 fn f() {
344 let s = <|>r#"random"string"#;
345 }
346 "##,
347 r###"
348 fn f() {
349 let s = r##"random"string"##;
350 }
351 "###,
352 )
353 }
354
355 #[test]
356 fn add_hash_not_works() {
357 check_assist_not_applicable(
358 add_hash,
359 r#"
360 fn f() {
361 let s = <|>"random string";
362 }
363 "#,
364 );
365 }
366
367 #[test]
368 fn remove_hash_target() {
369 check_assist_target(
370 remove_hash,
371 r##"
372 fn f() {
373 let s = <|>r#"random string"#;
374 }
375 "##,
376 r##"r#"random string"#"##,
377 );
378 }
379
380 #[test]
381 fn remove_hash_works() {
382 check_assist(
383 remove_hash,
384 r##"fn f() { let s = <|>r#"random string"#; }"##,
385 r#"fn f() { let s = r"random string"; }"#,
386 )
387 }
388
389 #[test]
390 fn cant_remove_required_hash() {
391 mark::check!(cant_remove_required_hash);
392 check_assist_not_applicable(
393 remove_hash,
394 r##"
395 fn f() {
396 let s = <|>r#"random"str"ing"#;
397 }
398 "##,
399 )
400 }
401
402 #[test]
403 fn remove_more_hash_works() {
404 check_assist(
405 remove_hash,
406 r###"
407 fn f() {
408 let s = <|>r##"random string"##;
409 }
410 "###,
411 r##"
412 fn f() {
413 let s = r#"random string"#;
414 }
415 "##,
416 )
417 }
418
419 #[test]
420 fn remove_hash_doesnt_work() {
421 check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>"random string"; }"#);
422 }
423
424 #[test]
425 fn remove_hash_no_hash_doesnt_work() {
426 check_assist_not_applicable(remove_hash, r#"fn f() { let s = <|>r"random string"; }"#);
427 }
428
429 #[test]
430 fn make_usual_string_target() {
431 check_assist_target(
432 make_usual_string,
433 r##"
434 fn f() {
435 let s = <|>r#"random string"#;
436 }
437 "##,
438 r##"r#"random string"#"##,
439 );
440 }
441
442 #[test]
443 fn make_usual_string_works() {
444 check_assist(
445 make_usual_string,
446 r##"
447 fn f() {
448 let s = <|>r#"random string"#;
449 }
450 "##,
451 r#"
452 fn f() {
453 let s = "random string";
454 }
455 "#,
456 )
457 }
458
459 #[test]
460 fn make_usual_string_with_quote_works() {
461 check_assist(
462 make_usual_string,
463 r##"
464 fn f() {
465 let s = <|>r#"random"str"ing"#;
466 }
467 "##,
468 r#"
469 fn f() {
470 let s = "random\"str\"ing";
471 }
472 "#,
473 )
474 }
475
476 #[test]
477 fn make_usual_string_more_hash_works() {
478 check_assist(
479 make_usual_string,
480 r###"
481 fn f() {
482 let s = <|>r##"random string"##;
483 }
484 "###,
485 r##"
486 fn f() {
487 let s = "random string";
488 }
489 "##,
490 )
491 }
492
493 #[test]
494 fn make_usual_string_not_works() {
495 check_assist_not_applicable(
496 make_usual_string,
497 r#"
498 fn f() {
499 let s = <|>"random string";
500 }
501 "#,
502 );
503 }
504}
diff --git a/crates/ra_assists/src/handlers/remove_dbg.rs b/crates/ra_assists/src/handlers/remove_dbg.rs
deleted file mode 100644
index 9430ce1b5..000000000
--- a/crates/ra_assists/src/handlers/remove_dbg.rs
+++ /dev/null
@@ -1,205 +0,0 @@
1use ra_syntax::{
2 ast::{self, AstNode},
3 TextRange, TextSize, T,
4};
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: remove_dbg
9//
10// Removes `dbg!()` macro call.
11//
12// ```
13// fn main() {
14// <|>dbg!(92);
15// }
16// ```
17// ->
18// ```
19// fn main() {
20// 92;
21// }
22// ```
23pub(crate) fn remove_dbg(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let macro_call = ctx.find_node_at_offset::<ast::MacroCall>()?;
25
26 if !is_valid_macrocall(&macro_call, "dbg")? {
27 return None;
28 }
29
30 let is_leaf = macro_call.syntax().next_sibling().is_none();
31
32 let macro_end = if macro_call.semicolon_token().is_some() {
33 macro_call.syntax().text_range().end() - TextSize::of(';')
34 } else {
35 macro_call.syntax().text_range().end()
36 };
37
38 // macro_range determines what will be deleted and replaced with macro_content
39 let macro_range = TextRange::new(macro_call.syntax().text_range().start(), macro_end);
40 let paste_instead_of_dbg = {
41 let text = macro_call.token_tree()?.syntax().text();
42
43 // leafiness determines if we should include the parenthesis or not
44 let slice_index: TextRange = if is_leaf {
45 // leaf means - we can extract the contents of the dbg! in text
46 TextRange::new(TextSize::of('('), text.len() - TextSize::of(')'))
47 } else {
48 // not leaf - means we should keep the parens
49 TextRange::up_to(text.len())
50 };
51 text.slice(slice_index).to_string()
52 };
53
54 let target = macro_call.syntax().text_range();
55 acc.add(AssistId("remove_dbg", AssistKind::Refactor), "Remove dbg!()", target, |builder| {
56 builder.replace(macro_range, paste_instead_of_dbg);
57 })
58}
59
60/// Verifies that the given macro_call actually matches the given name
61/// and contains proper ending tokens
62fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<bool> {
63 let path = macro_call.path()?;
64 let name_ref = path.segment()?.name_ref()?;
65
66 // Make sure it is actually a dbg-macro call, dbg followed by !
67 let excl = path.syntax().next_sibling_or_token()?;
68
69 if name_ref.text() != macro_name || excl.kind() != T![!] {
70 return None;
71 }
72
73 let node = macro_call.token_tree()?.syntax().clone();
74 let first_child = node.first_child_or_token()?;
75 let last_child = node.last_child_or_token()?;
76
77 match (first_child.kind(), last_child.kind()) {
78 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => Some(true),
79 _ => Some(false),
80 }
81}
82
83#[cfg(test)]
84mod tests {
85 use super::*;
86 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
87
88 #[test]
89 fn test_remove_dbg() {
90 check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1");
91
92 check_assist(remove_dbg, "dbg!<|>((1 + 1))", "(1 + 1)");
93
94 check_assist(remove_dbg, "dbg!(1 <|>+ 1)", "1 + 1");
95
96 check_assist(remove_dbg, "let _ = <|>dbg!(1 + 1)", "let _ = 1 + 1");
97
98 check_assist(
99 remove_dbg,
100 "
101fn foo(n: usize) {
102 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
103 // ...
104 }
105}
106",
107 "
108fn foo(n: usize) {
109 if let Some(_) = n.checked_sub(4) {
110 // ...
111 }
112}
113",
114 );
115 }
116
117 #[test]
118 fn test_remove_dbg_with_brackets_and_braces() {
119 check_assist(remove_dbg, "dbg![<|>1 + 1]", "1 + 1");
120 check_assist(remove_dbg, "dbg!{<|>1 + 1}", "1 + 1");
121 }
122
123 #[test]
124 fn test_remove_dbg_not_applicable() {
125 check_assist_not_applicable(remove_dbg, "<|>vec![1, 2, 3]");
126 check_assist_not_applicable(remove_dbg, "<|>dbg(5, 6, 7)");
127 check_assist_not_applicable(remove_dbg, "<|>dbg!(5, 6, 7");
128 }
129
130 #[test]
131 fn test_remove_dbg_target() {
132 check_assist_target(
133 remove_dbg,
134 "
135fn foo(n: usize) {
136 if let Some(_) = dbg!(n.<|>checked_sub(4)) {
137 // ...
138 }
139}
140",
141 "dbg!(n.checked_sub(4))",
142 );
143 }
144
145 #[test]
146 fn test_remove_dbg_keep_semicolon() {
147 // https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779
148 // not quite though
149 // adding a comment at the end of the line makes
150 // the ast::MacroCall to include the semicolon at the end
151 check_assist(
152 remove_dbg,
153 r#"let res = <|>dbg!(1 * 20); // needless comment"#,
154 r#"let res = 1 * 20; // needless comment"#,
155 );
156 }
157
158 #[test]
159 fn test_remove_dbg_keep_expression() {
160 check_assist(
161 remove_dbg,
162 r#"let res = <|>dbg!(a + b).foo();"#,
163 r#"let res = (a + b).foo();"#,
164 );
165 }
166
167 #[test]
168 fn test_remove_dbg_from_inside_fn() {
169 check_assist_target(
170 remove_dbg,
171 r#"
172fn square(x: u32) -> u32 {
173 x * x
174}
175
176fn main() {
177 let x = square(dbg<|>!(5 + 10));
178 println!("{}", x);
179}"#,
180 "dbg!(5 + 10)",
181 );
182
183 check_assist(
184 remove_dbg,
185 r#"
186fn square(x: u32) -> u32 {
187 x * x
188}
189
190fn main() {
191 let x = square(dbg<|>!(5 + 10));
192 println!("{}", x);
193}"#,
194 r#"
195fn square(x: u32) -> u32 {
196 x * x
197}
198
199fn main() {
200 let x = square(5 + 10);
201 println!("{}", x);
202}"#,
203 );
204 }
205}
diff --git a/crates/ra_assists/src/handlers/remove_mut.rs b/crates/ra_assists/src/handlers/remove_mut.rs
deleted file mode 100644
index ef55c354e..000000000
--- a/crates/ra_assists/src/handlers/remove_mut.rs
+++ /dev/null
@@ -1,37 +0,0 @@
1use ra_syntax::{SyntaxKind, TextRange, T};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: remove_mut
6//
7// Removes the `mut` keyword.
8//
9// ```
10// impl Walrus {
11// fn feed(&mut<|> self, amount: u32) {}
12// }
13// ```
14// ->
15// ```
16// impl Walrus {
17// fn feed(&self, amount: u32) {}
18// }
19// ```
20pub(crate) fn remove_mut(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let mut_token = ctx.find_token_at_offset(T![mut])?;
22 let delete_from = mut_token.text_range().start();
23 let delete_to = match mut_token.next_token() {
24 Some(it) if it.kind() == SyntaxKind::WHITESPACE => it.text_range().end(),
25 _ => mut_token.text_range().end(),
26 };
27
28 let target = mut_token.text_range();
29 acc.add(
30 AssistId("remove_mut", AssistKind::Refactor),
31 "Remove `mut` keyword",
32 target,
33 |builder| {
34 builder.delete(TextRange::new(delete_from, delete_to));
35 },
36 )
37}
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs
deleted file mode 100644
index 120250e79..000000000
--- a/crates/ra_assists/src/handlers/reorder_fields.rs
+++ /dev/null
@@ -1,220 +0,0 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5use ra_ide_db::RootDatabase;
6use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7
8use crate::{AssistContext, AssistId, AssistKind, Assists};
9
10// Assist: reorder_fields
11//
12// Reorder the fields of record literals and record patterns in the same order as in
13// the definition.
14//
15// ```
16// struct Foo {foo: i32, bar: i32};
17// const test: Foo = <|>Foo {bar: 0, foo: 1}
18// ```
19// ->
20// ```
21// struct Foo {foo: i32, bar: i32};
22// const test: Foo = Foo {foo: 1, bar: 0}
23// ```
24//
25pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
27}
28
29fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
30 let record = ctx.find_node_at_offset::<R>()?;
31 let path = record.syntax().children().find_map(ast::Path::cast)?;
32
33 let ranks = compute_fields_ranks(&path, &ctx)?;
34
35 let fields = get_fields(&record.syntax());
36 let sorted_fields = sorted_by_rank(&fields, |node| {
37 *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
38 });
39
40 if sorted_fields == fields {
41 return None;
42 }
43
44 let target = record.syntax().text_range();
45 acc.add(
46 AssistId("reorder_fields", AssistKind::RefactorRewrite),
47 "Reorder record fields",
48 target,
49 |edit| {
50 for (old, new) in fields.iter().zip(&sorted_fields) {
51 algo::diff(old, new).into_text_edit(edit.text_edit_builder());
52 }
53 },
54 )
55}
56
57fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
58 match node.kind() {
59 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
60 RECORD_PAT => vec![RECORD_FIELD_PAT, BIND_PAT],
61 _ => vec![],
62 }
63}
64
65fn get_field_name(node: &SyntaxNode) -> String {
66 let res = match_ast! {
67 match node {
68 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
69 ast::RecordFieldPat(field) => field.field_name().map(|it| it.to_string()),
70 _ => None,
71 }
72 };
73 res.unwrap_or_default()
74}
75
76fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
77 let kinds = get_fields_kind(record);
78 record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
79}
80
81fn sorted_by_rank(
82 fields: &[SyntaxNode],
83 get_rank: impl Fn(&SyntaxNode) -> usize,
84) -> Vec<SyntaxNode> {
85 fields.iter().cloned().sorted_by_key(get_rank).collect()
86}
87
88fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
89 match sema.resolve_path(path) {
90 Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
91 _ => None,
92 }
93}
94
95fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
96 Some(
97 struct_definition(path, &ctx.sema)?
98 .fields(ctx.db())
99 .iter()
100 .enumerate()
101 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
102 .collect(),
103 )
104}
105
106#[cfg(test)]
107mod tests {
108 use crate::tests::{check_assist, check_assist_not_applicable};
109
110 use super::*;
111
112 #[test]
113 fn not_applicable_if_sorted() {
114 check_assist_not_applicable(
115 reorder_fields,
116 r#"
117 struct Foo {
118 foo: i32,
119 bar: i32,
120 }
121
122 const test: Foo = <|>Foo { foo: 0, bar: 0 };
123 "#,
124 )
125 }
126
127 #[test]
128 fn trivial_empty_fields() {
129 check_assist_not_applicable(
130 reorder_fields,
131 r#"
132 struct Foo {};
133 const test: Foo = <|>Foo {}
134 "#,
135 )
136 }
137
138 #[test]
139 fn reorder_struct_fields() {
140 check_assist(
141 reorder_fields,
142 r#"
143 struct Foo {foo: i32, bar: i32};
144 const test: Foo = <|>Foo {bar: 0, foo: 1}
145 "#,
146 r#"
147 struct Foo {foo: i32, bar: i32};
148 const test: Foo = Foo {foo: 1, bar: 0}
149 "#,
150 )
151 }
152
153 #[test]
154 fn reorder_struct_pattern() {
155 check_assist(
156 reorder_fields,
157 r#"
158 struct Foo { foo: i64, bar: i64, baz: i64 }
159
160 fn f(f: Foo) -> {
161 match f {
162 <|>Foo { baz: 0, ref mut bar, .. } => (),
163 _ => ()
164 }
165 }
166 "#,
167 r#"
168 struct Foo { foo: i64, bar: i64, baz: i64 }
169
170 fn f(f: Foo) -> {
171 match f {
172 Foo { ref mut bar, baz: 0, .. } => (),
173 _ => ()
174 }
175 }
176 "#,
177 )
178 }
179
180 #[test]
181 fn reorder_with_extra_field() {
182 check_assist(
183 reorder_fields,
184 r#"
185 struct Foo {
186 foo: String,
187 bar: String,
188 }
189
190 impl Foo {
191 fn new() -> Foo {
192 let foo = String::new();
193 <|>Foo {
194 bar: foo.clone(),
195 extra: "Extra field",
196 foo,
197 }
198 }
199 }
200 "#,
201 r#"
202 struct Foo {
203 foo: String,
204 bar: String,
205 }
206
207 impl Foo {
208 fn new() -> Foo {
209 let foo = String::new();
210 Foo {
211 foo,
212 bar: foo.clone(),
213 extra: "Extra field",
214 }
215 }
216 }
217 "#,
218 )
219 }
220}
diff --git a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs b/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
deleted file mode 100644
index b7e30a7f2..000000000
--- a/crates/ra_assists/src/handlers/replace_if_let_with_match.rs
+++ /dev/null
@@ -1,255 +0,0 @@
1use ra_fmt::unwrap_trivial_block;
2use ra_syntax::{
3 ast::{
4 self,
5 edit::{AstNodeEdit, IndentLevel},
6 make,
7 },
8 AstNode,
9};
10
11use crate::{utils::TryEnum, AssistContext, AssistId, AssistKind, Assists};
12
13// Assist: replace_if_let_with_match
14//
15// Replaces `if let` with an else branch with a `match` expression.
16//
17// ```
18// enum Action { Move { distance: u32 }, Stop }
19//
20// fn handle(action: Action) {
21// <|>if let Action::Move { distance } = action {
22// foo(distance)
23// } else {
24// bar()
25// }
26// }
27// ```
28// ->
29// ```
30// enum Action { Move { distance: u32 }, Stop }
31//
32// fn handle(action: Action) {
33// match action {
34// Action::Move { distance } => foo(distance),
35// _ => bar(),
36// }
37// }
38// ```
39pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
41 let cond = if_expr.condition()?;
42 let pat = cond.pat()?;
43 let expr = cond.expr()?;
44 let then_block = if_expr.then_branch()?;
45 let else_block = match if_expr.else_branch()? {
46 ast::ElseBranch::Block(it) => it,
47 ast::ElseBranch::IfExpr(_) => return None,
48 };
49
50 let target = if_expr.syntax().text_range();
51 acc.add(
52 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
53 "Replace with match",
54 target,
55 move |edit| {
56 let match_expr = {
57 let then_arm = {
58 let then_block = then_block.reset_indent().indent(IndentLevel(1));
59 let then_expr = unwrap_trivial_block(then_block);
60 make::match_arm(vec![pat.clone()], then_expr)
61 };
62 let else_arm = {
63 let pattern = ctx
64 .sema
65 .type_of_pat(&pat)
66 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
67 .map(|it| it.sad_pattern())
68 .unwrap_or_else(|| make::placeholder_pat().into());
69 let else_expr = unwrap_trivial_block(else_block);
70 make::match_arm(vec![pattern], else_expr)
71 };
72 let match_expr =
73 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
74 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
75 };
76
77 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
78 },
79 )
80}
81
82#[cfg(test)]
83mod tests {
84 use super::*;
85
86 use crate::tests::{check_assist, check_assist_target};
87
88 #[test]
89 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
90 check_assist(
91 replace_if_let_with_match,
92 r#"
93impl VariantData {
94 pub fn is_struct(&self) -> bool {
95 if <|>let VariantData::Struct(..) = *self {
96 true
97 } else {
98 false
99 }
100 }
101} "#,
102 r#"
103impl VariantData {
104 pub fn is_struct(&self) -> bool {
105 match *self {
106 VariantData::Struct(..) => true,
107 _ => false,
108 }
109 }
110} "#,
111 )
112 }
113
114 #[test]
115 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
116 check_assist(
117 replace_if_let_with_match,
118 r#"
119fn foo() {
120 if <|>let VariantData::Struct(..) = a {
121 bar(
122 123
123 )
124 } else {
125 false
126 }
127} "#,
128 r#"
129fn foo() {
130 match a {
131 VariantData::Struct(..) => {
132 bar(
133 123
134 )
135 }
136 _ => false,
137 }
138} "#,
139 )
140 }
141
142 #[test]
143 fn replace_if_let_with_match_target() {
144 check_assist_target(
145 replace_if_let_with_match,
146 r#"
147impl VariantData {
148 pub fn is_struct(&self) -> bool {
149 if <|>let VariantData::Struct(..) = *self {
150 true
151 } else {
152 false
153 }
154 }
155} "#,
156 "if let VariantData::Struct(..) = *self {
157 true
158 } else {
159 false
160 }",
161 );
162 }
163
164 #[test]
165 fn special_case_option() {
166 check_assist(
167 replace_if_let_with_match,
168 r#"
169enum Option<T> { Some(T), None }
170use Option::*;
171
172fn foo(x: Option<i32>) {
173 <|>if let Some(x) = x {
174 println!("{}", x)
175 } else {
176 println!("none")
177 }
178}
179 "#,
180 r#"
181enum Option<T> { Some(T), None }
182use Option::*;
183
184fn foo(x: Option<i32>) {
185 match x {
186 Some(x) => println!("{}", x),
187 None => println!("none"),
188 }
189}
190 "#,
191 );
192 }
193
194 #[test]
195 fn special_case_result() {
196 check_assist(
197 replace_if_let_with_match,
198 r#"
199enum Result<T, E> { Ok(T), Err(E) }
200use Result::*;
201
202fn foo(x: Result<i32, ()>) {
203 <|>if let Ok(x) = x {
204 println!("{}", x)
205 } else {
206 println!("none")
207 }
208}
209 "#,
210 r#"
211enum Result<T, E> { Ok(T), Err(E) }
212use Result::*;
213
214fn foo(x: Result<i32, ()>) {
215 match x {
216 Ok(x) => println!("{}", x),
217 Err(_) => println!("none"),
218 }
219}
220 "#,
221 );
222 }
223
224 #[test]
225 fn nested_indent() {
226 check_assist(
227 replace_if_let_with_match,
228 r#"
229fn main() {
230 if true {
231 <|>if let Ok(rel_path) = path.strip_prefix(root_path) {
232 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
233 Some((*id, rel_path))
234 } else {
235 None
236 }
237 }
238}
239"#,
240 r#"
241fn main() {
242 if true {