aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/Cargo.toml23
-rw-r--r--crates/assists/src/assist_config.rs16
-rw-r--r--crates/assists/src/assist_context.rs253
-rw-r--r--crates/assists/src/ast_transform.rs213
-rw-r--r--crates/assists/src/handlers/add_explicit_type.rs207
-rw-r--r--crates/assists/src/handlers/add_missing_impl_members.rs814
-rw-r--r--crates/assists/src/handlers/add_turbo_fish.rs164
-rw-r--r--crates/assists/src/handlers/apply_demorgan.rs93
-rw-r--r--crates/assists/src/handlers/auto_import.rs965
-rw-r--r--crates/assists/src/handlers/change_visibility.rs212
-rw-r--r--crates/assists/src/handlers/convert_integer_literal.rs268
-rw-r--r--crates/assists/src/handlers/early_return.rs515
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs907
-rw-r--r--crates/assists/src/handlers/extract_struct_from_enum_variant.rs520
-rw-r--r--crates/assists/src/handlers/extract_variable.rs588
-rw-r--r--crates/assists/src/handlers/fill_match_arms.rs746
-rw-r--r--crates/assists/src/handlers/fix_visibility.rs607
-rw-r--r--crates/assists/src/handlers/flip_binexpr.rs134
-rw-r--r--crates/assists/src/handlers/flip_comma.rs84
-rw-r--r--crates/assists/src/handlers/flip_trait_bound.rs121
-rw-r--r--crates/assists/src/handlers/generate_default_from_enum_variant.rs175
-rw-r--r--crates/assists/src/handlers/generate_derive.rs132
-rw-r--r--crates/assists/src/handlers/generate_from_impl_for_enum.rs201
-rw-r--r--crates/assists/src/handlers/generate_function.rs1059
-rw-r--r--crates/assists/src/handlers/generate_impl.rs148
-rw-r--r--crates/assists/src/handlers/generate_new.rs421
-rw-r--r--crates/assists/src/handlers/infer_function_return_type.rs345
-rw-r--r--crates/assists/src/handlers/inline_function.rs202
-rw-r--r--crates/assists/src/handlers/inline_local_variable.rs724
-rw-r--r--crates/assists/src/handlers/introduce_named_lifetime.rs315
-rw-r--r--crates/assists/src/handlers/invert_if.rs146
-rw-r--r--crates/assists/src/handlers/merge_imports.rs343
-rw-r--r--crates/assists/src/handlers/merge_match_arms.rs248
-rw-r--r--crates/assists/src/handlers/move_bounds.rs152
-rw-r--r--crates/assists/src/handlers/move_guard.rs367
-rw-r--r--crates/assists/src/handlers/move_module_to_file.rs145
-rw-r--r--crates/assists/src/handlers/pull_assignment_up.rs400
-rw-r--r--crates/assists/src/handlers/qualify_path.rs1212
-rw-r--r--crates/assists/src/handlers/raw_string.rs512
-rw-r--r--crates/assists/src/handlers/remove_dbg.rs421
-rw-r--r--crates/assists/src/handlers/remove_mut.rs37
-rw-r--r--crates/assists/src/handlers/remove_unused_param.rs288
-rw-r--r--crates/assists/src/handlers/reorder_fields.rs227
-rw-r--r--crates/assists/src/handlers/reorder_impl.rs201
-rw-r--r--crates/assists/src/handlers/replace_derive_with_manual_impl.rs401
-rw-r--r--crates/assists/src/handlers/replace_if_let_with_match.rs530
-rw-r--r--crates/assists/src/handlers/replace_impl_trait_with_generic.rs168
-rw-r--r--crates/assists/src/handlers/replace_let_with_if_let.rs101
-rw-r--r--crates/assists/src/handlers/replace_qualified_name_with_use.rs678
-rw-r--r--crates/assists/src/handlers/replace_string_with_char.rs137
-rw-r--r--crates/assists/src/handlers/replace_unwrap_with_match.rs188
-rw-r--r--crates/assists/src/handlers/split_import.rs79
-rw-r--r--crates/assists/src/handlers/toggle_ignore.rs98
-rw-r--r--crates/assists/src/handlers/unmerge_use.rs231
-rw-r--r--crates/assists/src/handlers/unwrap_block.rs582
-rw-r--r--crates/assists/src/handlers/wrap_return_type_in_result.rs1158
-rw-r--r--crates/assists/src/lib.rs230
-rw-r--r--crates/assists/src/tests.rs236
-rw-r--r--crates/assists/src/tests/generated.rs1184
-rw-r--r--crates/assists/src/utils.rs250
60 files changed, 0 insertions, 22122 deletions
diff --git a/crates/assists/Cargo.toml b/crates/assists/Cargo.toml
deleted file mode 100644
index ed8ad666f..000000000
--- a/crates/assists/Cargo.toml
+++ /dev/null
@@ -1,23 +0,0 @@
1[package]
2name = "assists"
3version = "0.0.0"
4description = "TBD"
5license = "MIT OR Apache-2.0"
6authors = ["rust-analyzer developers"]
7edition = "2018"
8
9[lib]
10doctest = false
11
12[dependencies]
13rustc-hash = "1.1.0"
14itertools = "0.10.0"
15either = "1.6.1"
16
17stdx = { path = "../stdx", version = "0.0.0" }
18syntax = { path = "../syntax", version = "0.0.0" }
19text_edit = { path = "../text_edit", version = "0.0.0" }
20profile = { path = "../profile", version = "0.0.0" }
21ide_db = { path = "../ide_db", version = "0.0.0" }
22hir = { path = "../hir", version = "0.0.0" }
23test_utils = { path = "../test_utils", version = "0.0.0" }
diff --git a/crates/assists/src/assist_config.rs b/crates/assists/src/assist_config.rs
deleted file mode 100644
index 9cabf037c..000000000
--- a/crates/assists/src/assist_config.rs
+++ /dev/null
@@ -1,16 +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 ide_db::helpers::{insert_use::InsertUseConfig, SnippetCap};
8
9use crate::AssistKind;
10
11#[derive(Clone, Debug, PartialEq, Eq)]
12pub struct AssistConfig {
13 pub snippet_cap: Option<SnippetCap>,
14 pub allowed: Option<Vec<AssistKind>>,
15 pub insert_use: InsertUseConfig,
16}
diff --git a/crates/assists/src/assist_context.rs b/crates/assists/src/assist_context.rs
deleted file mode 100644
index 8d93edba2..000000000
--- a/crates/assists/src/assist_context.rs
+++ /dev/null
@@ -1,253 +0,0 @@
1//! See `AssistContext`
2
3use std::mem;
4
5use hir::Semantics;
6use ide_db::{
7 base_db::{AnchoredPathBuf, FileId, FileRange},
8 helpers::SnippetCap,
9};
10use ide_db::{
11 label::Label,
12 source_change::{FileSystemEdit, SourceChange},
13 RootDatabase,
14};
15use syntax::{
16 algo::{self, find_node_at_offset, SyntaxRewriter},
17 AstNode, AstToken, SourceFile, SyntaxElement, SyntaxKind, SyntaxToken, TextRange, TextSize,
18 TokenAtOffset,
19};
20use text_edit::{TextEdit, TextEditBuilder};
21
22use crate::{assist_config::AssistConfig, Assist, AssistId, AssistKind, GroupLabel};
23
24/// `AssistContext` allows to apply an assist or check if it could be applied.
25///
26/// Assists use a somewhat over-engineered approach, given the current needs.
27/// The assists workflow consists of two phases. In the first phase, a user asks
28/// for the list of available assists. In the second phase, the user picks a
29/// particular assist and it gets applied.
30///
31/// There are two peculiarities here:
32///
33/// * first, we ideally avoid computing more things then necessary to answer "is
34/// assist applicable" in the first phase.
35/// * second, when we are applying assist, we don't have a guarantee that there
36/// weren't any changes between the point when user asked for assists and when
37/// they applied a particular assist. So, when applying assist, we need to do
38/// all the checks from scratch.
39///
40/// To avoid repeating the same code twice for both "check" and "apply"
41/// functions, we use an approach reminiscent of that of Django's function based
42/// views dealing with forms. Each assist receives a runtime parameter,
43/// `resolve`. It first check if an edit is applicable (potentially computing
44/// info required to compute the actual edit). If it is applicable, and
45/// `resolve` is `true`, it then computes the actual edit.
46///
47/// So, to implement the original assists workflow, we can first apply each edit
48/// with `resolve = false`, and then applying the selected edit again, with
49/// `resolve = true` this time.
50///
51/// Note, however, that we don't actually use such two-phase logic at the
52/// moment, because the LSP API is pretty awkward in this place, and it's much
53/// easier to just compute the edit eagerly :-)
54pub(crate) struct AssistContext<'a> {
55 pub(crate) config: &'a AssistConfig,
56 pub(crate) sema: Semantics<'a, RootDatabase>,
57 pub(crate) frange: FileRange,
58 source_file: SourceFile,
59}
60
61impl<'a> AssistContext<'a> {
62 pub(crate) fn new(
63 sema: Semantics<'a, RootDatabase>,
64 config: &'a AssistConfig,
65 frange: FileRange,
66 ) -> AssistContext<'a> {
67 let source_file = sema.parse(frange.file_id);
68 AssistContext { config, sema, frange, source_file }
69 }
70
71 pub(crate) fn db(&self) -> &RootDatabase {
72 self.sema.db
73 }
74
75 // NB, this ignores active selection.
76 pub(crate) fn offset(&self) -> TextSize {
77 self.frange.range.start()
78 }
79
80 pub(crate) fn token_at_offset(&self) -> TokenAtOffset<SyntaxToken> {
81 self.source_file.syntax().token_at_offset(self.offset())
82 }
83 pub(crate) fn find_token_syntax_at_offset(&self, kind: SyntaxKind) -> Option<SyntaxToken> {
84 self.token_at_offset().find(|it| it.kind() == kind)
85 }
86 pub(crate) fn find_token_at_offset<T: AstToken>(&self) -> Option<T> {
87 self.token_at_offset().find_map(T::cast)
88 }
89 pub(crate) fn find_node_at_offset<N: AstNode>(&self) -> Option<N> {
90 find_node_at_offset(self.source_file.syntax(), self.offset())
91 }
92 pub(crate) fn find_node_at_offset_with_descend<N: AstNode>(&self) -> Option<N> {
93 self.sema.find_node_at_offset_with_descend(self.source_file.syntax(), self.offset())
94 }
95 pub(crate) fn covering_element(&self) -> SyntaxElement {
96 self.source_file.syntax().covering_element(self.frange.range)
97 }
98 // FIXME: remove
99 pub(crate) fn covering_node_for_range(&self, range: TextRange) -> SyntaxElement {
100 self.source_file.syntax().covering_element(range)
101 }
102}
103
104pub(crate) struct Assists {
105 resolve: bool,
106 file: FileId,
107 buf: Vec<Assist>,
108 allowed: Option<Vec<AssistKind>>,
109}
110
111impl Assists {
112 pub(crate) fn new(ctx: &AssistContext, resolve: bool) -> Assists {
113 Assists {
114 resolve,
115 file: ctx.frange.file_id,
116 buf: Vec::new(),
117 allowed: ctx.config.allowed.clone(),
118 }
119 }
120
121 pub(crate) fn finish(mut self) -> Vec<Assist> {
122 self.buf.sort_by_key(|assist| assist.target.len());
123 self.buf
124 }
125
126 pub(crate) fn add(
127 &mut self,
128 id: AssistId,
129 label: impl Into<String>,
130 target: TextRange,
131 f: impl FnOnce(&mut AssistBuilder),
132 ) -> Option<()> {
133 if !self.is_allowed(&id) {
134 return None;
135 }
136 let label = Label::new(label.into());
137 let assist = Assist { id, label, group: None, target, source_change: None };
138 self.add_impl(assist, f)
139 }
140
141 pub(crate) fn add_group(
142 &mut self,
143 group: &GroupLabel,
144 id: AssistId,
145 label: impl Into<String>,
146 target: TextRange,
147 f: impl FnOnce(&mut AssistBuilder),
148 ) -> Option<()> {
149 if !self.is_allowed(&id) {
150 return None;
151 }
152 let label = Label::new(label.into());
153 let assist = Assist { id, label, group: Some(group.clone()), target, source_change: None };
154 self.add_impl(assist, f)
155 }
156
157 fn add_impl(&mut self, mut assist: Assist, f: impl FnOnce(&mut AssistBuilder)) -> Option<()> {
158 let source_change = if self.resolve {
159 let mut builder = AssistBuilder::new(self.file);
160 f(&mut builder);
161 Some(builder.finish())
162 } else {
163 None
164 };
165 assist.source_change = source_change.clone();
166
167 self.buf.push(assist);
168 Some(())
169 }
170
171 fn is_allowed(&self, id: &AssistId) -> bool {
172 match &self.allowed {
173 Some(allowed) => allowed.iter().any(|kind| kind.contains(id.1)),
174 None => true,
175 }
176 }
177}
178
179pub(crate) struct AssistBuilder {
180 edit: TextEditBuilder,
181 file_id: FileId,
182 source_change: SourceChange,
183}
184
185impl AssistBuilder {
186 pub(crate) fn new(file_id: FileId) -> AssistBuilder {
187 AssistBuilder { edit: TextEdit::builder(), file_id, source_change: SourceChange::default() }
188 }
189
190 pub(crate) fn edit_file(&mut self, file_id: FileId) {
191 self.commit();
192 self.file_id = file_id;
193 }
194
195 fn commit(&mut self) {
196 let edit = mem::take(&mut self.edit).finish();
197 if !edit.is_empty() {
198 self.source_change.insert_source_edit(self.file_id, edit);
199 }
200 }
201
202 /// Remove specified `range` of text.
203 pub(crate) fn delete(&mut self, range: TextRange) {
204 self.edit.delete(range)
205 }
206 /// Append specified `text` at the given `offset`
207 pub(crate) fn insert(&mut self, offset: TextSize, text: impl Into<String>) {
208 self.edit.insert(offset, text.into())
209 }
210 /// Append specified `snippet` at the given `offset`
211 pub(crate) fn insert_snippet(
212 &mut self,
213 _cap: SnippetCap,
214 offset: TextSize,
215 snippet: impl Into<String>,
216 ) {
217 self.source_change.is_snippet = true;
218 self.insert(offset, snippet);
219 }
220 /// Replaces specified `range` of text with a given string.
221 pub(crate) fn replace(&mut self, range: TextRange, replace_with: impl Into<String>) {
222 self.edit.replace(range, replace_with.into())
223 }
224 /// Replaces specified `range` of text with a given `snippet`.
225 pub(crate) fn replace_snippet(
226 &mut self,
227 _cap: SnippetCap,
228 range: TextRange,
229 snippet: impl Into<String>,
230 ) {
231 self.source_change.is_snippet = true;
232 self.replace(range, snippet);
233 }
234 pub(crate) fn replace_ast<N: AstNode>(&mut self, old: N, new: N) {
235 algo::diff(old.syntax(), new.syntax()).into_text_edit(&mut self.edit)
236 }
237 pub(crate) fn rewrite(&mut self, rewriter: SyntaxRewriter) {
238 if let Some(node) = rewriter.rewrite_root() {
239 let new = rewriter.rewrite(&node);
240 algo::diff(&node, &new).into_text_edit(&mut self.edit);
241 }
242 }
243 pub(crate) fn create_file(&mut self, dst: AnchoredPathBuf, content: impl Into<String>) {
244 let file_system_edit =
245 FileSystemEdit::CreateFile { dst: dst.clone(), initial_contents: content.into() };
246 self.source_change.push_file_system_edit(file_system_edit);
247 }
248
249 fn finish(mut self) -> SourceChange {
250 self.commit();
251 mem::take(&mut self.source_change)
252 }
253}
diff --git a/crates/assists/src/ast_transform.rs b/crates/assists/src/ast_transform.rs
deleted file mode 100644
index 4a3ed7783..000000000
--- a/crates/assists/src/ast_transform.rs
+++ /dev/null
@@ -1,213 +0,0 @@
1//! `AstTransformer`s are functions that replace nodes in an AST and can be easily combined.
2use hir::{HirDisplay, PathResolution, SemanticsScope};
3use ide_db::helpers::mod_path_to_ast;
4use rustc_hash::FxHashMap;
5use syntax::{
6 algo::SyntaxRewriter,
7 ast::{self, AstNode},
8 SyntaxNode,
9};
10
11pub fn apply<'a, N: AstNode>(transformer: &dyn AstTransform<'a>, node: N) -> N {
12 SyntaxRewriter::from_fn(|element| match element {
13 syntax::SyntaxElement::Node(n) => {
14 let replacement = transformer.get_substitution(&n, transformer)?;
15 Some(replacement.into())
16 }
17 _ => None,
18 })
19 .rewrite_ast(&node)
20}
21
22/// `AstTransform` helps with applying bulk transformations to syntax nodes.
23///
24/// This is mostly useful for IDE code generation. If you paste some existing
25/// code into a new context (for example, to add method overrides to an `impl`
26/// block), you generally want to appropriately qualify the names, and sometimes
27/// you might want to substitute generic parameters as well:
28///
29/// ```
30/// mod x {
31/// pub struct A;
32/// pub trait T<U> { fn foo(&self, _: U) -> A; }
33/// }
34///
35/// mod y {
36/// use x::T;
37///
38/// impl T<()> for () {
39/// // If we invoke **Add Missing Members** here, we want to copy-paste `foo`.
40/// // But we want a slightly-modified version of it:
41/// fn foo(&self, _: ()) -> x::A {}
42/// }
43/// }
44/// ```
45///
46/// So, a single `AstTransform` describes such function from `SyntaxNode` to
47/// `SyntaxNode`. Note that the API here is a bit too high-order and high-brow.
48/// We'd want to somehow express this concept simpler, but so far nobody got to
49/// simplifying this!
50pub trait AstTransform<'a> {
51 fn get_substitution(
52 &self,
53 node: &SyntaxNode,
54 recur: &dyn AstTransform<'a>,
55 ) -> Option<SyntaxNode>;
56
57 fn or<T: AstTransform<'a> + 'a>(self, other: T) -> Box<dyn AstTransform<'a> + 'a>
58 where
59 Self: Sized + 'a,
60 {
61 Box::new(Or(Box::new(self), Box::new(other)))
62 }
63}
64
65struct Or<'a>(Box<dyn AstTransform<'a> + 'a>, Box<dyn AstTransform<'a> + 'a>);
66
67impl<'a> AstTransform<'a> for Or<'a> {
68 fn get_substitution(
69 &self,
70 node: &SyntaxNode,
71 recur: &dyn AstTransform<'a>,
72 ) -> Option<SyntaxNode> {
73 self.0.get_substitution(node, recur).or_else(|| self.1.get_substitution(node, recur))
74 }
75}
76
77pub struct SubstituteTypeParams<'a> {
78 source_scope: &'a SemanticsScope<'a>,
79 substs: FxHashMap<hir::TypeParam, ast::Type>,
80}
81
82impl<'a> SubstituteTypeParams<'a> {
83 pub fn for_trait_impl(
84 source_scope: &'a SemanticsScope<'a>,
85 // FIXME: there's implicit invariant that `trait_` and `source_scope` match...
86 trait_: hir::Trait,
87 impl_def: ast::Impl,
88 ) -> SubstituteTypeParams<'a> {
89 let substs = get_syntactic_substs(impl_def).unwrap_or_default();
90 let generic_def: hir::GenericDef = trait_.into();
91 let substs_by_param: FxHashMap<_, _> = generic_def
92 .type_params(source_scope.db)
93 .into_iter()
94 // this is a trait impl, so we need to skip the first type parameter -- this is a bit hacky
95 .skip(1)
96 // The actual list of trait type parameters may be longer than the one
97 // used in the `impl` block due to trailing default type parameters.
98 // For that case we extend the `substs` with an empty iterator so we
99 // can still hit those trailing values and check if they actually have
100 // a default type. If they do, go for that type from `hir` to `ast` so
101 // the resulting change can be applied correctly.
102 .zip(substs.into_iter().map(Some).chain(std::iter::repeat(None)))
103 .filter_map(|(k, v)| match v {
104 Some(v) => Some((k, v)),
105 None => {
106 let default = k.default(source_scope.db)?;
107 Some((
108 k,
109 ast::make::ty(
110 &default
111 .display_source_code(source_scope.db, source_scope.module()?.into())
112 .ok()?,
113 ),
114 ))
115 }
116 })
117 .collect();
118 return SubstituteTypeParams { source_scope, substs: substs_by_param };
119
120 // FIXME: It would probably be nicer if we could get this via HIR (i.e. get the
121 // trait ref, and then go from the types in the substs back to the syntax).
122 fn get_syntactic_substs(impl_def: ast::Impl) -> Option<Vec<ast::Type>> {
123 let target_trait = impl_def.trait_()?;
124 let path_type = match target_trait {
125 ast::Type::PathType(path) => path,
126 _ => return None,
127 };
128 let generic_arg_list = path_type.path()?.segment()?.generic_arg_list()?;
129
130 let mut result = Vec::new();
131 for generic_arg in generic_arg_list.generic_args() {
132 match generic_arg {
133 ast::GenericArg::TypeArg(type_arg) => result.push(type_arg.ty()?),
134 ast::GenericArg::AssocTypeArg(_)
135 | ast::GenericArg::LifetimeArg(_)
136 | ast::GenericArg::ConstArg(_) => (),
137 }
138 }
139
140 Some(result)
141 }
142 }
143}
144
145impl<'a> AstTransform<'a> for SubstituteTypeParams<'a> {
146 fn get_substitution(
147 &self,
148 node: &SyntaxNode,
149 _recur: &dyn AstTransform<'a>,
150 ) -> Option<SyntaxNode> {
151 let type_ref = ast::Type::cast(node.clone())?;
152 let path = match &type_ref {
153 ast::Type::PathType(path_type) => path_type.path()?,
154 _ => return None,
155 };
156 let resolution = self.source_scope.speculative_resolve(&path)?;
157 match resolution {
158 hir::PathResolution::TypeParam(tp) => Some(self.substs.get(&tp)?.syntax().clone()),
159 _ => None,
160 }
161 }
162}
163
164pub struct QualifyPaths<'a> {
165 target_scope: &'a SemanticsScope<'a>,
166 source_scope: &'a SemanticsScope<'a>,
167}
168
169impl<'a> QualifyPaths<'a> {
170 pub fn new(target_scope: &'a SemanticsScope<'a>, source_scope: &'a SemanticsScope<'a>) -> Self {
171 Self { target_scope, source_scope }
172 }
173}
174
175impl<'a> AstTransform<'a> for QualifyPaths<'a> {
176 fn get_substitution(
177 &self,
178 node: &SyntaxNode,
179 recur: &dyn AstTransform<'a>,
180 ) -> Option<SyntaxNode> {
181 // FIXME handle value ns?
182 let from = self.target_scope.module()?;
183 let p = ast::Path::cast(node.clone())?;
184 if p.segment().and_then(|s| s.param_list()).is_some() {
185 // don't try to qualify `Fn(Foo) -> Bar` paths, they are in prelude anyway
186 return None;
187 }
188 let resolution = self.source_scope.speculative_resolve(&p)?;
189 match resolution {
190 PathResolution::Def(def) => {
191 let found_path = from.find_use_path(self.source_scope.db.upcast(), def)?;
192 let mut path = mod_path_to_ast(&found_path);
193
194 let type_args = p
195 .segment()
196 .and_then(|s| s.generic_arg_list())
197 .map(|arg_list| apply(recur, arg_list));
198 if let Some(type_args) = type_args {
199 let last_segment = path.segment().unwrap();
200 path = path.with_segment(last_segment.with_generic_args(type_args))
201 }
202
203 Some(path.syntax().clone())
204 }
205 PathResolution::Local(_)
206 | PathResolution::TypeParam(_)
207 | PathResolution::SelfType(_)
208 | PathResolution::ConstParam(_) => None,
209 PathResolution::Macro(_) => None,
210 PathResolution::AssocItem(_) => None,
211 }
212 }
213}
diff --git a/crates/assists/src/handlers/add_explicit_type.rs b/crates/assists/src/handlers/add_explicit_type.rs
deleted file mode 100644
index cb1548cef..000000000
--- a/crates/assists/src/handlers/add_explicit_type.rs
+++ /dev/null
@@ -1,207 +0,0 @@
1use hir::HirDisplay;
2use 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$0 = 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::IdentPat(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::InferType::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$0 = 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$0 = 1; }", "fn f() { let a: i32 = 1; }");
90 }
91
92 #[test]
93 fn add_explicit_type_works_for_underscore() {
94 check_assist(add_explicit_type, "fn f() { let a$0: _ = 1; }", "fn f() { let a: i32 = 1; }");
95 }
96
97 #[test]
98 fn add_explicit_type_works_for_nested_underscore() {
99 check_assist(
100 add_explicit_type,
101 r#"
102 enum Option<T> {
103 Some(T),
104 None
105 }
106
107 fn f() {
108 let a$0: Option<_> = Option::Some(1);
109 }"#,
110 r#"
111 enum Option<T> {
112 Some(T),
113 None
114 }
115
116 fn f() {
117 let a: Option<i32> = Option::Some(1);
118 }"#,
119 );
120 }
121
122 #[test]
123 fn add_explicit_type_works_for_macro_call() {
124 check_assist(
125 add_explicit_type,
126 r"macro_rules! v { () => {0u64} } fn f() { let a$0 = v!(); }",
127 r"macro_rules! v { () => {0u64} } fn f() { let a: u64 = v!(); }",
128 );
129 }
130
131 #[test]
132 fn add_explicit_type_works_for_macro_call_recursive() {
133 check_assist(
134 add_explicit_type,
135 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a$0 = v!(); }"#,
136 r#"macro_rules! u { () => {0u64} } macro_rules! v { () => {u!()} } fn f() { let a: u64 = v!(); }"#,
137 );
138 }
139
140 #[test]
141 fn add_explicit_type_not_applicable_if_ty_not_inferred() {
142 check_assist_not_applicable(add_explicit_type, "fn f() { let a$0 = None; }");
143 }
144
145 #[test]
146 fn add_explicit_type_not_applicable_if_ty_already_specified() {
147 check_assist_not_applicable(add_explicit_type, "fn f() { let a$0: i32 = 1; }");
148 }
149
150 #[test]
151 fn add_explicit_type_not_applicable_if_specified_ty_is_tuple() {
152 check_assist_not_applicable(add_explicit_type, "fn f() { let a$0: (i32, i32) = (3, 4); }");
153 }
154
155 #[test]
156 fn add_explicit_type_not_applicable_if_cursor_after_equals() {
157 check_assist_not_applicable(
158 add_explicit_type,
159 "fn f() {let a =$0 match 1 {2 => 3, 3 => 5};}",
160 )
161 }
162
163 #[test]
164 fn add_explicit_type_not_applicable_if_cursor_before_let() {
165 check_assist_not_applicable(
166 add_explicit_type,
167 "fn f() $0{let a = match 1 {2 => 3, 3 => 5};}",
168 )
169 }
170
171 #[test]
172 fn closure_parameters_are_not_added() {
173 check_assist_not_applicable(
174 add_explicit_type,
175 r#"
176fn main() {
177 let multiply_by_two$0 = |i| i * 3;
178 let six = multiply_by_two(2);
179}"#,
180 )
181 }
182
183 #[test]
184 fn default_generics_should_not_be_added() {
185 check_assist(
186 add_explicit_type,
187 r#"
188struct Test<K, T = u8> {
189 k: K,
190 t: T,
191}
192
193fn main() {
194 let test$0 = Test { t: 23u8, k: 33 };
195}"#,
196 r#"
197struct Test<K, T = u8> {
198 k: K,
199 t: T,
200}
201
202fn main() {
203 let test: Test<i32> = Test { t: 23u8, k: 33 };
204}"#,
205 );
206 }
207}
diff --git a/crates/assists/src/handlers/add_missing_impl_members.rs b/crates/assists/src/handlers/add_missing_impl_members.rs
deleted file mode 100644
index 63cea754d..000000000
--- a/crates/assists/src/handlers/add_missing_impl_members.rs
+++ /dev/null
@@ -1,814 +0,0 @@
1use ide_db::traits::resolve_target_trait;
2use syntax::ast::{self, AstNode};
3
4use crate::{
5 assist_context::{AssistContext, Assists},
6 utils::add_trait_assoc_items_to_impl,
7 utils::DefaultMethods,
8 utils::{filter_assoc_items, render_snippet, Cursor},
9 AssistId, AssistKind,
10};
11
12// Assist: add_impl_missing_members
13//
14// Adds scaffold for required impl members.
15//
16// ```
17// trait Trait<T> {
18// type X;
19// fn foo(&self) -> T;
20// fn bar(&self) {}
21// }
22//
23// impl Trait<u32> for () {$0
24//
25// }
26// ```
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// $0type X;
37//
38// fn foo(&self) -> u32 {
39// todo!()
40// }
41// }
42// ```
43pub(crate) fn add_missing_impl_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 add_missing_impl_members_inner(
45 acc,
46 ctx,
47 DefaultMethods::No,
48 "add_impl_missing_members",
49 "Implement missing members",
50 )
51}
52
53// Assist: add_impl_default_members
54//
55// Adds scaffold for overriding default impl members.
56//
57// ```
58// trait Trait {
59// type X;
60// fn foo(&self);
61// fn bar(&self) {}
62// }
63//
64// impl Trait for () {
65// type X = ();
66// fn foo(&self) {}$0
67//
68// }
69// ```
70// ->
71// ```
72// trait Trait {
73// type X;
74// fn foo(&self);
75// fn bar(&self) {}
76// }
77//
78// impl Trait for () {
79// type X = ();
80// fn foo(&self) {}
81//
82// $0fn bar(&self) {}
83// }
84// ```
85pub(crate) fn add_missing_default_members(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
86 add_missing_impl_members_inner(
87 acc,
88 ctx,
89 DefaultMethods::Only,
90 "add_impl_default_members",
91 "Implement default members",
92 )
93}
94
95fn add_missing_impl_members_inner(
96 acc: &mut Assists,
97 ctx: &AssistContext,
98 mode: DefaultMethods,
99 assist_id: &'static str,
100 label: &'static str,
101) -> Option<()> {
102 let _p = profile::span("add_missing_impl_members_inner");
103 let impl_def = ctx.find_node_at_offset::<ast::Impl>()?;
104 let trait_ = resolve_target_trait(&ctx.sema, &impl_def)?;
105
106 let missing_items = filter_assoc_items(
107 ctx.db(),
108 &ide_db::traits::get_missing_assoc_items(&ctx.sema, &impl_def),
109 mode,
110 );
111
112 if missing_items.is_empty() {
113 return None;
114 }
115
116 let target = impl_def.syntax().text_range();
117 acc.add(AssistId(assist_id, AssistKind::QuickFix), label, target, |builder| {
118 let target_scope = ctx.sema.scope(impl_def.syntax());
119 let (new_impl_def, first_new_item) =
120 add_trait_assoc_items_to_impl(&ctx.sema, missing_items, trait_, impl_def, target_scope);
121 match ctx.config.snippet_cap {
122 None => builder.replace(target, new_impl_def.to_string()),
123 Some(cap) => {
124 let mut cursor = Cursor::Before(first_new_item.syntax());
125 let placeholder;
126 if let ast::AssocItem::Fn(func) = &first_new_item {
127 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast) {
128 if m.syntax().text() == "todo!()" {
129 placeholder = m;
130 cursor = Cursor::Replace(placeholder.syntax());
131 }
132 }
133 }
134 builder.replace_snippet(
135 cap,
136 target,
137 render_snippet(cap, new_impl_def.syntax(), cursor),
138 )
139 }
140 };
141 })
142}
143
144#[cfg(test)]
145mod tests {
146 use crate::tests::{check_assist, check_assist_not_applicable};
147
148 use super::*;
149
150 #[test]
151 fn test_add_missing_impl_members() {
152 check_assist(
153 add_missing_impl_members,
154 r#"
155trait Foo {
156 type Output;
157
158 const CONST: usize = 42;
159
160 fn foo(&self);
161 fn bar(&self);
162 fn baz(&self);
163}
164
165struct S;
166
167impl Foo for S {
168 fn bar(&self) {}
169$0
170}"#,
171 r#"
172trait Foo {
173 type Output;
174
175 const CONST: usize = 42;
176
177 fn foo(&self);
178 fn bar(&self);
179 fn baz(&self);
180}
181
182struct S;
183
184impl Foo for S {
185 fn bar(&self) {}
186
187 $0type Output;
188
189 const CONST: usize = 42;
190
191 fn foo(&self) {
192 todo!()
193 }
194
195 fn baz(&self) {
196 todo!()
197 }
198}"#,
199 );
200 }
201
202 #[test]
203 fn test_copied_overriden_members() {
204 check_assist(
205 add_missing_impl_members,
206 r#"
207trait Foo {
208 fn foo(&self);
209 fn bar(&self) -> bool { true }
210 fn baz(&self) -> u32 { 42 }
211}
212
213struct S;
214
215impl Foo for S {
216 fn bar(&self) {}
217$0
218}"#,
219 r#"
220trait Foo {
221 fn foo(&self);
222 fn bar(&self) -> bool { true }
223 fn baz(&self) -> u32 { 42 }
224}
225
226struct S;
227
228impl Foo for S {
229 fn bar(&self) {}
230
231 fn foo(&self) {
232 ${0:todo!()}
233 }
234}"#,
235 );
236 }
237
238 #[test]
239 fn test_empty_impl_def() {
240 check_assist(
241 add_missing_impl_members,
242 r#"
243trait Foo { fn foo(&self); }
244struct S;
245impl Foo for S { $0 }"#,
246 r#"
247trait Foo { fn foo(&self); }
248struct S;
249impl Foo for S {
250 fn foo(&self) {
251 ${0:todo!()}
252 }
253}"#,
254 );
255 }
256
257 #[test]
258 fn test_impl_def_without_braces() {
259 check_assist(
260 add_missing_impl_members,
261 r#"
262trait Foo { fn foo(&self); }
263struct S;
264impl Foo for S$0"#,
265 r#"
266trait Foo { fn foo(&self); }
267struct S;
268impl Foo for S {
269 fn foo(&self) {
270 ${0:todo!()}
271 }
272}"#,
273 );
274 }
275
276 #[test]
277 fn fill_in_type_params_1() {
278 check_assist(
279 add_missing_impl_members,
280 r#"
281trait Foo<T> { fn foo(&self, t: T) -> &T; }
282struct S;
283impl Foo<u32> for S { $0 }"#,
284 r#"
285trait Foo<T> { fn foo(&self, t: T) -> &T; }
286struct S;
287impl Foo<u32> for S {
288 fn foo(&self, t: u32) -> &u32 {
289 ${0:todo!()}
290 }
291}"#,
292 );
293 }
294
295 #[test]
296 fn fill_in_type_params_2() {
297 check_assist(
298 add_missing_impl_members,
299 r#"
300trait Foo<T> { fn foo(&self, t: T) -> &T; }
301struct S;
302impl<U> Foo<U> for S { $0 }"#,
303 r#"
304trait Foo<T> { fn foo(&self, t: T) -> &T; }
305struct S;
306impl<U> Foo<U> for S {
307 fn foo(&self, t: U) -> &U {
308 ${0:todo!()}
309 }
310}"#,
311 );
312 }
313
314 #[test]
315 fn test_cursor_after_empty_impl_def() {
316 check_assist(
317 add_missing_impl_members,
318 r#"
319trait Foo { fn foo(&self); }
320struct S;
321impl Foo for S {}$0"#,
322 r#"
323trait Foo { fn foo(&self); }
324struct S;
325impl Foo for S {
326 fn foo(&self) {
327 ${0:todo!()}
328 }
329}"#,
330 )
331 }
332
333 #[test]
334 fn test_qualify_path_1() {
335 check_assist(
336 add_missing_impl_members,
337 r#"
338mod foo {
339 pub struct Bar;
340 trait Foo { fn foo(&self, bar: Bar); }
341}
342struct S;
343impl foo::Foo for S { $0 }"#,
344 r#"
345mod foo {
346 pub struct Bar;
347 trait Foo { fn foo(&self, bar: Bar); }
348}
349struct S;
350impl foo::Foo for S {
351 fn foo(&self, bar: foo::Bar) {
352 ${0:todo!()}
353 }
354}"#,
355 );
356 }
357
358 #[test]
359 fn test_qualify_path_2() {
360 check_assist(
361 add_missing_impl_members,
362 r#"
363mod foo {
364 pub mod bar {
365 pub struct Bar;
366 pub trait Foo { fn foo(&self, bar: Bar); }
367 }
368}
369
370use foo::bar;
371
372struct S;
373impl bar::Foo for S { $0 }"#,
374 r#"
375mod foo {
376 pub mod bar {
377 pub struct Bar;
378 pub trait Foo { fn foo(&self, bar: Bar); }
379 }
380}
381
382use foo::bar;
383
384struct S;
385impl bar::Foo for S {
386 fn foo(&self, bar: bar::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 { $0 }"#,
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 { $0 }"#,
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 { $0 }"#,
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 { $0 }"#,
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 { $0 }"#,
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 { $0 }"#,
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 { $0 }"#,
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 { $0 }"#,
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 {}$0"#,
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
603 fn foo(&self) {
604 todo!()
605 }
606}"#,
607 )
608 }
609
610 #[test]
611 fn test_default_methods() {
612 check_assist(
613 add_missing_default_members,
614 r#"
615trait Foo {
616 type Output;
617
618 const CONST: usize = 42;
619
620 fn valid(some: u32) -> bool { false }
621 fn foo(some: u32) -> bool;
622}
623struct S;
624impl Foo for S { $0 }"#,
625 r#"
626trait Foo {
627 type Output;
628
629 const CONST: usize = 42;
630
631 fn valid(some: u32) -> bool { false }
632 fn foo(some: u32) -> bool;
633}
634struct S;
635impl Foo for S {
636 $0fn valid(some: u32) -> bool { false }
637}"#,
638 )
639 }
640
641 #[test]
642 fn test_generic_single_default_parameter() {
643 check_assist(
644 add_missing_impl_members,
645 r#"
646trait Foo<T = Self> {
647 fn bar(&self, other: &T);
648}
649
650struct S;
651impl Foo for S { $0 }"#,
652 r#"
653trait Foo<T = Self> {
654 fn bar(&self, other: &T);
655}
656
657struct S;
658impl Foo for S {
659 fn bar(&self, other: &Self) {
660 ${0:todo!()}
661 }
662}"#,
663 )
664 }
665
666 #[test]
667 fn test_generic_default_parameter_is_second() {
668 check_assist(
669 add_missing_impl_members,
670 r#"
671trait Foo<T1, T2 = Self> {
672 fn bar(&self, this: &T1, that: &T2);
673}
674
675struct S<T>;
676impl Foo<T> for S<T> { $0 }"#,
677 r#"
678trait Foo<T1, T2 = Self> {
679 fn bar(&self, this: &T1, that: &T2);
680}
681
682struct S<T>;
683impl Foo<T> for S<T> {
684 fn bar(&self, this: &T, that: &Self) {
685 ${0:todo!()}
686 }
687}"#,
688 )
689 }
690
691 #[test]
692 fn test_assoc_type_bounds_are_removed() {
693 check_assist(
694 add_missing_impl_members,
695 r#"
696trait Tr {
697 type Ty: Copy + 'static;
698}
699
700impl Tr for ()$0 {
701}"#,
702 r#"
703trait Tr {
704 type Ty: Copy + 'static;
705}
706
707impl Tr for () {
708 $0type Ty;
709}"#,
710 )
711 }
712
713 #[test]
714 fn test_whitespace_fixup_preserves_bad_tokens() {
715 check_assist(
716 add_missing_impl_members,
717 r#"
718trait Tr {
719 fn foo();
720}
721
722impl Tr for ()$0 {
723 +++
724}"#,
725 r#"
726trait Tr {
727 fn foo();
728}
729
730impl Tr for () {
731 fn foo() {
732 ${0:todo!()}
733 }
734 +++
735}"#,
736 )
737 }
738
739 #[test]
740 fn test_whitespace_fixup_preserves_comments() {
741 check_assist(
742 add_missing_impl_members,
743 r#"
744trait Tr {
745 fn foo();
746}
747
748impl Tr for ()$0 {
749 // very important
750}"#,
751 r#"
752trait Tr {
753 fn foo();
754}
755
756impl Tr for () {
757 fn foo() {
758 ${0:todo!()}
759 }
760 // very important
761}"#,
762 )
763 }
764
765 #[test]
766 fn weird_path() {
767 check_assist(
768 add_missing_impl_members,
769 r#"
770trait Test {
771 fn foo(&self, x: crate)
772}
773impl Test for () {
774 $0
775}
776"#,
777 r#"
778trait Test {
779 fn foo(&self, x: crate)
780}
781impl Test for () {
782 fn foo(&self, x: crate) {
783 ${0:todo!()}
784 }
785}
786"#,
787 )
788 }
789
790 #[test]
791 fn missing_generic_type() {
792 check_assist(
793 add_missing_impl_members,
794 r#"
795trait Foo<BAR> {
796 fn foo(&self, bar: BAR);
797}
798impl Foo for () {
799 $0
800}
801"#,
802 r#"
803trait Foo<BAR> {
804 fn foo(&self, bar: BAR);
805}
806impl Foo for () {
807 fn foo(&self, bar: BAR) {
808 ${0:todo!()}
809 }
810}
811"#,
812 )
813 }
814}
diff --git a/crates/assists/src/handlers/add_turbo_fish.rs b/crates/assists/src/handlers/add_turbo_fish.rs
deleted file mode 100644
index 8e9ea4fad..000000000
--- a/crates/assists/src/handlers/add_turbo_fish.rs
+++ /dev/null
@@ -1,164 +0,0 @@
1use ide_db::defs::{Definition, NameRefClass};
2use 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$0();
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_syntax_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 NameRefClass::classify(&ctx.sema, &name_ref)? {
43 NameRefClass::Definition(def) => def,
44 NameRefClass::ExternCrate(_) | 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$0();
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()$0;
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$0();
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$0::<()>();
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$0();
160}
161"#,
162 );
163 }
164}
diff --git a/crates/assists/src/handlers/apply_demorgan.rs b/crates/assists/src/handlers/apply_demorgan.rs
deleted file mode 100644
index ed4d11455..000000000
--- a/crates/assists/src/handlers/apply_demorgan.rs
+++ /dev/null
@@ -1,93 +0,0 @@
1use syntax::ast::{self, AstNode};
2
3use crate::{utils::invert_boolean_expression, AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: apply_demorgan
6//
7// Apply https://en.wikipedia.org/wiki/De_Morgan%27s_laws[De Morgan's law].
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 ||$0 !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 &&$0 !x }", "fn f() { !(x || x) }")
72 }
73
74 #[test]
75 fn demorgan_turns_or_into_and() {
76 check_assist(apply_demorgan, "fn f() { !x ||$0 !x }", "fn f() { !(x && x) }")
77 }
78
79 #[test]
80 fn demorgan_removes_inequality() {
81 check_assist(apply_demorgan, "fn f() { x != x ||$0 !x }", "fn f() { !(x == x && x) }")
82 }
83
84 #[test]
85 fn demorgan_general_case() {
86 check_assist(apply_demorgan, "fn f() { x ||$0 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() { $0 !x || !x }")
92 }
93}
diff --git a/crates/assists/src/handlers/auto_import.rs b/crates/assists/src/handlers/auto_import.rs
deleted file mode 100644
index 4e2a4fcd9..000000000
--- a/crates/assists/src/handlers/auto_import.rs
+++ /dev/null
@@ -1,965 +0,0 @@
1use ide_db::helpers::{
2 import_assets::{ImportAssets, ImportCandidate},
3 insert_use::{insert_use, ImportScope},
4 mod_path_to_ast,
5};
6use syntax::ast;
7
8use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
9
10// Feature: Auto Import
11//
12// Using the `auto-import` assist it is possible to insert missing imports for unresolved items.
13// When inserting an import it will do so in a structured manner by keeping imports grouped,
14// separated by a newline in the following order:
15//
16// - `std` and `core`
17// - External Crates
18// - Current Crate, paths prefixed by `crate`
19// - Current Module, paths prefixed by `self`
20// - Super Module, paths prefixed by `super`
21//
22// Example:
23// ```rust
24// use std::fs::File;
25//
26// use itertools::Itertools;
27// use syntax::ast;
28//
29// use crate::utils::insert_use;
30//
31// use self::auto_import;
32//
33// use super::AssistContext;
34// ```
35//
36// .Merge Behaviour
37//
38// It is possible to configure how use-trees are merged with the `importMergeBehaviour` setting.
39// It has the following configurations:
40//
41// - `full`: This setting will cause auto-import to always completely merge use-trees that share the
42// same path prefix while also merging inner trees that share the same path-prefix. This kind of
43// nesting is only supported in Rust versions later than 1.24.
44// - `last`: This setting will cause auto-import to merge use-trees as long as the resulting tree
45// will only contain a nesting of single segment paths at the very end.
46// - `none`: This setting will cause auto-import to never merge use-trees keeping them as simple
47// paths.
48//
49// In `VS Code` the configuration for this is `rust-analyzer.assist.importMergeBehaviour`.
50//
51// .Import Prefix
52//
53// The style of imports in the same crate is configurable through the `importPrefix` setting.
54// It has the following configurations:
55//
56// - `by_crate`: This setting will force paths to be always absolute, starting with the `crate`
57// prefix, unless the item is defined outside of the current crate.
58// - `by_self`: This setting will force paths that are relative to the current module to always
59// start with `self`. This will result in paths that always start with either `crate`, `self`,
60// `super` or an extern crate identifier.
61// - `plain`: This setting does not impose any restrictions in imports.
62//
63// In `VS Code` the configuration for this is `rust-analyzer.assist.importPrefix`.
64
65// Assist: auto_import
66//
67// If the name is unresolved, provides all possible imports for it.
68//
69// ```
70// fn main() {
71// let map = HashMap$0::new();
72// }
73// # pub mod std { pub mod collections { pub struct HashMap { } } }
74// ```
75// ->
76// ```
77// use std::collections::HashMap;
78//
79// fn main() {
80// let map = HashMap::new();
81// }
82// # pub mod std { pub mod collections { pub struct HashMap { } } }
83// ```
84pub(crate) fn auto_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
85 let import_assets =
86 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
87 ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
88 } else if let Some(method_under_caret) =
89 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
90 {
91 ImportAssets::for_method_call(method_under_caret, &ctx.sema)
92 } else {
93 None
94 }?;
95 let proposed_imports = import_assets.search_for_imports(&ctx.sema, &ctx.config.insert_use);
96 if proposed_imports.is_empty() {
97 return None;
98 }
99
100 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
101 let group = import_group_message(import_assets.import_candidate());
102 let scope =
103 ImportScope::find_insert_use_container(import_assets.syntax_under_caret(), &ctx.sema)?;
104 for (import, _) in proposed_imports {
105 acc.add_group(
106 &group,
107 AssistId("auto_import", AssistKind::QuickFix),
108 format!("Import `{}`", &import),
109 range,
110 |builder| {
111 let rewriter =
112 insert_use(&scope, mod_path_to_ast(&import), ctx.config.insert_use.merge);
113 builder.rewrite(rewriter);
114 },
115 );
116 }
117 Some(())
118}
119
120fn import_group_message(import_candidate: &ImportCandidate) -> GroupLabel {
121 let name = match import_candidate {
122 ImportCandidate::Path(candidate) => format!("Import {}", &candidate.name),
123 ImportCandidate::TraitAssocItem(candidate) => {
124 format!("Import a trait for item {}", &candidate.name)
125 }
126 ImportCandidate::TraitMethod(candidate) => {
127 format!("Import a trait for method {}", &candidate.name)
128 }
129 };
130 GroupLabel(name)
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
137
138 #[test]
139 fn applicable_when_found_an_import_partial() {
140 check_assist(
141 auto_import,
142 r"
143 mod std {
144 pub mod fmt {
145 pub struct Formatter;
146 }
147 }
148
149 use std::fmt;
150
151 $0Formatter
152 ",
153 r"
154 mod std {
155 pub mod fmt {
156 pub struct Formatter;
157 }
158 }
159
160 use std::fmt::{self, Formatter};
161
162 Formatter
163 ",
164 );
165 }
166
167 #[test]
168 fn applicable_when_found_an_import() {
169 check_assist(
170 auto_import,
171 r"
172 $0PubStruct
173
174 pub mod PubMod {
175 pub struct PubStruct;
176 }
177 ",
178 r"
179 use PubMod::PubStruct;
180
181 PubStruct
182
183 pub mod PubMod {
184 pub struct PubStruct;
185 }
186 ",
187 );
188 }
189
190 #[test]
191 fn applicable_when_found_an_import_in_macros() {
192 check_assist(
193 auto_import,
194 r"
195 macro_rules! foo {
196 ($i:ident) => { fn foo(a: $i) {} }
197 }
198 foo!(Pub$0Struct);
199
200 pub mod PubMod {
201 pub struct PubStruct;
202 }
203 ",
204 r"
205 use PubMod::PubStruct;
206
207 macro_rules! foo {
208 ($i:ident) => { fn foo(a: $i) {} }
209 }
210 foo!(PubStruct);
211
212 pub mod PubMod {
213 pub struct PubStruct;
214 }
215 ",
216 );
217 }
218
219 #[test]
220 fn auto_imports_are_merged() {
221 check_assist(
222 auto_import,
223 r"
224 use PubMod::PubStruct1;
225
226 struct Test {
227 test: Pub$0Struct2<u8>,
228 }
229
230 pub mod PubMod {
231 pub struct PubStruct1;
232 pub struct PubStruct2<T> {
233 _t: T,
234 }
235 }
236 ",
237 r"
238 use PubMod::{PubStruct1, PubStruct2};
239
240 struct Test {
241 test: PubStruct2<u8>,
242 }
243
244 pub mod PubMod {
245 pub struct PubStruct1;
246 pub struct PubStruct2<T> {
247 _t: T,
248 }
249 }
250 ",
251 );
252 }
253
254 #[test]
255 fn applicable_when_found_multiple_imports() {
256 check_assist(
257 auto_import,
258 r"
259 PubSt$0ruct
260
261 pub mod PubMod1 {
262 pub struct PubStruct;
263 }
264 pub mod PubMod2 {
265 pub struct PubStruct;
266 }
267 pub mod PubMod3 {
268 pub struct PubStruct;
269 }
270 ",
271 r"
272 use PubMod3::PubStruct;
273
274 PubStruct
275
276 pub mod PubMod1 {
277 pub struct PubStruct;
278 }
279 pub mod PubMod2 {
280 pub struct PubStruct;
281 }
282 pub mod PubMod3 {
283 pub struct PubStruct;
284 }
285 ",
286 );
287 }
288
289 #[test]
290 fn not_applicable_for_already_imported_types() {
291 check_assist_not_applicable(
292 auto_import,
293 r"
294 use PubMod::PubStruct;
295
296 PubStruct$0
297
298 pub mod PubMod {
299 pub struct PubStruct;
300 }
301 ",
302 );
303 }
304
305 #[test]
306 fn not_applicable_for_types_with_private_paths() {
307 check_assist_not_applicable(
308 auto_import,
309 r"
310 PrivateStruct$0
311
312 pub mod PubMod {
313 struct PrivateStruct;
314 }
315 ",
316 );
317 }
318
319 #[test]
320 fn not_applicable_when_no_imports_found() {
321 check_assist_not_applicable(
322 auto_import,
323 "
324 PubStruct$0",
325 );
326 }
327
328 #[test]
329 fn not_applicable_in_import_statements() {
330 check_assist_not_applicable(
331 auto_import,
332 r"
333 use PubStruct$0;
334
335 pub mod PubMod {
336 pub struct PubStruct;
337 }",
338 );
339 }
340
341 #[test]
342 fn function_import() {
343 check_assist(
344 auto_import,
345 r"
346 test_function$0
347
348 pub mod PubMod {
349 pub fn test_function() {};
350 }
351 ",
352 r"
353 use PubMod::test_function;
354
355 test_function
356
357 pub mod PubMod {
358 pub fn test_function() {};
359 }
360 ",
361 );
362 }
363
364 #[test]
365 fn macro_import() {
366 check_assist(
367 auto_import,
368 r"
369//- /lib.rs crate:crate_with_macro
370#[macro_export]
371macro_rules! foo {
372 () => ()
373}
374
375//- /main.rs crate:main deps:crate_with_macro
376fn main() {
377 foo$0
378}
379",
380 r"use crate_with_macro::foo;
381
382fn main() {
383 foo
384}
385",
386 );
387 }
388
389 #[test]
390 fn auto_import_target() {
391 check_assist_target(
392 auto_import,
393 r"
394 struct AssistInfo {
395 group_label: Option<$0GroupLabel>,
396 }
397
398 mod m { pub struct GroupLabel; }
399 ",
400 "GroupLabel",
401 )
402 }
403
404 #[test]
405 fn not_applicable_when_path_start_is_imported() {
406 check_assist_not_applicable(
407 auto_import,
408 r"
409 pub mod mod1 {
410 pub mod mod2 {
411 pub mod mod3 {
412 pub struct TestStruct;
413 }
414 }
415 }
416
417 use mod1::mod2;
418 fn main() {
419 mod2::mod3::TestStruct$0
420 }
421 ",
422 );
423 }
424
425 #[test]
426 fn not_applicable_for_imported_function() {
427 check_assist_not_applicable(
428 auto_import,
429 r"
430 pub mod test_mod {
431 pub fn test_function() {}
432 }
433
434 use test_mod::test_function;
435 fn main() {
436 test_function$0
437 }
438 ",
439 );
440 }
441
442 #[test]
443 fn associated_struct_function() {
444 check_assist(
445 auto_import,
446 r"
447 mod test_mod {
448 pub struct TestStruct {}
449 impl TestStruct {
450 pub fn test_function() {}
451 }
452 }
453
454 fn main() {
455 TestStruct::test_function$0
456 }
457 ",
458 r"
459 use test_mod::TestStruct;
460
461 mod test_mod {
462 pub struct TestStruct {}
463 impl TestStruct {
464 pub fn test_function() {}
465 }
466 }
467
468 fn main() {
469 TestStruct::test_function
470 }
471 ",
472 );
473 }
474
475 #[test]
476 fn associated_struct_const() {
477 check_assist(
478 auto_import,
479 r"
480 mod test_mod {
481 pub struct TestStruct {}
482 impl TestStruct {
483 const TEST_CONST: u8 = 42;
484 }
485 }
486
487 fn main() {
488 TestStruct::TEST_CONST$0
489 }
490 ",
491 r"
492 use test_mod::TestStruct;
493
494 mod test_mod {
495 pub struct TestStruct {}
496 impl TestStruct {
497 const TEST_CONST: u8 = 42;
498 }
499 }
500
501 fn main() {
502 TestStruct::TEST_CONST
503 }
504 ",
505 );
506 }
507
508 #[test]
509 fn associated_trait_function() {
510 check_assist(
511 auto_import,
512 r"
513 mod test_mod {
514 pub trait TestTrait {
515 fn test_function();
516 }
517 pub struct TestStruct {}
518 impl TestTrait for TestStruct {
519 fn test_function() {}
520 }
521 }
522
523 fn main() {
524 test_mod::TestStruct::test_function$0
525 }
526 ",
527 r"
528 use test_mod::TestTrait;
529
530 mod test_mod {
531 pub trait TestTrait {
532 fn test_function();
533 }
534 pub struct TestStruct {}
535 impl TestTrait for TestStruct {
536 fn test_function() {}
537 }
538 }
539
540 fn main() {
541 test_mod::TestStruct::test_function
542 }
543 ",
544 );
545 }
546
547 #[test]
548 fn not_applicable_for_imported_trait_for_function() {
549 check_assist_not_applicable(
550 auto_import,
551 r"
552 mod test_mod {
553 pub trait TestTrait {
554 fn test_function();
555 }
556 pub trait TestTrait2 {
557 fn test_function();
558 }
559 pub enum TestEnum {
560 One,
561 Two,
562 }
563 impl TestTrait2 for TestEnum {
564 fn test_function() {}
565 }
566 impl TestTrait for TestEnum {
567 fn test_function() {}
568 }
569 }
570
571 use test_mod::TestTrait2;
572 fn main() {
573 test_mod::TestEnum::test_function$0;
574 }
575 ",
576 )
577 }
578
579 #[test]
580 fn associated_trait_const() {
581 check_assist(
582 auto_import,
583 r"
584 mod test_mod {
585 pub trait TestTrait {
586 const TEST_CONST: u8;
587 }
588 pub struct TestStruct {}
589 impl TestTrait for TestStruct {
590 const TEST_CONST: u8 = 42;
591 }
592 }
593
594 fn main() {
595 test_mod::TestStruct::TEST_CONST$0
596 }
597 ",
598 r"
599 use test_mod::TestTrait;
600
601 mod test_mod {
602 pub trait TestTrait {
603 const TEST_CONST: u8;
604 }
605 pub struct TestStruct {}
606 impl TestTrait for TestStruct {
607 const TEST_CONST: u8 = 42;
608 }
609 }
610
611 fn main() {
612 test_mod::TestStruct::TEST_CONST
613 }
614 ",
615 );
616 }
617
618 #[test]
619 fn not_applicable_for_imported_trait_for_const() {
620 check_assist_not_applicable(
621 auto_import,
622 r"
623 mod test_mod {
624 pub trait TestTrait {
625 const TEST_CONST: u8;
626 }
627 pub trait TestTrait2 {
628 const TEST_CONST: f64;
629 }
630 pub enum TestEnum {
631 One,
632 Two,
633 }
634 impl TestTrait2 for TestEnum {
635 const TEST_CONST: f64 = 42.0;
636 }
637 impl TestTrait for TestEnum {
638 const TEST_CONST: u8 = 42;
639 }
640 }
641
642 use test_mod::TestTrait2;
643 fn main() {
644 test_mod::TestEnum::TEST_CONST$0;
645 }
646 ",
647 )
648 }
649
650 #[test]
651 fn trait_method() {
652 check_assist(
653 auto_import,
654 r"
655 mod test_mod {
656 pub trait TestTrait {
657 fn test_method(&self);
658 }
659 pub struct TestStruct {}
660 impl TestTrait for TestStruct {
661 fn test_method(&self) {}
662 }
663 }
664
665 fn main() {
666 let test_struct = test_mod::TestStruct {};
667 test_struct.test_meth$0od()
668 }
669 ",
670 r"
671 use test_mod::TestTrait;
672
673 mod test_mod {
674 pub trait TestTrait {
675 fn test_method(&self);
676 }
677 pub struct TestStruct {}
678 impl TestTrait for TestStruct {
679 fn test_method(&self) {}
680 }
681 }
682
683 fn main() {
684 let test_struct = test_mod::TestStruct {};
685 test_struct.test_method()
686 }
687 ",
688 );
689 }
690
691 #[test]
692 fn trait_method_cross_crate() {
693 check_assist(
694 auto_import,
695 r"
696 //- /main.rs crate:main deps:dep
697 fn main() {
698 let test_struct = dep::test_mod::TestStruct {};
699 test_struct.test_meth$0od()
700 }
701 //- /dep.rs crate:dep
702 pub mod test_mod {
703 pub trait TestTrait {
704 fn test_method(&self);
705 }
706 pub struct TestStruct {}
707 impl TestTrait for TestStruct {
708 fn test_method(&self) {}
709 }
710 }
711 ",
712 r"
713 use dep::test_mod::TestTrait;
714
715 fn main() {
716 let test_struct = dep::test_mod::TestStruct {};
717 test_struct.test_method()
718 }
719 ",
720 );
721 }
722
723 #[test]
724 fn assoc_fn_cross_crate() {
725 check_assist(
726 auto_import,
727 r"
728 //- /main.rs crate:main deps:dep
729 fn main() {
730 dep::test_mod::TestStruct::test_func$0tion
731 }
732 //- /dep.rs crate:dep
733 pub mod test_mod {
734 pub trait TestTrait {
735 fn test_function();
736 }
737 pub struct TestStruct {}
738 impl TestTrait for TestStruct {
739 fn test_function() {}
740 }
741 }
742 ",
743 r"
744 use dep::test_mod::TestTrait;
745
746 fn main() {
747 dep::test_mod::TestStruct::test_function
748 }
749 ",
750 );
751 }
752
753 #[test]
754 fn assoc_const_cross_crate() {
755 check_assist(
756 auto_import,
757 r"
758 //- /main.rs crate:main deps:dep
759 fn main() {
760 dep::test_mod::TestStruct::CONST$0
761 }
762 //- /dep.rs crate:dep
763 pub mod test_mod {
764 pub trait TestTrait {
765 const CONST: bool;
766 }
767 pub struct TestStruct {}
768 impl TestTrait for TestStruct {
769 const CONST: bool = true;
770 }
771 }
772 ",
773 r"
774 use dep::test_mod::TestTrait;
775
776 fn main() {
777 dep::test_mod::TestStruct::CONST
778 }
779 ",
780 );
781 }
782
783 #[test]
784 fn assoc_fn_as_method_cross_crate() {
785 check_assist_not_applicable(
786 auto_import,
787 r"
788 //- /main.rs crate:main deps:dep
789 fn main() {
790 let test_struct = dep::test_mod::TestStruct {};
791 test_struct.test_func$0tion()
792 }
793 //- /dep.rs crate:dep
794 pub mod test_mod {
795 pub trait TestTrait {
796 fn test_function();
797 }
798 pub struct TestStruct {}
799 impl TestTrait for TestStruct {
800 fn test_function() {}
801 }
802 }
803 ",
804 );
805 }
806
807 #[test]
808 fn private_trait_cross_crate() {
809 check_assist_not_applicable(
810 auto_import,
811 r"
812 //- /main.rs crate:main deps:dep
813 fn main() {
814 let test_struct = dep::test_mod::TestStruct {};
815 test_struct.test_meth$0od()
816 }
817 //- /dep.rs crate:dep
818 pub mod test_mod {
819 trait TestTrait {
820 fn test_method(&self);
821 }
822 pub struct TestStruct {}
823 impl TestTrait for TestStruct {
824 fn test_method(&self) {}
825 }
826 }
827 ",
828 );
829 }
830
831 #[test]
832 fn not_applicable_for_imported_trait_for_method() {
833 check_assist_not_applicable(
834 auto_import,
835 r"
836 mod test_mod {
837 pub trait TestTrait {
838 fn test_method(&self);
839 }
840 pub trait TestTrait2 {
841 fn test_method(&self);
842 }
843 pub enum TestEnum {
844 One,
845 Two,
846 }
847 impl TestTrait2 for TestEnum {
848 fn test_method(&self) {}
849 }
850 impl TestTrait for TestEnum {
851 fn test_method(&self) {}
852 }
853 }
854
855 use test_mod::TestTrait2;
856 fn main() {
857 let one = test_mod::TestEnum::One;
858 one.test$0_method();
859 }
860 ",
861 )
862 }
863
864 #[test]
865 fn dep_import() {
866 check_assist(
867 auto_import,
868 r"
869//- /lib.rs crate:dep
870pub struct Struct;
871
872//- /main.rs crate:main deps:dep
873fn main() {
874 Struct$0
875}
876",
877 r"use dep::Struct;
878
879fn main() {
880 Struct
881}
882",
883 );
884 }
885
886 #[test]
887 fn whole_segment() {
888 // Tests that only imports whose last segment matches the identifier get suggested.
889 check_assist(
890 auto_import,
891 r"
892//- /lib.rs crate:dep
893pub mod fmt {
894 pub trait Display {}
895}
896
897pub fn panic_fmt() {}
898
899//- /main.rs crate:main deps:dep
900struct S;
901
902impl f$0mt::Display for S {}
903",
904 r"use dep::fmt;
905
906struct S;
907
908impl fmt::Display for S {}
909",
910 );
911 }
912
913 #[test]
914 fn macro_generated() {
915 // Tests that macro-generated items are suggested from external crates.
916 check_assist(
917 auto_import,
918 r"
919//- /lib.rs crate:dep
920macro_rules! mac {
921 () => {
922 pub struct Cheese;
923 };
924}
925
926mac!();
927
928//- /main.rs crate:main deps:dep
929fn main() {
930 Cheese$0;
931}
932",
933 r"use dep::Cheese;
934
935fn main() {
936 Cheese;
937}
938",
939 );
940 }
941
942 #[test]
943 fn casing() {
944 // Tests that differently cased names don't interfere and we only suggest the matching one.
945 check_assist(
946 auto_import,
947 r"
948//- /lib.rs crate:dep
949pub struct FMT;
950pub struct fmt;
951
952//- /main.rs crate:main deps:dep
953fn main() {
954 FMT$0;
955}
956",
957 r"use dep::FMT;
958
959fn main() {
960 FMT;
961}
962",
963 );
964 }
965}
diff --git a/crates/assists/src/handlers/change_visibility.rs b/crates/assists/src/handlers/change_visibility.rs
deleted file mode 100644
index ac8c44124..000000000
--- a/crates/assists/src/handlers/change_visibility.rs
+++ /dev/null
@@ -1,212 +0,0 @@
1use syntax::{
2 ast::{self, NameOwner, VisibilityOwner},
3 AstNode,
4 SyntaxKind::{CONST, ENUM, FN, MODULE, STATIC, STRUCT, TRAIT, TYPE_ALIAS, 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// $0fn 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]
34 | T![static]
35 | T![fn]
36 | T![mod]
37 | T![struct]
38 | T![enum]
39 | T![trait]
40 | T![type]
41 )
42 });
43
44 let (offset, target) = if let Some(keyword) = item_keyword {
45 let parent = keyword.parent();
46 let def_kws = vec![CONST, STATIC, TYPE_ALIAS, FN, MODULE, STRUCT, ENUM, TRAIT];
47 // Parent is not a definition, can't add visibility
48 if !def_kws.iter().any(|&def_kw| def_kw == parent.kind()) {
49 return None;
50 }
51 // Already have visibility, do nothing
52 if parent.children().any(|child| child.kind() == VISIBILITY) {
53 return None;
54 }
55 (vis_offset(&parent), keyword.text_range())
56 } else if let Some(field_name) = ctx.find_node_at_offset::<ast::Name>() {
57 let field = field_name.syntax().ancestors().find_map(ast::RecordField::cast)?;
58 if field.name()? != field_name {
59 mark::hit!(change_visibility_field_false_positive);
60 return None;
61 }
62 if field.visibility().is_some() {
63 return None;
64 }
65 (vis_offset(field.syntax()), field_name.syntax().text_range())
66 } else if let Some(field) = ctx.find_node_at_offset::<ast::TupleField>() {
67 if field.visibility().is_some() {
68 return None;
69 }
70 (vis_offset(field.syntax()), field.syntax().text_range())
71 } else {
72 return None;
73 };
74
75 acc.add(
76 AssistId("change_visibility", AssistKind::RefactorRewrite),
77 "Change visibility to pub(crate)",
78 target,
79 |edit| {
80 edit.insert(offset, "pub(crate) ");
81 },
82 )
83}
84
85fn change_vis(acc: &mut Assists, vis: ast::Visibility) -> Option<()> {
86 if vis.syntax().text() == "pub" {
87 let target = vis.syntax().text_range();
88 return acc.add(
89 AssistId("change_visibility", AssistKind::RefactorRewrite),
90 "Change Visibility to pub(crate)",
91 target,
92 |edit| {
93 edit.replace(vis.syntax().text_range(), "pub(crate)");
94 },
95 );
96 }
97 if vis.syntax().text() == "pub(crate)" {
98 let target = vis.syntax().text_range();
99 return acc.add(
100 AssistId("change_visibility", AssistKind::RefactorRewrite),
101 "Change visibility to pub",
102 target,
103 |edit| {
104 edit.replace(vis.syntax().text_range(), "pub");
105 },
106 );
107 }
108 None
109}
110
111#[cfg(test)]
112mod tests {
113 use test_utils::mark;
114
115 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
116
117 use super::*;
118
119 #[test]
120 fn change_visibility_adds_pub_crate_to_items() {
121 check_assist(change_visibility, "$0fn foo() {}", "pub(crate) fn foo() {}");
122 check_assist(change_visibility, "f$0n foo() {}", "pub(crate) fn foo() {}");
123 check_assist(change_visibility, "$0struct Foo {}", "pub(crate) struct Foo {}");
124 check_assist(change_visibility, "$0mod foo {}", "pub(crate) mod foo {}");
125 check_assist(change_visibility, "$0trait Foo {}", "pub(crate) trait Foo {}");
126 check_assist(change_visibility, "m$0od {}", "pub(crate) mod {}");
127 check_assist(change_visibility, "unsafe f$0n foo() {}", "pub(crate) unsafe fn foo() {}");
128 }
129
130 #[test]
131 fn change_visibility_works_with_struct_fields() {
132 check_assist(
133 change_visibility,
134 r"struct S { $0field: u32 }",
135 r"struct S { pub(crate) field: u32 }",
136 );
137 check_assist(change_visibility, r"struct S ( $0u32 )", r"struct S ( pub(crate) u32 )");
138 }
139
140 #[test]
141 fn change_visibility_field_false_positive() {
142 mark::check!(change_visibility_field_false_positive);
143 check_assist_not_applicable(
144 change_visibility,
145 r"struct S { field: [(); { let $0x = ();}] }",
146 )
147 }
148
149 #[test]
150 fn change_visibility_pub_to_pub_crate() {
151 check_assist(change_visibility, "$0pub fn foo() {}", "pub(crate) fn foo() {}")
152 }
153
154 #[test]
155 fn change_visibility_pub_crate_to_pub() {
156 check_assist(change_visibility, "$0pub(crate) fn foo() {}", "pub fn foo() {}")
157 }
158
159 #[test]
160 fn change_visibility_const() {
161 check_assist(change_visibility, "$0const FOO = 3u8;", "pub(crate) const FOO = 3u8;");
162 }
163
164 #[test]
165 fn change_visibility_static() {
166 check_assist(change_visibility, "$0static FOO = 3u8;", "pub(crate) static FOO = 3u8;");
167 }
168
169 #[test]
170 fn change_visibility_type_alias() {
171 check_assist(change_visibility, "$0type T = ();", "pub(crate) type T = ();");
172 }
173
174 #[test]
175 fn change_visibility_handles_comment_attrs() {
176 check_assist(
177 change_visibility,
178 r"
179 /// docs
180
181 // comments
182
183 #[derive(Debug)]
184 $0struct Foo;
185 ",
186 r"
187 /// docs
188
189 // comments
190
191 #[derive(Debug)]
192 pub(crate) struct Foo;
193 ",
194 )
195 }
196
197 #[test]
198 fn not_applicable_for_enum_variants() {
199 check_assist_not_applicable(
200 change_visibility,
201 r"mod foo { pub enum Foo {Foo1} }
202 fn main() { foo::Foo::Foo1$0 } ",
203 );
204 }
205
206 #[test]
207 fn change_visibility_target() {
208 check_assist_target(change_visibility, "$0fn foo() {}", "fn");
209 check_assist_target(change_visibility, "pub(crate)$0 fn foo() {}", "pub(crate)");
210 check_assist_target(change_visibility, "struct S { $0field: u32 }", "field");
211 }
212}
diff --git a/crates/assists/src/handlers/convert_integer_literal.rs b/crates/assists/src/handlers/convert_integer_literal.rs
deleted file mode 100644
index a8a819cfc..000000000
--- a/crates/assists/src/handlers/convert_integer_literal.rs
+++ /dev/null
@@ -1,268 +0,0 @@
1use syntax::{ast, ast::Radix, AstToken};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists, GroupLabel};
4
5// Assist: convert_integer_literal
6//
7// Converts the base of integer literals to other bases.
8//
9// ```
10// const _: i32 = 10$0;
11// ```
12// ->
13// ```
14// const _: i32 = 0b1010;
15// ```
16pub(crate) fn convert_integer_literal(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
17 let literal = ctx.find_node_at_offset::<ast::Literal>()?;
18 let literal = match literal.kind() {
19 ast::LiteralKind::IntNumber(it) => it,
20 _ => return None,
21 };
22 let radix = literal.radix();
23 let value = literal.value()?;
24 let suffix = literal.suffix();
25
26 let range = literal.syntax().text_range();
27 let group_id = GroupLabel("Convert integer base".into());
28
29 for &target_radix in Radix::ALL {
30 if target_radix == radix {
31 continue;
32 }
33
34 let mut converted = match target_radix {
35 Radix::Binary => format!("0b{:b}", value),
36 Radix::Octal => format!("0o{:o}", value),
37 Radix::Decimal => value.to_string(),
38 Radix::Hexadecimal => format!("0x{:X}", value),
39 };
40
41 let label = format!("Convert {} to {}{}", literal, converted, suffix.unwrap_or_default());
42
43 // Appends the type suffix back into the new literal if it exists.
44 if let Some(suffix) = suffix {
45 converted.push_str(suffix);
46 }
47
48 acc.add_group(
49 &group_id,
50 AssistId("convert_integer_literal", AssistKind::RefactorInline),
51 label,
52 range,
53 |builder| builder.replace(range, converted),
54 );
55 }
56
57 Some(())
58}
59
60#[cfg(test)]
61mod tests {
62 use crate::tests::{check_assist_by_label, check_assist_not_applicable, check_assist_target};
63
64 use super::*;
65
66 #[test]
67 fn binary_target() {
68 check_assist_target(convert_integer_literal, "const _: i32 = 0b1010$0;", "0b1010");
69 }
70
71 #[test]
72 fn octal_target() {
73 check_assist_target(convert_integer_literal, "const _: i32 = 0o12$0;", "0o12");
74 }
75
76 #[test]
77 fn decimal_target() {
78 check_assist_target(convert_integer_literal, "const _: i32 = 10$0;", "10");
79 }
80
81 #[test]
82 fn hexadecimal_target() {
83 check_assist_target(convert_integer_literal, "const _: i32 = 0xA$0;", "0xA");
84 }
85
86 #[test]
87 fn binary_target_with_underscores() {
88 check_assist_target(convert_integer_literal, "const _: i32 = 0b10_10$0;", "0b10_10");
89 }
90
91 #[test]
92 fn octal_target_with_underscores() {
93 check_assist_target(convert_integer_literal, "const _: i32 = 0o1_2$0;", "0o1_2");
94 }
95
96 #[test]
97 fn decimal_target_with_underscores() {
98 check_assist_target(convert_integer_literal, "const _: i32 = 1_0$0;", "1_0");
99 }
100
101 #[test]
102 fn hexadecimal_target_with_underscores() {
103 check_assist_target(convert_integer_literal, "const _: i32 = 0x_A$0;", "0x_A");
104 }
105
106 #[test]
107 fn convert_decimal_integer() {
108 let before = "const _: i32 = 1000$0;";
109
110 check_assist_by_label(
111 convert_integer_literal,
112 before,
113 "const _: i32 = 0b1111101000;",
114 "Convert 1000 to 0b1111101000",
115 );
116
117 check_assist_by_label(
118 convert_integer_literal,
119 before,
120 "const _: i32 = 0o1750;",
121 "Convert 1000 to 0o1750",
122 );
123
124 check_assist_by_label(
125 convert_integer_literal,
126 before,
127 "const _: i32 = 0x3E8;",
128 "Convert 1000 to 0x3E8",
129 );
130 }
131
132 #[test]
133 fn convert_hexadecimal_integer() {
134 let before = "const _: i32 = 0xFF$0;";
135
136 check_assist_by_label(
137 convert_integer_literal,
138 before,
139 "const _: i32 = 0b11111111;",
140 "Convert 0xFF to 0b11111111",
141 );
142
143 check_assist_by_label(
144 convert_integer_literal,
145 before,
146 "const _: i32 = 0o377;",
147 "Convert 0xFF to 0o377",
148 );
149
150 check_assist_by_label(
151 convert_integer_literal,
152 before,
153 "const _: i32 = 255;",
154 "Convert 0xFF to 255",
155 );
156 }
157
158 #[test]
159 fn convert_binary_integer() {
160 let before = "const _: i32 = 0b11111111$0;";
161
162 check_assist_by_label(
163 convert_integer_literal,
164 before,
165 "const _: i32 = 0o377;",
166 "Convert 0b11111111 to 0o377",
167 );
168
169 check_assist_by_label(
170 convert_integer_literal,
171 before,
172 "const _: i32 = 255;",
173 "Convert 0b11111111 to 255",
174 );
175
176 check_assist_by_label(
177 convert_integer_literal,
178 before,
179 "const _: i32 = 0xFF;",
180 "Convert 0b11111111 to 0xFF",
181 );
182 }
183
184 #[test]
185 fn convert_octal_integer() {
186 let before = "const _: i32 = 0o377$0;";
187
188 check_assist_by_label(
189 convert_integer_literal,
190 before,
191 "const _: i32 = 0b11111111;",
192 "Convert 0o377 to 0b11111111",
193 );
194
195 check_assist_by_label(
196 convert_integer_literal,
197 before,
198 "const _: i32 = 255;",
199 "Convert 0o377 to 255",
200 );
201
202 check_assist_by_label(
203 convert_integer_literal,
204 before,
205 "const _: i32 = 0xFF;",
206 "Convert 0o377 to 0xFF",
207 );
208 }
209
210 #[test]
211 fn convert_integer_with_underscores() {
212 let before = "const _: i32 = 1_00_0$0;";
213
214 check_assist_by_label(
215 convert_integer_literal,
216 before,
217 "const _: i32 = 0b1111101000;",
218 "Convert 1_00_0 to 0b1111101000",
219 );
220
221 check_assist_by_label(
222 convert_integer_literal,
223 before,
224 "const _: i32 = 0o1750;",
225 "Convert 1_00_0 to 0o1750",
226 );
227
228 check_assist_by_label(
229 convert_integer_literal,
230 before,
231 "const _: i32 = 0x3E8;",
232 "Convert 1_00_0 to 0x3E8",
233 );
234 }
235
236 #[test]
237 fn convert_integer_with_suffix() {
238 let before = "const _: i32 = 1000i32$0;";
239
240 check_assist_by_label(
241 convert_integer_literal,
242 before,
243 "const _: i32 = 0b1111101000i32;",
244 "Convert 1000i32 to 0b1111101000i32",
245 );
246
247 check_assist_by_label(
248 convert_integer_literal,
249 before,
250 "const _: i32 = 0o1750i32;",
251 "Convert 1000i32 to 0o1750i32",
252 );
253
254 check_assist_by_label(
255 convert_integer_literal,
256 before,
257 "const _: i32 = 0x3E8i32;",
258 "Convert 1000i32 to 0x3E8i32",
259 );
260 }
261
262 #[test]
263 fn convert_overflowing_literal() {
264 let before = "const _: i32 =
265 111111111111111111111111111111111111111111111111111111111111111111111111$0;";
266 check_assist_not_applicable(convert_integer_literal, before);
267 }
268}
diff --git a/crates/assists/src/handlers/early_return.rs b/crates/assists/src/handlers/early_return.rs
deleted file mode 100644
index 8bbbb7ed5..000000000
--- a/crates/assists/src/handlers/early_return.rs
+++ /dev/null
@@ -1,515 +0,0 @@
1use std::{iter::once, ops::RangeInclusive};
2
3use 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// $0if 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.fields().count() == 1 => {
55 let path = pat.path()?;
56 match path.qualifier() {
57 None => {
58 let bound_ident = pat.fields().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.tail_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, None)
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::ident_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::wildcard_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::ident_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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 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$0 true {
508 foo();
509 }
510 }
511 }
512 "#,
513 );
514 }
515}
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
deleted file mode 100644
index 5fe617ba4..000000000
--- a/crates/assists/src/handlers/expand_glob_import.rs
+++ /dev/null
@@ -1,907 +0,0 @@
1use either::Either;
2use hir::{AssocItem, MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef};
3use ide_db::{
4 defs::{Definition, NameRefClass},
5 search::SearchScope,
6};
7use syntax::{
8 algo::SyntaxRewriter,
9 ast::{self, make},
10 AstNode, Direction, SyntaxNode, SyntaxToken, T,
11};
12
13use crate::{
14 assist_context::{AssistContext, Assists},
15 AssistId, AssistKind,
16};
17
18// Assist: expand_glob_import
19//
20// Expands glob imports.
21//
22// ```
23// mod foo {
24// pub struct Bar;
25// pub struct Baz;
26// }
27//
28// use foo::*$0;
29//
30// fn qux(bar: Bar, baz: Baz) {}
31// ```
32// ->
33// ```
34// mod foo {
35// pub struct Bar;
36// pub struct Baz;
37// }
38//
39// use foo::{Baz, Bar};
40//
41// fn qux(bar: Bar, baz: Baz) {}
42// ```
43pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let star = ctx.find_token_syntax_at_offset(T![*])?;
45 let (parent, mod_path) = find_parent_and_path(&star)?;
46 let target_module = match ctx.sema.resolve_path(&mod_path)? {
47 PathResolution::Def(ModuleDef::Module(it)) => it,
48 _ => return None,
49 };
50
51 let current_scope = ctx.sema.scope(&star.parent());
52 let current_module = current_scope.module()?;
53
54 let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?;
55 let imported_defs = find_imported_defs(ctx, star)?;
56 let names_to_import = find_names_to_import(ctx, refs_in_target, imported_defs);
57
58 let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone());
59 acc.add(
60 AssistId("expand_glob_import", AssistKind::RefactorRewrite),
61 "Expand glob import",
62 target.text_range(),
63 |builder| {
64 let mut rewriter = SyntaxRewriter::default();
65 replace_ast(&mut rewriter, parent, mod_path, names_to_import);
66 builder.rewrite(rewriter);
67 },
68 )
69}
70
71fn find_parent_and_path(
72 star: &SyntaxToken,
73) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> {
74 return star.ancestors().find_map(|n| {
75 find_use_tree_list(n.clone())
76 .and_then(|(u, p)| Some((Either::Right(u), p)))
77 .or_else(|| find_use_tree(n).and_then(|(u, p)| Some((Either::Left(u), p))))
78 });
79
80 fn find_use_tree_list(n: SyntaxNode) -> Option<(ast::UseTreeList, ast::Path)> {
81 let use_tree_list = ast::UseTreeList::cast(n)?;
82 let path = use_tree_list.parent_use_tree().path()?;
83 Some((use_tree_list, path))
84 }
85
86 fn find_use_tree(n: SyntaxNode) -> Option<(ast::UseTree, ast::Path)> {
87 let use_tree = ast::UseTree::cast(n)?;
88 let path = use_tree.path()?;
89 Some((use_tree, path))
90 }
91}
92
93#[derive(Debug, PartialEq, Clone)]
94enum Def {
95 ModuleDef(ModuleDef),
96 MacroDef(MacroDef),
97}
98
99impl Def {
100 fn is_referenced_in(&self, ctx: &AssistContext) -> bool {
101 let def = match self {
102 Def::ModuleDef(def) => Definition::ModuleDef(*def),
103 Def::MacroDef(def) => Definition::Macro(*def),
104 };
105
106 let search_scope = SearchScope::single_file(ctx.frange.file_id);
107 def.usages(&ctx.sema).in_scope(search_scope).at_least_one()
108 }
109}
110
111#[derive(Debug, Clone)]
112struct Ref {
113 // could be alias
114 visible_name: Name,
115 def: Def,
116}
117
118impl Ref {
119 fn from_scope_def(name: Name, scope_def: ScopeDef) -> Option<Self> {
120 match scope_def {
121 ScopeDef::ModuleDef(def) => Some(Ref { visible_name: name, def: Def::ModuleDef(def) }),
122 ScopeDef::MacroDef(def) => Some(Ref { visible_name: name, def: Def::MacroDef(def) }),
123 _ => None,
124 }
125 }
126}
127
128#[derive(Debug, Clone)]
129struct Refs(Vec<Ref>);
130
131impl Refs {
132 fn used_refs(&self, ctx: &AssistContext) -> Refs {
133 Refs(
134 self.0
135 .clone()
136 .into_iter()
137 .filter(|r| {
138 if let Def::ModuleDef(ModuleDef::Trait(tr)) = r.def {
139 if tr
140 .items(ctx.db())
141 .into_iter()
142 .find(|ai| {
143 if let AssocItem::Function(f) = *ai {
144 Def::ModuleDef(ModuleDef::Function(f)).is_referenced_in(ctx)
145 } else {
146 false
147 }
148 })
149 .is_some()
150 {
151 return true;
152 }
153 }
154
155 r.def.is_referenced_in(ctx)
156 })
157 .collect(),
158 )
159 }
160
161 fn filter_out_by_defs(&self, defs: Vec<Def>) -> Refs {
162 Refs(self.0.clone().into_iter().filter(|r| !defs.contains(&r.def)).collect())
163 }
164}
165
166fn find_refs_in_mod(
167 ctx: &AssistContext,
168 module: Module,
169 visible_from: Option<Module>,
170) -> Option<Refs> {
171 if let Some(from) = visible_from {
172 if !is_mod_visible_from(ctx, module, from) {
173 return None;
174 }
175 }
176
177 let module_scope = module.scope(ctx.db(), visible_from);
178 let refs = module_scope.into_iter().filter_map(|(n, d)| Ref::from_scope_def(n, d)).collect();
179 Some(Refs(refs))
180}
181
182fn is_mod_visible_from(ctx: &AssistContext, module: Module, from: Module) -> bool {
183 match module.parent(ctx.db()) {
184 Some(parent) => {
185 parent.visibility_of(ctx.db(), &ModuleDef::Module(module)).map_or(true, |vis| {
186 vis.is_visible_from(ctx.db(), from.into()) && is_mod_visible_from(ctx, parent, from)
187 })
188 }
189 None => true,
190 }
191}
192
193// looks for name refs in parent use block's siblings
194//
195// mod bar {
196// mod qux {
197// struct Qux;
198// }
199//
200// pub use qux::Qux;
201// }
202//
203// ↓ ---------------
204// use foo::*$0;
205// use baz::Baz;
206// ↑ ---------------
207fn find_imported_defs(ctx: &AssistContext, star: SyntaxToken) -> Option<Vec<Def>> {
208 let parent_use_item_syntax =
209 star.ancestors().find_map(|n| if ast::Use::can_cast(n.kind()) { Some(n) } else { None })?;
210
211 Some(
212 [Direction::Prev, Direction::Next]
213 .iter()
214 .map(|dir| {
215 parent_use_item_syntax
216 .siblings(dir.to_owned())
217 .filter(|n| ast::Use::can_cast(n.kind()))
218 })
219 .flatten()
220 .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast)))
221 .flatten()
222 .filter_map(|r| match NameRefClass::classify(&ctx.sema, &r)? {
223 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
224 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
225 _ => None,
226 })
227 .collect(),
228 )
229}
230
231fn find_names_to_import(
232 ctx: &AssistContext,
233 refs_in_target: Refs,
234 imported_defs: Vec<Def>,
235) -> Vec<Name> {
236 let used_refs = refs_in_target.used_refs(ctx).filter_out_by_defs(imported_defs);
237 used_refs.0.iter().map(|r| r.visible_name.clone()).collect()
238}
239
240fn replace_ast(
241 rewriter: &mut SyntaxRewriter,
242 parent: Either<ast::UseTree, ast::UseTreeList>,
243 path: ast::Path,
244 names_to_import: Vec<Name>,
245) {
246 let existing_use_trees = match parent.clone() {
247 Either::Left(_) => vec![],
248 Either::Right(u) => u
249 .use_trees()
250 .filter(|n|
251 // filter out star
252 n.star_token().is_none())
253 .collect(),
254 };
255
256 let new_use_trees: Vec<ast::UseTree> = names_to_import
257 .iter()
258 .map(|n| {
259 let path = make::path_unqualified(make::path_segment(make::name_ref(&n.to_string())));
260 make::use_tree(path, None, None, false)
261 })
262 .collect();
263
264 let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
265
266 match use_trees.as_slice() {
267 [name] => {
268 if let Some(end_path) = name.path() {
269 rewriter.replace_ast(
270 &parent.left_or_else(|tl| tl.parent_use_tree()),
271 &make::use_tree(make::path_concat(path, end_path), None, None, false),
272 );
273 }
274 }
275 names => match &parent {
276 Either::Left(parent) => rewriter.replace_ast(
277 parent,
278 &make::use_tree(path, Some(make::use_tree_list(names.to_owned())), None, false),
279 ),
280 Either::Right(parent) => {
281 rewriter.replace_ast(parent, &make::use_tree_list(names.to_owned()))
282 }
283 },
284 };
285}
286
287#[cfg(test)]
288mod tests {
289 use crate::tests::{check_assist, check_assist_not_applicable};
290
291 use super::*;
292
293 #[test]
294 fn expanding_glob_import() {
295 check_assist(
296 expand_glob_import,
297 r"
298mod foo {
299 pub struct Bar;
300 pub struct Baz;
301 pub struct Qux;
302
303 pub fn f() {}
304}
305
306use foo::*$0;
307
308fn qux(bar: Bar, baz: Baz) {
309 f();
310}
311",
312 r"
313mod foo {
314 pub struct Bar;
315 pub struct Baz;
316 pub struct Qux;
317
318 pub fn f() {}
319}
320
321use foo::{Baz, Bar, f};
322
323fn qux(bar: Bar, baz: Baz) {
324 f();
325}
326",
327 )
328 }
329
330 #[test]
331 fn expanding_glob_import_with_existing_explicit_names() {
332 check_assist(
333 expand_glob_import,
334 r"
335mod foo {
336 pub struct Bar;
337 pub struct Baz;
338 pub struct Qux;
339
340 pub fn f() {}
341}
342
343use foo::{*$0, f};
344
345fn qux(bar: Bar, baz: Baz) {
346 f();
347}
348",
349 r"
350mod foo {
351 pub struct Bar;
352 pub struct Baz;
353 pub struct Qux;
354
355 pub fn f() {}
356}
357
358use foo::{f, Baz, Bar};
359
360fn qux(bar: Bar, baz: Baz) {
361 f();
362}
363",
364 )
365 }
366
367 #[test]
368 fn expanding_glob_import_with_existing_uses_in_same_module() {
369 check_assist(
370 expand_glob_import,
371 r"
372mod foo {
373 pub struct Bar;
374 pub struct Baz;
375 pub struct Qux;
376
377 pub fn f() {}
378}
379
380use foo::Bar;
381use foo::{*$0, f};
382
383fn qux(bar: Bar, baz: Baz) {
384 f();
385}
386",
387 r"
388mod foo {
389 pub struct Bar;
390 pub struct Baz;
391 pub struct Qux;
392
393 pub fn f() {}
394}
395
396use foo::Bar;
397use foo::{f, Baz};
398
399fn qux(bar: Bar, baz: Baz) {
400 f();
401}
402",
403 )
404 }
405
406 #[test]
407 fn expanding_nested_glob_import() {
408 check_assist(
409 expand_glob_import,
410 r"
411mod foo {
412 pub mod bar {
413 pub struct Bar;
414 pub struct Baz;
415 pub struct Qux;
416
417 pub fn f() {}
418 }
419
420 pub mod baz {
421 pub fn g() {}
422 }
423}
424
425use foo::{bar::{*$0, f}, baz::*};
426
427fn qux(bar: Bar, baz: Baz) {
428 f();
429 g();
430}
431",
432 r"
433mod foo {
434 pub mod bar {
435 pub struct Bar;
436 pub struct Baz;
437 pub struct Qux;
438
439 pub fn f() {}
440 }
441
442 pub mod baz {
443 pub fn g() {}
444 }
445}
446
447use foo::{bar::{f, Baz, Bar}, baz::*};
448
449fn qux(bar: Bar, baz: Baz) {
450 f();
451 g();
452}
453",
454 );
455
456 check_assist(
457 expand_glob_import,
458 r"
459mod foo {
460 pub mod bar {
461 pub struct Bar;
462 pub struct Baz;
463 pub struct Qux;
464
465 pub fn f() {}
466 }
467
468 pub mod baz {
469 pub fn g() {}
470 }
471}
472
473use foo::{bar::{Bar, Baz, f}, baz::*$0};
474
475fn qux(bar: Bar, baz: Baz) {
476 f();
477 g();
478}
479",
480 r"
481mod foo {
482 pub mod bar {
483 pub struct Bar;
484 pub struct Baz;
485 pub struct Qux;
486
487 pub fn f() {}
488 }
489
490 pub mod baz {
491 pub fn g() {}
492 }
493}
494
495use foo::{bar::{Bar, Baz, f}, baz::g};
496
497fn qux(bar: Bar, baz: Baz) {
498 f();
499 g();
500}
501",
502 );
503
504 check_assist(
505 expand_glob_import,
506 r"
507mod foo {
508 pub mod bar {
509 pub struct Bar;
510 pub struct Baz;
511 pub struct Qux;
512
513 pub fn f() {}
514 }
515
516 pub mod baz {
517 pub fn g() {}
518
519 pub mod qux {
520 pub fn h() {}
521 pub fn m() {}
522
523 pub mod q {
524 pub fn j() {}
525 }
526 }
527 }
528}
529
530use foo::{
531 bar::{*, f},
532 baz::{g, qux::*$0}
533};
534
535fn qux(bar: Bar, baz: Baz) {
536 f();
537 g();
538 h();
539 q::j();
540}
541",
542 r"
543mod foo {
544 pub mod bar {
545 pub struct Bar;
546 pub struct Baz;
547 pub struct Qux;
548
549 pub fn f() {}
550 }
551
552 pub mod baz {
553 pub fn g() {}
554
555 pub mod qux {
556 pub fn h() {}
557 pub fn m() {}
558
559 pub mod q {
560 pub fn j() {}
561 }
562 }
563 }
564}
565
566use foo::{
567 bar::{*, f},
568 baz::{g, qux::{q, h}}
569};
570
571fn qux(bar: Bar, baz: Baz) {
572 f();
573 g();
574 h();
575 q::j();
576}
577",
578 );
579
580 check_assist(
581 expand_glob_import,
582 r"
583mod foo {
584 pub mod bar {
585 pub struct Bar;
586 pub struct Baz;
587 pub struct Qux;
588
589 pub fn f() {}
590 }
591
592 pub mod baz {
593 pub fn g() {}
594
595 pub mod qux {
596 pub fn h() {}
597 pub fn m() {}
598
599 pub mod q {
600 pub fn j() {}
601 }
602 }
603 }
604}
605
606use foo::{
607 bar::{*, f},
608 baz::{g, qux::{h, q::*$0}}
609};
610
611fn qux(bar: Bar, baz: Baz) {
612 f();
613 g();
614 h();
615 j();
616}
617",
618 r"
619mod foo {
620 pub mod bar {
621 pub struct Bar;
622 pub struct Baz;
623 pub struct Qux;
624
625 pub fn f() {}
626 }
627
628 pub mod baz {
629 pub fn g() {}
630
631 pub mod qux {
632 pub fn h() {}
633 pub fn m() {}
634
635 pub mod q {
636 pub fn j() {}
637 }
638 }
639 }
640}
641
642use foo::{
643 bar::{*, f},
644 baz::{g, qux::{h, q::j}}
645};
646
647fn qux(bar: Bar, baz: Baz) {
648 f();
649 g();
650 h();
651 j();
652}
653",
654 );
655
656 check_assist(
657 expand_glob_import,
658 r"
659mod foo {
660 pub mod bar {
661 pub struct Bar;
662 pub struct Baz;
663 pub struct Qux;
664
665 pub fn f() {}
666 }
667
668 pub mod baz {
669 pub fn g() {}
670
671 pub mod qux {
672 pub fn h() {}
673 pub fn m() {}
674
675 pub mod q {
676 pub fn j() {}
677 }
678 }
679 }
680}
681
682use foo::{
683 bar::{*, f},
684 baz::{g, qux::{q::j, *$0}}
685};
686
687fn qux(bar: Bar, baz: Baz) {
688 f();
689 g();
690 h();
691 j();
692}
693",
694 r"
695mod foo {
696 pub mod bar {
697 pub struct Bar;
698 pub struct Baz;
699 pub struct Qux;
700
701 pub fn f() {}
702 }
703
704 pub mod baz {
705 pub fn g() {}
706
707 pub mod qux {
708 pub fn h() {}
709 pub fn m() {}
710
711 pub mod q {
712 pub fn j() {}
713 }
714 }
715 }
716}
717
718use foo::{
719 bar::{*, f},
720 baz::{g, qux::{q::j, h}}
721};
722
723fn qux(bar: Bar, baz: Baz) {
724 f();
725 g();
726 h();
727 j();
728}
729",
730 );
731 }
732
733 #[test]
734 fn expanding_glob_import_with_macro_defs() {
735 // FIXME: this is currently fails because `Definition::find_usages` ignores macros
736 // https://github.com/rust-analyzer/rust-analyzer/issues/3484
737 //
738 // check_assist(
739 // expand_glob_import,
740 // r"
741 // //- /lib.rs crate:foo
742 // #[macro_export]
743 // macro_rules! bar {
744 // () => ()
745 // }
746
747 // pub fn baz() {}
748
749 // //- /main.rs crate:main deps:foo
750 // use foo::*$0;
751
752 // fn main() {
753 // bar!();
754 // baz();
755 // }
756 // ",
757 // r"
758 // use foo::{bar, baz};
759
760 // fn main() {
761 // bar!();
762 // baz();
763 // }
764 // ",
765 // )
766 }
767
768 #[test]
769 fn expanding_glob_import_with_trait_method_uses() {
770 check_assist(
771 expand_glob_import,
772 r"
773//- /lib.rs crate:foo
774pub trait Tr {
775 fn method(&self) {}
776}
777impl Tr for () {}
778
779//- /main.rs crate:main deps:foo
780use foo::*$0;
781
782fn main() {
783 ().method();
784}
785",
786 r"
787use foo::Tr;
788
789fn main() {
790 ().method();
791}
792",
793 );
794
795 check_assist(
796 expand_glob_import,
797 r"
798//- /lib.rs crate:foo
799pub trait Tr {
800 fn method(&self) {}
801}
802impl Tr for () {}
803
804pub trait Tr2 {
805 fn method2(&self) {}
806}
807impl Tr2 for () {}
808
809//- /main.rs crate:main deps:foo
810use foo::*$0;
811
812fn main() {
813 ().method();
814}
815",
816 r"
817use foo::Tr;
818
819fn main() {
820 ().method();
821}
822",
823 );
824 }
825
826 #[test]
827 fn expanding_is_not_applicable_if_target_module_is_not_accessible_from_current_scope() {
828 check_assist_not_applicable(
829 expand_glob_import,
830 r"
831mod foo {
832 mod bar {
833 pub struct Bar;
834 }
835}
836
837use foo::bar::*$0;
838
839fn baz(bar: Bar) {}
840",
841 );
842
843 check_assist_not_applicable(
844 expand_glob_import,
845 r"
846mod foo {
847 mod bar {
848 pub mod baz {
849 pub struct Baz;
850 }
851 }
852}
853
854use foo::bar::baz::*$0;
855
856fn qux(baz: Baz) {}
857",
858 );
859 }
860
861 #[test]
862 fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
863 check_assist_not_applicable(
864 expand_glob_import,
865 r"
866 mod foo {
867 pub struct Bar;
868 pub struct Baz;
869 pub struct Qux;
870 }
871
872 use foo::Bar$0;
873
874 fn qux(bar: Bar, baz: Baz) {}
875 ",
876 )
877 }
878
879 #[test]
880 fn expanding_glob_import_single_nested_glob_only() {
881 check_assist(
882 expand_glob_import,
883 r"
884mod foo {
885 pub struct Bar;
886}
887
888use foo::{*$0};
889
890struct Baz {
891 bar: Bar
892}
893",
894 r"
895mod foo {
896 pub struct Bar;
897}
898
899use foo::Bar;
900
901struct Baz {
902 bar: Bar
903}
904",
905 );
906 }
907}
diff --git a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs b/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
deleted file mode 100644
index e3ef04932..000000000
--- a/crates/assists/src/handlers/extract_struct_from_enum_variant.rs
+++ /dev/null
@@ -1,520 +0,0 @@
1use std::iter;
2
3use either::Either;
4use hir::{AsName, Module, ModuleDef, Name, Variant};
5use ide_db::{
6 defs::Definition,
7 helpers::{
8 insert_use::{insert_use, ImportScope},
9 mod_path_to_ast,
10 },
11 search::FileReference,
12 RootDatabase,
13};
14use rustc_hash::FxHashSet;
15use syntax::{
16 algo::{find_node_at_offset, SyntaxRewriter},
17 ast::{self, edit::IndentLevel, make, AstNode, NameOwner, VisibilityOwner},
18 SourceFile, SyntaxElement, SyntaxNode, T,
19};
20
21use crate::{AssistContext, AssistId, AssistKind, Assists};
22
23// Assist: extract_struct_from_enum_variant
24//
25// Extracts a struct from enum variant.
26//
27// ```
28// enum A { $0One(u32, u32) }
29// ```
30// ->
31// ```
32// struct One(pub u32, pub u32);
33//
34// enum A { One(One) }
35// ```
36pub(crate) fn extract_struct_from_enum_variant(
37 acc: &mut Assists,
38 ctx: &AssistContext,
39) -> Option<()> {
40 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
41 let field_list = extract_field_list_if_applicable(&variant)?;
42
43 let variant_name = variant.name()?;
44 let variant_hir = ctx.sema.to_def(&variant)?;
45 if existing_definition(ctx.db(), &variant_name, &variant_hir) {
46 return None;
47 }
48
49 let enum_ast = variant.parent_enum();
50 let enum_hir = ctx.sema.to_def(&enum_ast)?;
51 let target = variant.syntax().text_range();
52 acc.add(
53 AssistId("extract_struct_from_enum_variant", AssistKind::RefactorRewrite),
54 "Extract struct from enum variant",
55 target,
56 |builder| {
57 let variant_hir_name = variant_hir.name(ctx.db());
58 let enum_module_def = ModuleDef::from(enum_hir);
59 let usages =
60 Definition::ModuleDef(ModuleDef::Variant(variant_hir)).usages(&ctx.sema).all();
61
62 let mut visited_modules_set = FxHashSet::default();
63 let current_module = enum_hir.module(ctx.db());
64 visited_modules_set.insert(current_module);
65 let mut def_rewriter = None;
66 for (file_id, references) in usages {
67 let mut rewriter = SyntaxRewriter::default();
68 let source_file = ctx.sema.parse(file_id);
69 for reference in references {
70 update_reference(
71 ctx,
72 &mut rewriter,
73 reference,
74 &source_file,
75 &enum_module_def,
76 &variant_hir_name,
77 &mut visited_modules_set,
78 );
79 }
80 if file_id == ctx.frange.file_id {
81 def_rewriter = Some(rewriter);
82 continue;
83 }
84 builder.edit_file(file_id);
85 builder.rewrite(rewriter);
86 }
87 let mut rewriter = def_rewriter.unwrap_or_default();
88 update_variant(&mut rewriter, &variant);
89 extract_struct_def(
90 &mut rewriter,
91 &enum_ast,
92 variant_name.clone(),
93 &field_list,
94 &variant.parent_enum().syntax().clone().into(),
95 enum_ast.visibility(),
96 );
97 builder.edit_file(ctx.frange.file_id);
98 builder.rewrite(rewriter);
99 },
100 )
101}
102
103fn extract_field_list_if_applicable(
104 variant: &ast::Variant,
105) -> Option<Either<ast::RecordFieldList, ast::TupleFieldList>> {
106 match variant.kind() {
107 ast::StructKind::Record(field_list) if field_list.fields().next().is_some() => {
108 Some(Either::Left(field_list))
109 }
110 ast::StructKind::Tuple(field_list) if field_list.fields().count() > 1 => {
111 Some(Either::Right(field_list))
112 }
113 _ => None,
114 }
115}
116
117fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Variant) -> bool {
118 variant
119 .parent_enum(db)
120 .module(db)
121 .scope(db, None)
122 .into_iter()
123 .filter(|(_, def)| match def {
124 // only check type-namespace
125 hir::ScopeDef::ModuleDef(def) => matches!(
126 def,
127 ModuleDef::Module(_)
128 | ModuleDef::Adt(_)
129 | ModuleDef::Variant(_)
130 | ModuleDef::Trait(_)
131 | ModuleDef::TypeAlias(_)
132 | ModuleDef::BuiltinType(_)
133 ),
134 _ => false,
135 })
136 .any(|(name, _)| name == variant_name.as_name())
137}
138
139fn insert_import(
140 ctx: &AssistContext,
141 rewriter: &mut SyntaxRewriter,
142 scope_node: &SyntaxNode,
143 module: &Module,
144 enum_module_def: &ModuleDef,
145 variant_hir_name: &Name,
146) -> Option<()> {
147 let db = ctx.db();
148 let mod_path = module.find_use_path_prefixed(
149 db,
150 enum_module_def.clone(),
151 ctx.config.insert_use.prefix_kind,
152 );
153 if let Some(mut mod_path) = mod_path {
154 mod_path.segments.pop();
155 mod_path.segments.push(variant_hir_name.clone());
156 let scope = ImportScope::find_insert_use_container(scope_node, &ctx.sema)?;
157 *rewriter += insert_use(&scope, mod_path_to_ast(&mod_path), ctx.config.insert_use.merge);
158 }
159 Some(())
160}
161
162fn extract_struct_def(
163 rewriter: &mut SyntaxRewriter,
164 enum_: &ast::Enum,
165 variant_name: ast::Name,
166 field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
167 start_offset: &SyntaxElement,
168 visibility: Option<ast::Visibility>,
169) -> Option<()> {
170 let pub_vis = Some(make::visibility_pub());
171 let field_list = match field_list {
172 Either::Left(field_list) => {
173 make::record_field_list(field_list.fields().flat_map(|field| {
174 Some(make::record_field(pub_vis.clone(), field.name()?, field.ty()?))
175 }))
176 .into()
177 }
178 Either::Right(field_list) => make::tuple_field_list(
179 field_list
180 .fields()
181 .flat_map(|field| Some(make::tuple_field(pub_vis.clone(), field.ty()?))),
182 )
183 .into(),
184 };
185
186 rewriter.insert_before(
187 start_offset,
188 make::struct_(visibility, variant_name, None, field_list).syntax(),
189 );
190 rewriter.insert_before(start_offset, &make::tokens::blank_line());
191
192 if let indent_level @ 1..=usize::MAX = IndentLevel::from_node(enum_.syntax()).0 as usize {
193 rewriter
194 .insert_before(start_offset, &make::tokens::whitespace(&" ".repeat(4 * indent_level)));
195 }
196 Some(())
197}
198
199fn update_variant(rewriter: &mut SyntaxRewriter, variant: &ast::Variant) -> Option<()> {
200 let name = variant.name()?;
201 let tuple_field = make::tuple_field(None, make::ty(name.text()));
202 let replacement = make::variant(
203 name,
204 Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
205 );
206 rewriter.replace(variant.syntax(), replacement.syntax());
207 Some(())
208}
209
210fn update_reference(
211 ctx: &AssistContext,
212 rewriter: &mut SyntaxRewriter,
213 reference: FileReference,
214 source_file: &SourceFile,
215 enum_module_def: &ModuleDef,
216 variant_hir_name: &Name,
217 visited_modules_set: &mut FxHashSet<Module>,
218) -> Option<()> {
219 let offset = reference.range.start();
220 let (segment, expr) = if let Some(path_expr) =
221 find_node_at_offset::<ast::PathExpr>(source_file.syntax(), offset)
222 {
223 // tuple variant
224 (path_expr.path()?.segment()?, path_expr.syntax().parent()?)
225 } else if let Some(record_expr) =
226 find_node_at_offset::<ast::RecordExpr>(source_file.syntax(), offset)
227 {
228 // record variant
229 (record_expr.path()?.segment()?, record_expr.syntax().clone())
230 } else {
231 return None;
232 };
233
234 let module = ctx.sema.scope(&expr).module()?;
235 if !visited_modules_set.contains(&module) {
236 if insert_import(ctx, rewriter, &expr, &module, enum_module_def, variant_hir_name).is_some()
237 {
238 visited_modules_set.insert(module);
239 }
240 }
241 rewriter.insert_after(segment.syntax(), &make::token(T!['(']));
242 rewriter.insert_after(segment.syntax(), segment.syntax());
243 rewriter.insert_after(&expr, &make::token(T![')']));
244 Some(())
245}
246
247#[cfg(test)]
248mod tests {
249 use ide_db::helpers::FamousDefs;
250
251 use crate::tests::{check_assist, check_assist_not_applicable};
252
253 use super::*;
254
255 #[test]
256 fn test_extract_struct_several_fields_tuple() {
257 check_assist(
258 extract_struct_from_enum_variant,
259 "enum A { $0One(u32, u32) }",
260 r#"struct One(pub u32, pub u32);
261
262enum A { One(One) }"#,
263 );
264 }
265
266 #[test]
267 fn test_extract_struct_several_fields_named() {
268 check_assist(
269 extract_struct_from_enum_variant,
270 "enum A { $0One { foo: u32, bar: u32 } }",
271 r#"struct One{ pub foo: u32, pub bar: u32 }
272
273enum A { One(One) }"#,
274 );
275 }
276
277 #[test]
278 fn test_extract_struct_one_field_named() {
279 check_assist(
280 extract_struct_from_enum_variant,
281 "enum A { $0One { foo: u32 } }",
282 r#"struct One{ pub foo: u32 }
283
284enum A { One(One) }"#,
285 );
286 }
287
288 #[test]
289 fn test_extract_enum_variant_name_value_namespace() {
290 check_assist(
291 extract_struct_from_enum_variant,
292 r#"const One: () = ();
293enum A { $0One(u32, u32) }"#,
294 r#"const One: () = ();
295struct One(pub u32, pub u32);
296
297enum A { One(One) }"#,
298 );
299 }
300
301 #[test]
302 fn test_extract_struct_pub_visibility() {
303 check_assist(
304 extract_struct_from_enum_variant,
305 "pub enum A { $0One(u32, u32) }",
306 r#"pub struct One(pub u32, pub u32);
307
308pub enum A { One(One) }"#,
309 );
310 }
311
312 #[test]
313 fn test_extract_struct_with_complex_imports() {
314 check_assist(
315 extract_struct_from_enum_variant,
316 r#"mod my_mod {
317 fn another_fn() {
318 let m = my_other_mod::MyEnum::MyField(1, 1);
319 }
320
321 pub mod my_other_mod {
322 fn another_fn() {
323 let m = MyEnum::MyField(1, 1);
324 }
325
326 pub enum MyEnum {
327 $0MyField(u8, u8),
328 }
329 }
330}
331
332fn another_fn() {
333 let m = my_mod::my_other_mod::MyEnum::MyField(1, 1);
334}"#,
335 r#"use my_mod::my_other_mod::MyField;
336
337mod my_mod {
338 use self::my_other_mod::MyField;
339
340 fn another_fn() {
341 let m = my_other_mod::MyEnum::MyField(MyField(1, 1));
342 }
343
344 pub mod my_other_mod {
345 fn another_fn() {
346 let m = MyEnum::MyField(MyField(1, 1));
347 }
348
349 pub struct MyField(pub u8, pub u8);
350
351 pub enum MyEnum {
352 MyField(MyField),
353 }
354 }
355}
356
357fn another_fn() {
358 let m = my_mod::my_other_mod::MyEnum::MyField(MyField(1, 1));
359}"#,
360 );
361 }
362
363 #[test]
364 fn extract_record_fix_references() {
365 check_assist(
366 extract_struct_from_enum_variant,
367 r#"
368enum E {
369 $0V { i: i32, j: i32 }
370}
371
372fn f() {
373 let e = E::V { i: 9, j: 2 };
374}
375"#,
376 r#"
377struct V{ pub i: i32, pub j: i32 }
378
379enum E {
380 V(V)
381}
382
383fn f() {
384 let e = E::V(V { i: 9, j: 2 });
385}
386"#,
387 )
388 }
389
390 #[test]
391 fn test_several_files() {
392 check_assist(
393 extract_struct_from_enum_variant,
394 r#"
395//- /main.rs
396enum E {
397 $0V(i32, i32)
398}
399mod foo;
400
401//- /foo.rs
402use crate::E;
403fn f() {
404 let e = E::V(9, 2);
405}
406"#,
407 r#"
408//- /main.rs
409struct V(pub i32, pub i32);
410
411enum E {
412 V(V)
413}
414mod foo;
415
416//- /foo.rs
417use crate::{E, V};
418fn f() {
419 let e = E::V(V(9, 2));
420}
421"#,
422 )
423 }
424
425 #[test]
426 fn test_several_files_record() {
427 check_assist(
428 extract_struct_from_enum_variant,
429 r#"
430//- /main.rs
431enum E {
432 $0V { i: i32, j: i32 }
433}
434mod foo;
435
436//- /foo.rs
437use crate::E;
438fn f() {
439 let e = E::V { i: 9, j: 2 };
440}
441"#,
442 r#"
443//- /main.rs
444struct V{ pub i: i32, pub j: i32 }
445
446enum E {
447 V(V)
448}
449mod foo;
450
451//- /foo.rs
452use crate::{E, V};
453fn f() {
454 let e = E::V(V { i: 9, j: 2 });
455}
456"#,
457 )
458 }
459
460 #[test]
461 fn test_extract_struct_record_nested_call_exp() {
462 check_assist(
463 extract_struct_from_enum_variant,
464 r#"
465enum A { $0One { a: u32, b: u32 } }
466
467struct B(A);
468
469fn foo() {
470 let _ = B(A::One { a: 1, b: 2 });
471}
472"#,
473 r#"
474struct One{ pub a: u32, pub b: u32 }
475
476enum A { One(One) }
477
478struct B(A);
479
480fn foo() {
481 let _ = B(A::One(One { a: 1, b: 2 }));
482}
483"#,
484 );
485 }
486
487 fn check_not_applicable(ra_fixture: &str) {
488 let fixture =
489 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
490 check_assist_not_applicable(extract_struct_from_enum_variant, &fixture)
491 }
492
493 #[test]
494 fn test_extract_enum_not_applicable_for_element_with_no_fields() {
495 check_not_applicable("enum A { $0One }");
496 }
497
498 #[test]
499 fn test_extract_enum_not_applicable_if_struct_exists() {
500 check_not_applicable(
501 r#"struct One;
502 enum A { $0One(u8, u32) }"#,
503 );
504 }
505
506 #[test]
507 fn test_extract_not_applicable_one_field() {
508 check_not_applicable(r"enum A { $0One(u32) }");
509 }
510
511 #[test]
512 fn test_extract_not_applicable_no_field_tuple() {
513 check_not_applicable(r"enum A { $0None() }");
514 }
515
516 #[test]
517 fn test_extract_not_applicable_no_field_named() {
518 check_not_applicable(r"enum A { $0None {} }");
519 }
520}
diff --git a/crates/assists/src/handlers/extract_variable.rs b/crates/assists/src/handlers/extract_variable.rs
deleted file mode 100644
index 98f3dc6ca..000000000
--- a/crates/assists/src/handlers/extract_variable.rs
+++ /dev/null
@@ -1,588 +0,0 @@
1use stdx::format_to;
2use syntax::{
3 ast::{self, AstNode},
4 SyntaxKind::{
5 BLOCK_EXPR, BREAK_EXPR, CLOSURE_EXPR, COMMENT, LOOP_EXPR, MATCH_ARM, PATH_EXPR, RETURN_EXPR,
6 },
7 SyntaxNode,
8};
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// $0(1 + 2)$0 * 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('\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.tail_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() == CLOSURE_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($01 + 1$0);
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 + /* $0comment$0 */ 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 $01 + 1$0;
214}"#,
215 r#"
216fn foo() {
217 let $0var_name = 1 + 1;
218}"#,
219 );
220 check_assist(
221 extract_variable,
222 "
223fn foo() {
224 $0{ let x = 0; x }$0
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 $01$0 + 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($01 + 1$0)
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 $0bar(1 + 1)$0
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 => ($02 + 2$0, 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 ($02 + y$0, 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| $0x * 2$0;
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| { $0x * 2$0 };
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 = $0Some(true)$0;
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 = $0bar.foo()$0;
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 $0return 2 + 2$0;
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 $0return 2 + 2$0;
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 $0return 2 + 2$0;
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 $0return 2 + 2$0;
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 $0break 2 + 2$0;
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 = $00f32 as u32$0;
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: $01 + 1$0 }
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() { $0return$0; } ");
562 }
563
564 #[test]
565 fn test_extract_var_for_break_not_applicable() {
566 check_assist_not_applicable(extract_variable, "fn main() { loop { $0break$0; }; }");
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 { $0return 2 + 2$0; }", "2 + 2");
573
574 check_assist_target(
575 extract_variable,
576 "
577fn main() {
578 let x = true;
579 let tuple = match x {
580 true => ($02 + 2$0, true)
581 _ => (0, false)
582 };
583}
584",
585 "2 + 2",
586 );
587 }
588}
diff --git a/crates/assists/src/handlers/fill_match_arms.rs b/crates/assists/src/handlers/fill_match_arms.rs
deleted file mode 100644
index da47187e4..000000000
--- a/crates/assists/src/handlers/fill_match_arms.rs
+++ /dev/null
@@ -1,746 +0,0 @@
1use std::iter;
2
3use hir::{Adt, HasSource, ModuleDef, Semantics};
4use ide_db::helpers::{mod_path_to_ast, FamousDefs};
5use ide_db::RootDatabase;
6use itertools::Itertools;
7use syntax::ast::{self, make, AstNode, MatchArm, NameOwner, Pat};
8use test_utils::mark;
9
10use crate::{
11 utils::{render_snippet, Cursor},
12 AssistContext, AssistId, AssistKind, Assists,
13};
14
15// Assist: fill_match_arms
16//
17// Adds missing clauses to a `match` expression.
18//
19// ```
20// enum Action { Move { distance: u32 }, Stop }
21//
22// fn handle(action: Action) {
23// match action {
24// $0
25// }
26// }
27// ```
28// ->
29// ```
30// enum Action { Move { distance: u32 }, Stop }
31//
32// fn handle(action: Action) {
33// match action {
34// $0Action::Move { distance } => {}
35// Action::Stop => {}
36// }
37// }
38// ```
39pub(crate) fn fill_match_arms(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let match_expr = ctx.find_node_at_offset::<ast::MatchExpr>()?;
41 let match_arm_list = match_expr.match_arm_list()?;
42
43 let expr = match_expr.expr()?;
44
45 let mut arms: Vec<MatchArm> = match_arm_list.arms().collect();
46 if arms.len() == 1 {
47 if let Some(Pat::WildcardPat(..)) = arms[0].pat() {
48 arms.clear();
49 }
50 }
51
52 let module = ctx.sema.scope(expr.syntax()).module()?;
53
54 let missing_arms: Vec<MatchArm> = if let Some(enum_def) = resolve_enum_def(&ctx.sema, &expr) {
55 let variants = enum_def.variants(ctx.db());
56
57 let mut variants = variants
58 .into_iter()
59 .filter_map(|variant| build_pat(ctx.db(), module, variant))
60 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
61 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
62 .collect::<Vec<_>>();
63 if Some(enum_def) == FamousDefs(&ctx.sema, Some(module.krate())).core_option_Option() {
64 // Match `Some` variant first.
65 mark::hit!(option_order);
66 variants.reverse()
67 }
68 variants
69 } else if let Some(enum_defs) = resolve_tuple_of_enum_def(&ctx.sema, &expr) {
70 // Partial fill not currently supported for tuple of enums.
71 if !arms.is_empty() {
72 return None;
73 }
74
75 // We do not currently support filling match arms for a tuple
76 // containing a single enum.
77 if enum_defs.len() < 2 {
78 return None;
79 }
80
81 // When calculating the match arms for a tuple of enums, we want
82 // to create a match arm for each possible combination of enum
83 // values. The `multi_cartesian_product` method transforms
84 // Vec<Vec<EnumVariant>> into Vec<(EnumVariant, .., EnumVariant)>
85 // where each tuple represents a proposed match arm.
86 enum_defs
87 .into_iter()
88 .map(|enum_def| enum_def.variants(ctx.db()))
89 .multi_cartesian_product()
90 .map(|variants| {
91 let patterns =
92 variants.into_iter().filter_map(|variant| build_pat(ctx.db(), module, variant));
93 ast::Pat::from(make::tuple_pat(patterns))
94 })
95 .filter(|variant_pat| is_variant_missing(&mut arms, variant_pat))
96 .map(|pat| make::match_arm(iter::once(pat), make::expr_empty_block()))
97 .collect()
98 } else {
99 return None;
100 };
101
102 if missing_arms.is_empty() {
103 return None;
104 }
105
106 let target = match_expr.syntax().text_range();
107 acc.add(
108 AssistId("fill_match_arms", AssistKind::QuickFix),
109 "Fill match arms",
110 target,
111 |builder| {
112 let new_arm_list = match_arm_list.remove_placeholder();
113 let n_old_arms = new_arm_list.arms().count();
114 let new_arm_list = new_arm_list.append_arms(missing_arms);
115 let first_new_arm = new_arm_list.arms().nth(n_old_arms);
116 let old_range = match_arm_list.syntax().text_range();
117 match (first_new_arm, ctx.config.snippet_cap) {
118 (Some(first_new_arm), Some(cap)) => {
119 let extend_lifetime;
120 let cursor =
121 match first_new_arm.syntax().descendants().find_map(ast::WildcardPat::cast)
122 {
123 Some(it) => {
124 extend_lifetime = it.syntax().clone();
125 Cursor::Replace(&extend_lifetime)
126 }
127 None => Cursor::Before(first_new_arm.syntax()),
128 };
129 let snippet = render_snippet(cap, new_arm_list.syntax(), cursor);
130 builder.replace_snippet(cap, old_range, snippet);
131 }
132 _ => builder.replace(old_range, new_arm_list.to_string()),
133 }
134 },
135 )
136}
137
138fn is_variant_missing(existing_arms: &mut Vec<MatchArm>, var: &Pat) -> bool {
139 existing_arms.iter().filter_map(|arm| arm.pat()).all(|pat| {
140 // Special casee OrPat as separate top-level pats
141 let top_level_pats: Vec<Pat> = match pat {
142 Pat::OrPat(pats) => pats.pats().collect::<Vec<_>>(),
143 _ => vec![pat],
144 };
145
146 !top_level_pats.iter().any(|pat| does_pat_match_variant(pat, var))
147 })
148}
149
150fn does_pat_match_variant(pat: &Pat, var: &Pat) -> bool {
151 let first_node_text = |pat: &Pat| pat.syntax().first_child().map(|node| node.text());
152
153 let pat_head = match pat {
154 Pat::IdentPat(bind_pat) => {
155 if let Some(p) = bind_pat.pat() {
156 first_node_text(&p)
157 } else {
158 return false;
159 }
160 }
161 pat => first_node_text(pat),
162 };
163
164 let var_head = first_node_text(var);
165
166 pat_head == var_head
167}
168
169fn resolve_enum_def(sema: &Semantics<RootDatabase>, expr: &ast::Expr) -> Option<hir::Enum> {
170 sema.type_of_expr(&expr)?.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
171 Some(Adt::Enum(e)) => Some(e),
172 _ => None,
173 })
174}
175
176fn resolve_tuple_of_enum_def(
177 sema: &Semantics<RootDatabase>,
178 expr: &ast::Expr,
179) -> Option<Vec<hir::Enum>> {
180 sema.type_of_expr(&expr)?
181 .tuple_fields(sema.db)
182 .iter()
183 .map(|ty| {
184 ty.autoderef(sema.db).find_map(|ty| match ty.as_adt() {
185 Some(Adt::Enum(e)) => Some(e),
186 // For now we only handle expansion for a tuple of enums. Here
187 // we map non-enum items to None and rely on `collect` to
188 // convert Vec<Option<hir::Enum>> into Option<Vec<hir::Enum>>.
189 _ => None,
190 })
191 })
192 .collect()
193}
194
195fn build_pat(db: &RootDatabase, module: hir::Module, var: hir::Variant) -> Option<ast::Pat> {
196 let path = mod_path_to_ast(&module.find_use_path(db, ModuleDef::from(var))?);
197
198 // FIXME: use HIR for this; it doesn't currently expose struct vs. tuple vs. unit variants though
199 let pat: ast::Pat = match var.source(db)?.value.kind() {
200 ast::StructKind::Tuple(field_list) => {
201 let pats = iter::repeat(make::wildcard_pat().into()).take(field_list.fields().count());
202 make::tuple_struct_pat(path, pats).into()
203 }
204 ast::StructKind::Record(field_list) => {
205 let pats = field_list.fields().map(|f| make::ident_pat(f.name().unwrap()).into());
206 make::record_pat(path, pats).into()
207 }
208 ast::StructKind::Unit => make::path_pat(path),
209 };
210
211 Some(pat)
212}
213
214#[cfg(test)]
215mod tests {
216 use ide_db::helpers::FamousDefs;
217 use test_utils::mark;
218
219 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
220
221 use super::fill_match_arms;
222
223 #[test]
224 fn all_match_arms_provided() {
225 check_assist_not_applicable(
226 fill_match_arms,
227 r#"
228 enum A {
229 As,
230 Bs{x:i32, y:Option<i32>},
231 Cs(i32, Option<i32>),
232 }
233 fn main() {
234 match A::As$0 {
235 A::As,
236 A::Bs{x,y:Some(_)} => {}
237 A::Cs(_, Some(_)) => {}
238 }
239 }
240 "#,
241 );
242 }
243
244 #[test]
245 fn tuple_of_non_enum() {
246 // for now this case is not handled, although it potentially could be
247 // in the future
248 check_assist_not_applicable(
249 fill_match_arms,
250 r#"
251 fn main() {
252 match (0, false)$0 {
253 }
254 }
255 "#,
256 );
257 }
258
259 #[test]
260 fn partial_fill_record_tuple() {
261 check_assist(
262 fill_match_arms,
263 r#"
264 enum A {
265 As,
266 Bs { x: i32, y: Option<i32> },
267 Cs(i32, Option<i32>),
268 }
269 fn main() {
270 match A::As$0 {
271 A::Bs { x, y: Some(_) } => {}
272 A::Cs(_, Some(_)) => {}
273 }
274 }
275 "#,
276 r#"
277 enum A {
278 As,
279 Bs { x: i32, y: Option<i32> },
280 Cs(i32, Option<i32>),
281 }
282 fn main() {
283 match A::As {
284 A::Bs { x, y: Some(_) } => {}
285 A::Cs(_, Some(_)) => {}
286 $0A::As => {}
287 }
288 }
289 "#,
290 );
291 }
292
293 #[test]
294 fn partial_fill_or_pat() {
295 check_assist(
296 fill_match_arms,
297 r#"
298enum A { As, Bs, Cs(Option<i32>) }
299fn main() {
300 match A::As$0 {
301 A::Cs(_) | A::Bs => {}
302 }
303}
304"#,
305 r#"
306enum A { As, Bs, Cs(Option<i32>) }
307fn main() {
308 match A::As {
309 A::Cs(_) | A::Bs => {}
310 $0A::As => {}
311 }
312}
313"#,
314 );
315 }
316
317 #[test]
318 fn partial_fill() {
319 check_assist(
320 fill_match_arms,
321 r#"
322enum A { As, Bs, Cs, Ds(String), Es(B) }
323enum B { Xs, Ys }
324fn main() {
325 match A::As$0 {
326 A::Bs if 0 < 1 => {}
327 A::Ds(_value) => { let x = 1; }
328 A::Es(B::Xs) => (),
329 }
330}
331"#,
332 r#"
333enum A { As, Bs, Cs, Ds(String), Es(B) }
334enum B { Xs, Ys }
335fn main() {
336 match A::As {
337 A::Bs if 0 < 1 => {}
338 A::Ds(_value) => { let x = 1; }
339 A::Es(B::Xs) => (),
340 $0A::As => {}
341 A::Cs => {}
342 }
343}
344"#,
345 );
346 }
347
348 #[test]
349 fn partial_fill_bind_pat() {
350 check_assist(
351 fill_match_arms,
352 r#"
353enum A { As, Bs, Cs(Option<i32>) }
354fn main() {
355 match A::As$0 {
356 A::As(_) => {}
357 a @ A::Bs(_) => {}
358 }
359}
360"#,
361 r#"
362enum A { As, Bs, Cs(Option<i32>) }
363fn main() {
364 match A::As {
365 A::As(_) => {}
366 a @ A::Bs(_) => {}
367 A::Cs(${0:_}) => {}
368 }
369}
370"#,
371 );
372 }
373
374 #[test]
375 fn fill_match_arms_empty_body() {
376 check_assist(
377 fill_match_arms,
378 r#"
379enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
380
381fn main() {
382 let a = A::As;
383 match a$0 {}
384}
385"#,
386 r#"
387enum A { As, Bs, Cs(String), Ds(String, String), Es { x: usize, y: usize } }
388
389fn main() {
390 let a = A::As;
391 match a {
392 $0A::As => {}
393 A::Bs => {}
394 A::Cs(_) => {}
395 A::Ds(_, _) => {}
396 A::Es { x, y } => {}
397 }
398}
399"#,
400 );
401 }
402
403 #[test]
404 fn fill_match_arms_tuple_of_enum() {
405 check_assist(
406 fill_match_arms,
407 r#"
408 enum A { One, Two }
409 enum B { One, Two }
410
411 fn main() {
412 let a = A::One;
413 let b = B::One;
414 match (a$0, b) {}
415 }
416 "#,
417 r#"
418 enum A { One, Two }
419 enum B { One, Two }
420
421 fn main() {
422 let a = A::One;
423 let b = B::One;
424 match (a, b) {
425 $0(A::One, B::One) => {}
426 (A::One, B::Two) => {}
427 (A::Two, B::One) => {}
428 (A::Two, B::Two) => {}
429 }
430 }
431 "#,
432 );
433 }
434
435 #[test]
436 fn fill_match_arms_tuple_of_enum_ref() {
437 check_assist(
438 fill_match_arms,
439 r#"
440 enum A { One, Two }
441 enum B { One, Two }
442
443 fn main() {
444 let a = A::One;
445 let b = B::One;
446 match (&a$0, &b) {}
447 }
448 "#,
449 r#"
450 enum A { One, Two }
451 enum B { One, Two }
452
453 fn main() {
454 let a = A::One;
455 let b = B::One;
456 match (&a, &b) {
457 $0(A::One, B::One) => {}
458 (A::One, B::Two) => {}
459 (A::Two, B::One) => {}
460 (A::Two, B::Two) => {}
461 }
462 }
463 "#,
464 );
465 }
466
467 #[test]
468 fn fill_match_arms_tuple_of_enum_partial() {
469 check_assist_not_applicable(
470 fill_match_arms,
471 r#"
472 enum A { One, Two }
473 enum B { One, Two }
474
475 fn main() {
476 let a = A::One;
477 let b = B::One;
478 match (a$0, b) {
479 (A::Two, B::One) => {}
480 }
481 }
482 "#,
483 );
484 }
485
486 #[test]
487 fn fill_match_arms_tuple_of_enum_not_applicable() {
488 check_assist_not_applicable(
489 fill_match_arms,
490 r#"
491 enum A { One, Two }
492 enum B { One, Two }
493
494 fn main() {
495 let a = A::One;
496 let b = B::One;
497 match (a$0, b) {
498 (A::Two, B::One) => {}
499 (A::One, B::One) => {}
500 (A::One, B::Two) => {}
501 (A::Two, B::Two) => {}
502 }
503 }
504 "#,
505 );
506 }
507
508 #[test]
509 fn fill_match_arms_single_element_tuple_of_enum() {
510 // For now we don't hande the case of a single element tuple, but
511 // we could handle this in the future if `make::tuple_pat` allowed
512 // creating a tuple with a single pattern.
513 check_assist_not_applicable(
514 fill_match_arms,
515 r#"
516 enum A { One, Two }
517
518 fn main() {
519 let a = A::One;
520 match (a$0, ) {
521 }
522 }
523 "#,
524 );
525 }
526
527 #[test]
528 fn test_fill_match_arm_refs() {
529 check_assist(
530 fill_match_arms,
531 r#"
532 enum A { As }
533
534 fn foo(a: &A) {
535 match a$0 {
536 }
537 }
538 "#,
539 r#"
540 enum A { As }
541
542 fn foo(a: &A) {
543 match a {
544 $0A::As => {}
545 }
546 }
547 "#,
548 );
549
550 check_assist(
551 fill_match_arms,
552 r#"
553 enum A {
554 Es { x: usize, y: usize }
555 }
556
557 fn foo(a: &mut A) {
558 match a$0 {
559 }
560 }
561 "#,
562 r#"
563 enum A {
564 Es { x: usize, y: usize }
565 }
566
567 fn foo(a: &mut A) {
568 match a {
569 $0A::Es { x, y } => {}
570 }
571 }
572 "#,
573 );
574 }
575
576 #[test]
577 fn fill_match_arms_target() {
578 check_assist_target(
579 fill_match_arms,
580 r#"
581 enum E { X, Y }
582
583 fn main() {
584 match E::X$0 {}
585 }
586 "#,
587 "match E::X {}",
588 );
589 }
590
591 #[test]
592 fn fill_match_arms_trivial_arm() {
593 check_assist(
594 fill_match_arms,
595 r#"
596 enum E { X, Y }
597
598 fn main() {
599 match E::X {
600 $0_ => {}
601 }
602 }
603 "#,
604 r#"
605 enum E { X, Y }
606
607 fn main() {
608 match E::X {
609 $0E::X => {}
610 E::Y => {}
611 }
612 }
613 "#,
614 );
615 }
616
617 #[test]
618 fn fill_match_arms_qualifies_path() {
619 check_assist(
620 fill_match_arms,
621 r#"
622 mod foo { pub enum E { X, Y } }
623 use foo::E::X;
624
625 fn main() {
626 match X {
627 $0
628 }
629 }
630 "#,
631 r#"
632 mod foo { pub enum E { X, Y } }
633 use foo::E::X;
634
635 fn main() {
636 match X {
637 $0X => {}
638 foo::E::Y => {}
639 }
640 }
641 "#,
642 );
643 }
644
645 #[test]
646 fn fill_match_arms_preserves_comments() {
647 check_assist(
648 fill_match_arms,
649 r#"
650 enum A { One, Two }
651 fn foo(a: A) {
652 match a {
653 // foo bar baz$0
654 A::One => {}
655 // This is where the rest should be
656 }
657 }
658 "#,
659 r#"
660 enum A { One, Two }
661 fn foo(a: A) {
662 match a {
663 // foo bar baz
664 A::One => {}
665 // This is where the rest should be
666 $0A::Two => {}
667 }
668 }
669 "#,
670 );
671 }
672
673 #[test]
674 fn fill_match_arms_preserves_comments_empty() {
675 check_assist(
676 fill_match_arms,
677 r#"
678 enum A { One, Two }
679 fn foo(a: A) {
680 match a {
681 // foo bar baz$0
682 }
683 }
684 "#,
685 r#"
686 enum A { One, Two }
687 fn foo(a: A) {
688 match a {
689 // foo bar baz
690 $0A::One => {}
691 A::Two => {}
692 }
693 }
694 "#,
695 );
696 }
697
698 #[test]
699 fn fill_match_arms_placeholder() {
700 check_assist(
701 fill_match_arms,
702 r#"
703 enum A { One, Two, }
704 fn foo(a: A) {
705 match a$0 {
706 _ => (),
707 }
708 }
709 "#,
710 r#"
711 enum A { One, Two, }
712 fn foo(a: A) {
713 match a {
714 $0A::One => {}
715 A::Two => {}
716 }
717 }
718 "#,
719 );
720 }
721
722 #[test]
723 fn option_order() {
724 mark::check!(option_order);
725 let before = r#"
726fn foo(opt: Option<i32>) {
727 match opt$0 {
728 }
729}
730"#;
731 let before = &format!("//- /main.rs crate:main deps:core{}{}", before, FamousDefs::FIXTURE);
732
733 check_assist(
734 fill_match_arms,
735 before,
736 r#"
737fn foo(opt: Option<i32>) {
738 match opt {
739 Some(${0:_}) => {}
740 None => {}
741 }
742}
743"#,
744 );
745 }
746}
diff --git a/crates/assists/src/handlers/fix_visibility.rs b/crates/assists/src/handlers/fix_visibility.rs
deleted file mode 100644
index 6c7824e55..000000000
--- a/crates/assists/src/handlers/fix_visibility.rs
+++ /dev/null
@@ -1,607 +0,0 @@
1use hir::{db::HirDatabase, HasSource, HasVisibility, PathResolution};
2use ide_db::base_db::FileId;
3use syntax::{
4 ast::{self, VisibilityOwner},
5 AstNode, TextRange, TextSize,
6};
7
8use crate::{utils::vis_offset, AssistContext, AssistId, AssistKind, Assists};
9
10// FIXME: this really should be a fix for diagnostic, rather than an assist.
11
12// Assist: fix_visibility
13//
14// Makes inaccessible item public.
15//
16// ```
17// mod m {
18// fn frobnicate() {}
19// }
20// fn main() {
21// m::frobnicate$0() {}
22// }
23// ```
24// ->
25// ```
26// mod m {
27// $0pub(crate) fn frobnicate() {}
28// }
29// fn main() {
30// m::frobnicate() {}
31// }
32// ```
33pub(crate) fn fix_visibility(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34 add_vis_to_referenced_module_def(acc, ctx)
35 .or_else(|| add_vis_to_referenced_record_field(acc, ctx))
36}
37
38fn add_vis_to_referenced_module_def(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let path: ast::Path = ctx.find_node_at_offset()?;
40 let path_res = ctx.sema.resolve_path(&path)?;
41 let def = match path_res {
42 PathResolution::Def(def) => def,
43 _ => return None,
44 };
45
46 let current_module = ctx.sema.scope(&path.syntax()).module()?;
47 let target_module = def.module(ctx.db())?;
48
49 let vis = target_module.visibility_of(ctx.db(), &def)?;
50 if vis.is_visible_from(ctx.db(), current_module.into()) {
51 return None;
52 };
53
54 let (offset, current_visibility, target, target_file, target_name) =
55 target_data_for_def(ctx.db(), def)?;
56
57 let missing_visibility =
58 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
59
60 let assist_label = match target_name {
61 None => format!("Change visibility to {}", missing_visibility),
62 Some(name) => format!("Change visibility of {} to {}", name, missing_visibility),
63 };
64
65 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
66 builder.edit_file(target_file);
67 match ctx.config.snippet_cap {
68 Some(cap) => match current_visibility {
69 Some(current_visibility) => builder.replace_snippet(
70 cap,
71 current_visibility.syntax().text_range(),
72 format!("$0{}", missing_visibility),
73 ),
74 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
75 },
76 None => match current_visibility {
77 Some(current_visibility) => {
78 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
79 }
80 None => builder.insert(offset, format!("{} ", missing_visibility)),
81 },
82 }
83 })
84}
85
86fn add_vis_to_referenced_record_field(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
87 let record_field: ast::RecordExprField = ctx.find_node_at_offset()?;
88 let (record_field_def, _) = ctx.sema.resolve_record_field(&record_field)?;
89
90 let current_module = ctx.sema.scope(record_field.syntax()).module()?;
91 let visibility = record_field_def.visibility(ctx.db());
92 if visibility.is_visible_from(ctx.db(), current_module.into()) {
93 return None;
94 }
95
96 let parent = record_field_def.parent_def(ctx.db());
97 let parent_name = parent.name(ctx.db());
98 let target_module = parent.module(ctx.db());
99
100 let in_file_source = record_field_def.source(ctx.db())?;
101 let (offset, current_visibility, target) = match in_file_source.value {
102 hir::FieldSource::Named(it) => {
103 let s = it.syntax();
104 (vis_offset(s), it.visibility(), s.text_range())
105 }
106 hir::FieldSource::Pos(it) => {
107 let s = it.syntax();
108 (vis_offset(s), it.visibility(), s.text_range())
109 }
110 };
111
112 let missing_visibility =
113 if current_module.krate() == target_module.krate() { "pub(crate)" } else { "pub" };
114 let target_file = in_file_source.file_id.original_file(ctx.db());
115
116 let target_name = record_field_def.name(ctx.db());
117 let assist_label =
118 format!("Change visibility of {}.{} to {}", parent_name, target_name, missing_visibility);
119
120 acc.add(AssistId("fix_visibility", AssistKind::QuickFix), assist_label, target, |builder| {
121 builder.edit_file(target_file);
122 match ctx.config.snippet_cap {
123 Some(cap) => match current_visibility {
124 Some(current_visibility) => builder.replace_snippet(
125 cap,
126 current_visibility.syntax().text_range(),
127 format!("$0{}", missing_visibility),
128 ),
129 None => builder.insert_snippet(cap, offset, format!("$0{} ", missing_visibility)),
130 },
131 None => match current_visibility {
132 Some(current_visibility) => {
133 builder.replace(current_visibility.syntax().text_range(), missing_visibility)
134 }
135 None => builder.insert(offset, format!("{} ", missing_visibility)),
136 },
137 }
138 })
139}
140
141fn target_data_for_def(
142 db: &dyn HirDatabase,
143 def: hir::ModuleDef,
144) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId, Option<hir::Name>)> {
145 fn offset_target_and_file_id<S, Ast>(
146 db: &dyn HirDatabase,
147 x: S,
148 ) -> Option<(TextSize, Option<ast::Visibility>, TextRange, FileId)>
149 where
150 S: HasSource<Ast = Ast>,
151 Ast: AstNode + ast::VisibilityOwner,
152 {
153 let source = x.source(db)?;
154 let in_file_syntax = source.syntax();
155 let file_id = in_file_syntax.file_id;
156 let syntax = in_file_syntax.value;
157 let current_visibility = source.value.visibility();
158 Some((
159 vis_offset(syntax),
160 current_visibility,
161 syntax.text_range(),
162 file_id.original_file(db.upcast()),
163 ))
164 }
165
166 let target_name;
167 let (offset, current_visibility, target, target_file) = match def {
168 hir::ModuleDef::Function(f) => {
169 target_name = Some(f.name(db));
170 offset_target_and_file_id(db, f)?
171 }
172 hir::ModuleDef::Adt(adt) => {
173 target_name = Some(adt.name(db));
174 match adt {
175 hir::Adt::Struct(s) => offset_target_and_file_id(db, s)?,
176 hir::Adt::Union(u) => offset_target_and_file_id(db, u)?,
177 hir::Adt::Enum(e) => offset_target_and_file_id(db, e)?,
178 }
179 }
180 hir::ModuleDef::Const(c) => {
181 target_name = c.name(db);
182 offset_target_and_file_id(db, c)?
183 }
184 hir::ModuleDef::Static(s) => {
185 target_name = s.name(db);
186 offset_target_and_file_id(db, s)?
187 }
188 hir::ModuleDef::Trait(t) => {
189 target_name = Some(t.name(db));
190 offset_target_and_file_id(db, t)?
191 }
192 hir::ModuleDef::TypeAlias(t) => {
193 target_name = Some(t.name(db));
194 offset_target_and_file_id(db, t)?
195 }
196 hir::ModuleDef::Module(m) => {
197 target_name = m.name(db);
198 let in_file_source = m.declaration_source(db)?;
199 let file_id = in_file_source.file_id.original_file(db.upcast());
200 let syntax = in_file_source.value.syntax();
201 (vis_offset(syntax), in_file_source.value.visibility(), syntax.text_range(), file_id)
202 }
203 // Enum variants can't be private, we can't modify builtin types
204 hir::ModuleDef::Variant(_) | hir::ModuleDef::BuiltinType(_) => return None,
205 };
206
207 Some((offset, current_visibility, target, target_file, target_name))
208}
209
210#[cfg(test)]
211mod tests {
212 use crate::tests::{check_assist, check_assist_not_applicable};
213
214 use super::*;
215
216 #[test]
217 fn fix_visibility_of_fn() {
218 check_assist(
219 fix_visibility,
220 r"mod foo { fn foo() {} }
221 fn main() { foo::foo$0() } ",
222 r"mod foo { $0pub(crate) fn foo() {} }
223 fn main() { foo::foo() } ",
224 );
225 check_assist_not_applicable(
226 fix_visibility,
227 r"mod foo { pub fn foo() {} }
228 fn main() { foo::foo$0() } ",
229 )
230 }
231
232 #[test]
233 fn fix_visibility_of_adt_in_submodule() {
234 check_assist(
235 fix_visibility,
236 r"mod foo { struct Foo; }
237 fn main() { foo::Foo$0 } ",
238 r"mod foo { $0pub(crate) struct Foo; }
239 fn main() { foo::Foo } ",
240 );
241 check_assist_not_applicable(
242 fix_visibility,
243 r"mod foo { pub struct Foo; }
244 fn main() { foo::Foo$0 } ",
245 );
246 check_assist(
247 fix_visibility,
248 r"mod foo { enum Foo; }
249 fn main() { foo::Foo$0 } ",
250 r"mod foo { $0pub(crate) enum Foo; }
251 fn main() { foo::Foo } ",
252 );
253 check_assist_not_applicable(
254 fix_visibility,
255 r"mod foo { pub enum Foo; }
256 fn main() { foo::Foo$0 } ",
257 );
258 check_assist(
259 fix_visibility,
260 r"mod foo { union Foo; }
261 fn main() { foo::Foo$0 } ",
262 r"mod foo { $0pub(crate) union Foo; }
263 fn main() { foo::Foo } ",
264 );
265 check_assist_not_applicable(
266 fix_visibility,
267 r"mod foo { pub union Foo; }
268 fn main() { foo::Foo$0 } ",
269 );
270 }
271
272 #[test]
273 fn fix_visibility_of_adt_in_other_file() {
274 check_assist(
275 fix_visibility,
276 r"
277//- /main.rs
278mod foo;
279fn main() { foo::Foo$0 }
280
281//- /foo.rs
282struct Foo;
283",
284 r"$0pub(crate) struct Foo;
285",
286 );
287 }
288
289 #[test]
290 fn fix_visibility_of_struct_field() {
291 check_assist(
292 fix_visibility,
293 r"mod foo { pub struct Foo { bar: (), } }
294 fn main() { foo::Foo { $0bar: () }; } ",
295 r"mod foo { pub struct Foo { $0pub(crate) bar: (), } }
296 fn main() { foo::Foo { bar: () }; } ",
297 );
298 check_assist(
299 fix_visibility,
300 r"
301//- /lib.rs
302mod foo;
303fn main() { foo::Foo { $0bar: () }; }
304//- /foo.rs
305pub struct Foo { bar: () }
306",
307 r"pub struct Foo { $0pub(crate) bar: () }
308",
309 );
310 check_assist_not_applicable(
311 fix_visibility,
312 r"mod foo { pub struct Foo { pub bar: (), } }
313 fn main() { foo::Foo { $0bar: () }; } ",
314 );
315 check_assist_not_applicable(
316 fix_visibility,
317 r"
318//- /lib.rs
319mod foo;
320fn main() { foo::Foo { $0bar: () }; }
321//- /foo.rs
322pub struct Foo { pub bar: () }
323",
324 );
325 }
326
327 #[test]
328 fn fix_visibility_of_enum_variant_field() {
329 // Enum variants, as well as their fields, always get the enum's visibility. In fact, rustc
330 // rejects any visibility specifiers on them, so this assist should never fire on them.
331 check_assist_not_applicable(
332 fix_visibility,
333 r"mod foo { pub enum Foo { Bar { bar: () } } }
334 fn main() { foo::Foo::Bar { $0bar: () }; } ",
335 );
336 check_assist_not_applicable(
337 fix_visibility,
338 r"
339//- /lib.rs
340mod foo;
341fn main() { foo::Foo::Bar { $0bar: () }; }
342//- /foo.rs
343pub enum Foo { Bar { 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 { $0bar: () }; } ",
350 );
351 check_assist_not_applicable(
352 fix_visibility,
353 r"
354//- /lib.rs
355mod foo;
356fn main() { foo::Foo { $0bar: () }; }
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 { $0bar: () }; } ",
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 { $0bar: () }; }
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 { $0bar: () }; } ",
390 );
391 check_assist_not_applicable(
392 fix_visibility,
393 r"
394//- /lib.rs
395mod foo;
396fn main() { foo::Foo { $0bar: () }; }
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$0 } ",
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$0 } ",
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$0 } ",
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$0 } ",
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::$0Foo; } ",
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$0; } ",
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$0; } ",
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$0; } ",
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$0::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$0::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$0::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$0::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$0>::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$0
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$0
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 { $0bar: () };
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$0
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/assists/src/handlers/flip_binexpr.rs b/crates/assists/src/handlers/flip_binexpr.rs
deleted file mode 100644
index 209e5d43c..000000000
--- a/crates/assists/src/handlers/flip_binexpr.rs
+++ /dev/null
@@ -1,134 +0,0 @@
1use 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 +$0 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 ==$0 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 +=$0 2 }")
86 }
87
88 #[test]
89 fn flip_binexpr_works_for_eq() {
90 check_assist(flip_binexpr, "fn f() { let res = 1 ==$0 2; }", "fn f() { let res = 2 == 1; }")
91 }
92
93 #[test]
94 fn flip_binexpr_works_for_gt() {
95 check_assist(flip_binexpr, "fn f() { let res = 1 >$0 2; }", "fn f() { let res = 2 < 1; }")
96 }
97
98 #[test]
99 fn flip_binexpr_works_for_lteq() {
100 check_assist(flip_binexpr, "fn f() { let res = 1 <=$0 2; }", "fn f() { let res = 2 >= 1; }")
101 }
102
103 #[test]
104 fn flip_binexpr_works_for_complex_expr() {
105 check_assist(
106 flip_binexpr,
107 "fn f() { let res = (1 + 1) ==$0 (2 + 2); }",
108 "fn f() { let res = (2 + 2) == (1 + 1); }",
109 )
110 }
111
112 #[test]
113 fn flip_binexpr_works_inside_match() {
114 check_assist(
115 flip_binexpr,
116 r#"
117 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
118 match other.downcast_ref::<Self>() {
119 None => false,
120 Some(it) => it ==$0 self,
121 }
122 }
123 "#,
124 r#"
125 fn dyn_eq(&self, other: &dyn Diagnostic) -> bool {
126 match other.downcast_ref::<Self>() {
127 None => false,
128 Some(it) => self == it,
129 }
130 }
131 "#,
132 )
133 }
134}
diff --git a/crates/assists/src/handlers/flip_comma.rs b/crates/assists/src/handlers/flip_comma.rs
deleted file mode 100644
index 18cf64a34..000000000
--- a/crates/assists/src/handlers/flip_comma.rs
+++ /dev/null
@@ -1,84 +0,0 @@
1use 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),$0 (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_syntax_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 r#"fn foo(x: i32,$0 y: Result<(), ()>) {}"#,
53 r#"fn foo(y: Result<(), ()>, x: i32) {}"#,
54 )
55 }
56
57 #[test]
58 fn flip_comma_target() {
59 check_assist_target(flip_comma, r#"fn foo(x: i32,$0 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,$0 \
72 }",
73 ",",
74 );
75
76 check_assist_target(
77 flip_comma,
78 "pub struct Test { \
79 foo: usize,$0 \
80 }",
81 ",",
82 );
83 }
84}
diff --git a/crates/assists/src/handlers/flip_trait_bound.rs b/crates/assists/src/handlers/flip_trait_bound.rs
deleted file mode 100644
index d419d263e..000000000
--- a/crates/assists/src/handlers/flip_trait_bound.rs
+++ /dev/null
@@ -1,121 +0,0 @@
1use 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 +$0 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_syntax_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 $0+ 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: $0A { }")
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 $0+ 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 +$0 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 $0+ 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 +$0 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 $0+ '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> $0+ 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 +$0 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/assists/src/handlers/generate_default_from_enum_variant.rs b/crates/assists/src/handlers/generate_default_from_enum_variant.rs
deleted file mode 100644
index 6a2ab9596..000000000
--- a/crates/assists/src/handlers/generate_default_from_enum_variant.rs
+++ /dev/null
@@ -1,175 +0,0 @@
1use ide_db::helpers::FamousDefs;
2use ide_db::RootDatabase;
3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark;
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: generate_default_from_enum_variant
9//
10// Adds a Default impl for an enum using a variant.
11//
12// ```
13// enum Version {
14// Undefined,
15// Minor$0,
16// Major,
17// }
18// ```
19// ->
20// ```
21// enum Version {
22// Undefined,
23// Minor,
24// Major,
25// }
26//
27// impl Default for Version {
28// fn default() -> Self {
29// Self::Minor
30// }
31// }
32// ```
33pub(crate) fn generate_default_from_enum_variant(
34 acc: &mut Assists,
35 ctx: &AssistContext,
36) -> Option<()> {
37 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
38 let variant_name = variant.name()?;
39 let enum_name = variant.parent_enum().name()?;
40 if !matches!(variant.kind(), ast::StructKind::Unit) {
41 mark::hit!(test_gen_default_on_non_unit_variant_not_implemented);
42 return None;
43 }
44
45 if existing_default_impl(&ctx.sema, &variant).is_some() {
46 mark::hit!(test_gen_default_impl_already_exists);
47 return None;
48 }
49
50 let target = variant.syntax().text_range();
51 acc.add(
52 AssistId("generate_default_from_enum_variant", AssistKind::Generate),
53 "Generate `Default` impl from this enum variant",
54 target,
55 |edit| {
56 let start_offset = variant.parent_enum().syntax().text_range().end();
57 let buf = format!(
58 r#"
59
60impl Default for {0} {{
61 fn default() -> Self {{
62 Self::{1}
63 }}
64}}"#,
65 enum_name, variant_name
66 );
67 edit.insert(start_offset, buf);
68 },
69 )
70}
71
72fn existing_default_impl(
73 sema: &'_ hir::Semantics<'_, RootDatabase>,
74 variant: &ast::Variant,
75) -> Option<()> {
76 let variant = sema.to_def(variant)?;
77 let enum_ = variant.parent_enum(sema.db);
78 let krate = enum_.module(sema.db).krate();
79
80 let default_trait = FamousDefs(sema, Some(krate)).core_default_Default()?;
81 let enum_type = enum_.ty(sema.db);
82
83 if enum_type.impls_trait(sema.db, default_trait, &[]) {
84 Some(())
85 } else {
86 None
87 }
88}
89
90#[cfg(test)]
91mod tests {
92 use test_utils::mark;
93
94 use crate::tests::{check_assist, check_assist_not_applicable};
95
96 use super::*;
97
98 fn check_not_applicable(ra_fixture: &str) {
99 let fixture =
100 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
101 check_assist_not_applicable(generate_default_from_enum_variant, &fixture)
102 }
103
104 #[test]
105 fn test_generate_default_from_variant() {
106 check_assist(
107 generate_default_from_enum_variant,
108 r#"
109enum Variant {
110 Undefined,
111 Minor$0,
112 Major,
113}"#,
114 r#"enum Variant {
115 Undefined,
116 Minor,
117 Major,
118}
119
120impl Default for Variant {
121 fn default() -> Self {
122 Self::Minor
123 }
124}"#,
125 );
126 }
127
128 #[test]
129 fn test_generate_default_already_implemented() {
130 mark::check!(test_gen_default_impl_already_exists);
131 check_not_applicable(
132 r#"
133enum Variant {
134 Undefined,
135 Minor$0,
136 Major,
137}
138
139impl Default for Variant {
140 fn default() -> Self {
141 Self::Minor
142 }
143}"#,
144 );
145 }
146
147 #[test]
148 fn test_add_from_impl_no_element() {
149 mark::check!(test_gen_default_on_non_unit_variant_not_implemented);
150 check_not_applicable(
151 r#"
152enum Variant {
153 Undefined,
154 Minor(u32)$0,
155 Major,
156}"#,
157 );
158 }
159
160 #[test]
161 fn test_generate_default_from_variant_with_one_variant() {
162 check_assist(
163 generate_default_from_enum_variant,
164 r#"enum Variant { Undefi$0ned }"#,
165 r#"
166enum Variant { Undefined }
167
168impl Default for Variant {
169 fn default() -> Self {
170 Self::Undefined
171 }
172}"#,
173 );
174 }
175}
diff --git a/crates/assists/src/handlers/generate_derive.rs b/crates/assists/src/handlers/generate_derive.rs
deleted file mode 100644
index f876b7684..000000000
--- a/crates/assists/src/handlers/generate_derive.rs
+++ /dev/null
@@ -1,132 +0,0 @@
1use 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,$0
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, $0}",
80 "#[derive($0)]\nstruct Foo { a: i32, }",
81 );
82 check_assist(
83 generate_derive,
84 "struct Foo { $0 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$0, }",
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$0, }
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$0, }
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/assists/src/handlers/generate_from_impl_for_enum.rs b/crates/assists/src/handlers/generate_from_impl_for_enum.rs
deleted file mode 100644
index d9af6ab11..000000000
--- a/crates/assists/src/handlers/generate_from_impl_for_enum.rs
+++ /dev/null
@@ -1,201 +0,0 @@
1use ide_db::helpers::FamousDefs;
2use ide_db::RootDatabase;
3use syntax::ast::{self, AstNode, NameOwner};
4use test_utils::mark;
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: generate_from_impl_for_enum
9//
10// Adds a From impl for an enum variant with one tuple field.
11//
12// ```
13// enum A { $0One(u32) }
14// ```
15// ->
16// ```
17// enum A { One(u32) }
18//
19// impl From<u32> for A {
20// fn from(v: u32) -> Self {
21// A::One(v)
22// }
23// }
24// ```
25pub(crate) fn generate_from_impl_for_enum(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let variant = ctx.find_node_at_offset::<ast::Variant>()?;
27 let variant_name = variant.name()?;
28 let enum_name = variant.parent_enum().name()?;
29 let field_list = match variant.kind() {
30 ast::StructKind::Tuple(field_list) => field_list,
31 _ => return None,
32 };
33 if field_list.fields().count() != 1 {
34 return None;
35 }
36 let field_type = field_list.fields().next()?.ty()?;
37 let path = match field_type {
38 ast::Type::PathType(it) => it,
39 _ => return None,
40 };
41
42 if existing_from_impl(&ctx.sema, &variant).is_some() {
43 mark::hit!(test_add_from_impl_already_exists);
44 return None;
45 }
46
47 let target = variant.syntax().text_range();
48 acc.add(
49 AssistId("generate_from_impl_for_enum", AssistKind::Generate),
50 "Generate `From` impl for this enum variant",
51 target,
52 |edit| {
53 let start_offset = variant.parent_enum().syntax().text_range().end();
54 let buf = format!(
55 r#"
56
57impl From<{0}> for {1} {{
58 fn from(v: {0}) -> Self {{
59 {1}::{2}(v)
60 }}
61}}"#,
62 path.syntax(),
63 enum_name,
64 variant_name
65 );
66 edit.insert(start_offset, buf);
67 },
68 )
69}
70
71fn existing_from_impl(
72 sema: &'_ hir::Semantics<'_, RootDatabase>,
73 variant: &ast::Variant,
74) -> Option<()> {
75 let variant = sema.to_def(variant)?;
76 let enum_ = variant.parent_enum(sema.db);
77 let krate = enum_.module(sema.db).krate();
78
79 let from_trait = FamousDefs(sema, Some(krate)).core_convert_From()?;
80
81 let enum_type = enum_.ty(sema.db);
82
83 let wrapped_type = variant.fields(sema.db).get(0)?.signature_ty(sema.db);
84
85 if enum_type.impls_trait(sema.db, from_trait, &[wrapped_type]) {
86 Some(())
87 } else {
88 None
89 }
90}
91
92#[cfg(test)]
93mod tests {
94 use test_utils::mark;
95
96 use crate::tests::{check_assist, check_assist_not_applicable};
97
98 use super::*;
99
100 #[test]
101 fn test_generate_from_impl_for_enum() {
102 check_assist(
103 generate_from_impl_for_enum,
104 "enum A { $0One(u32) }",
105 r#"enum A { One(u32) }
106
107impl From<u32> for A {
108 fn from(v: u32) -> Self {
109 A::One(v)
110 }
111}"#,
112 );
113 }
114
115 #[test]
116 fn test_generate_from_impl_for_enum_complicated_path() {
117 check_assist(
118 generate_from_impl_for_enum,
119 r#"enum A { $0One(foo::bar::baz::Boo) }"#,
120 r#"enum A { One(foo::bar::baz::Boo) }
121
122impl From<foo::bar::baz::Boo> for A {
123 fn from(v: foo::bar::baz::Boo) -> Self {
124 A::One(v)
125 }
126}"#,
127 );
128 }
129
130 fn check_not_applicable(ra_fixture: &str) {
131 let fixture =
132 format!("//- /main.rs crate:main deps:core\n{}\n{}", ra_fixture, FamousDefs::FIXTURE);
133 check_assist_not_applicable(generate_from_impl_for_enum, &fixture)
134 }
135
136 #[test]
137 fn test_add_from_impl_no_element() {
138 check_not_applicable("enum A { $0One }");
139 }
140
141 #[test]
142 fn test_add_from_impl_more_than_one_element_in_tuple() {
143 check_not_applicable("enum A { $0One(u32, String) }");
144 }
145
146 #[test]
147 fn test_add_from_impl_struct_variant() {
148 check_not_applicable("enum A { $0One { x: u32 } }");
149 }
150
151 #[test]
152 fn test_add_from_impl_already_exists() {
153 mark::check!(test_add_from_impl_already_exists);
154 check_not_applicable(
155 r#"
156enum A { $0One(u32), }
157
158impl From<u32> for A {
159 fn from(v: u32) -> Self {
160 A::One(v)
161 }
162}
163"#,
164 );
165 }
166
167 #[test]
168 fn test_add_from_impl_different_variant_impl_exists() {
169 check_assist(
170 generate_from_impl_for_enum,
171 r#"enum A { $0One(u32), Two(String), }
172
173impl From<String> for A {
174 fn from(v: String) -> Self {
175 A::Two(v)
176 }
177}
178
179pub trait From<T> {
180 fn from(T) -> Self;
181}"#,
182 r#"enum A { One(u32), Two(String), }
183
184impl From<u32> for A {
185 fn from(v: u32) -> Self {
186 A::One(v)
187 }
188}
189
190impl From<String> for A {
191 fn from(v: String) -> Self {
192 A::Two(v)
193 }
194}
195
196pub trait From<T> {
197 fn from(T) -> Self;
198}"#,
199 );
200 }
201}
diff --git a/crates/assists/src/handlers/generate_function.rs b/crates/assists/src/handlers/generate_function.rs
deleted file mode 100644
index 06ac85f67..000000000
--- a/crates/assists/src/handlers/generate_function.rs
+++ /dev/null
@@ -1,1059 +0,0 @@
1use hir::HirDisplay;
2use ide_db::{base_db::FileId, helpers::SnippetCap};
3use rustc_hash::{FxHashMap, FxHashSet};
4use syntax::{
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make, ArgListOwner, AstNode, ModuleItemOwner,
9 },
10 SyntaxKind, SyntaxNode, TextSize,
11};
12
13use crate::{
14 utils::{render_snippet, Cursor},
15 AssistContext, AssistId, AssistKind, Assists,
16};
17
18// Assist: generate_function
19//
20// Adds a stub function with a signature matching the function under the cursor.
21//
22// ```
23// struct Baz;
24// fn baz() -> Baz { Baz }
25// fn foo() {
26// bar$0("", baz());
27// }
28//
29// ```
30// ->
31// ```
32// struct Baz;
33// fn baz() -> Baz { Baz }
34// fn foo() {
35// bar("", baz());
36// }
37//
38// fn bar(arg: &str, baz: Baz) ${0:-> ()} {
39// todo!()
40// }
41//
42// ```
43pub(crate) fn generate_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
44 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
45 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
46 let path = path_expr.path()?;
47
48 if ctx.sema.resolve_path(&path).is_some() {
49 // The function call already resolves, no need to add a function
50 return None;
51 }
52
53 let target_module = match path.qualifier() {
54 Some(qualifier) => match ctx.sema.resolve_path(&qualifier) {
55 Some(hir::PathResolution::Def(hir::ModuleDef::Module(module))) => Some(module),
56 _ => return None,
57 },
58 None => None,
59 };
60
61 let function_builder = FunctionBuilder::from_call(&ctx, &call, &path, target_module)?;
62
63 let target = call.syntax().text_range();
64 acc.add(
65 AssistId("generate_function", AssistKind::Generate),
66 format!("Generate `{}` function", function_builder.fn_name),
67 target,
68 |builder| {
69 let function_template = function_builder.render();
70 builder.edit_file(function_template.file);
71 let new_fn = function_template.to_string(ctx.config.snippet_cap);
72 match ctx.config.snippet_cap {
73 Some(cap) => builder.insert_snippet(cap, function_template.insert_offset, new_fn),
74 None => builder.insert(function_template.insert_offset, new_fn),
75 }
76 },
77 )
78}
79
80struct FunctionTemplate {
81 insert_offset: TextSize,
82 leading_ws: String,
83 fn_def: ast::Fn,
84 ret_type: ast::RetType,
85 trailing_ws: String,
86 file: FileId,
87}
88
89impl FunctionTemplate {
90 fn to_string(&self, cap: Option<SnippetCap>) -> String {
91 let f = match cap {
92 Some(cap) => {
93 render_snippet(cap, self.fn_def.syntax(), Cursor::Replace(self.ret_type.syntax()))
94 }
95 None => self.fn_def.to_string(),
96 };
97 format!("{}{}{}", self.leading_ws, f, self.trailing_ws)
98 }
99}
100
101struct FunctionBuilder {
102 target: GeneratedFunctionTarget,
103 fn_name: ast::Name,
104 type_params: Option<ast::GenericParamList>,
105 params: ast::ParamList,
106 file: FileId,
107 needs_pub: bool,
108}
109
110impl FunctionBuilder {
111 /// Prepares a generated function that matches `call`.
112 /// The function is generated in `target_module` or next to `call`
113 fn from_call(
114 ctx: &AssistContext,
115 call: &ast::CallExpr,
116 path: &ast::Path,
117 target_module: Option<hir::Module>,
118 ) -> Option<Self> {
119 let mut file = ctx.frange.file_id;
120 let target = match &target_module {
121 Some(target_module) => {
122 let module_source = target_module.definition_source(ctx.db());
123 let (in_file, target) = next_space_for_fn_in_module(ctx.sema.db, &module_source)?;
124 file = in_file;
125 target
126 }
127 None => next_space_for_fn_after_call_site(&call)?,
128 };
129 let needs_pub = target_module.is_some();
130 let target_module = target_module.or_else(|| ctx.sema.scope(target.syntax()).module())?;
131 let fn_name = fn_name(&path)?;
132 let (type_params, params) = fn_args(ctx, target_module, &call)?;
133
134 Some(Self { target, fn_name, type_params, params, file, needs_pub })
135 }
136
137 fn render(self) -> FunctionTemplate {
138 let placeholder_expr = make::expr_todo();
139 let fn_body = make::block_expr(vec![], Some(placeholder_expr));
140 let visibility = if self.needs_pub { Some(make::visibility_pub_crate()) } else { None };
141 let mut fn_def = make::fn_(
142 visibility,
143 self.fn_name,
144 self.type_params,
145 self.params,
146 fn_body,
147 Some(make::ret_type(make::ty_unit())),
148 );
149 let leading_ws;
150 let trailing_ws;
151
152 let insert_offset = match self.target {
153 GeneratedFunctionTarget::BehindItem(it) => {
154 let indent = IndentLevel::from_node(&it);
155 leading_ws = format!("\n\n{}", indent);
156 fn_def = fn_def.indent(indent);
157 trailing_ws = String::new();
158 it.text_range().end()
159 }
160 GeneratedFunctionTarget::InEmptyItemList(it) => {
161 let indent = IndentLevel::from_node(it.syntax());
162 leading_ws = format!("\n{}", indent + 1);
163 fn_def = fn_def.indent(indent + 1);
164 trailing_ws = format!("\n{}", indent);
165 it.syntax().text_range().start() + TextSize::of('{')
166 }
167 };
168
169 FunctionTemplate {
170 insert_offset,
171 leading_ws,
172 ret_type: fn_def.ret_type().unwrap(),
173 fn_def,
174 trailing_ws,
175 file: self.file,
176 }
177 }
178}
179
180enum GeneratedFunctionTarget {
181 BehindItem(SyntaxNode),
182 InEmptyItemList(ast::ItemList),
183}
184
185impl GeneratedFunctionTarget {
186 fn syntax(&self) -> &SyntaxNode {
187 match self {
188 GeneratedFunctionTarget::BehindItem(it) => it,
189 GeneratedFunctionTarget::InEmptyItemList(it) => it.syntax(),
190 }
191 }
192}
193
194fn fn_name(call: &ast::Path) -> Option<ast::Name> {
195 let name = call.segment()?.syntax().to_string();
196 Some(make::name(&name))
197}
198
199/// Computes the type variables and arguments required for the generated function
200fn fn_args(
201 ctx: &AssistContext,
202 target_module: hir::Module,
203 call: &ast::CallExpr,
204) -> Option<(Option<ast::GenericParamList>, ast::ParamList)> {
205 let mut arg_names = Vec::new();
206 let mut arg_types = Vec::new();
207 for arg in call.arg_list()?.args() {
208 arg_names.push(match fn_arg_name(&arg) {
209 Some(name) => name,
210 None => String::from("arg"),
211 });
212 arg_types.push(match fn_arg_type(ctx, target_module, &arg) {
213 Some(ty) => ty,
214 None => String::from("()"),
215 });
216 }
217 deduplicate_arg_names(&mut arg_names);
218 let params = arg_names.into_iter().zip(arg_types).map(|(name, ty)| make::param(name, ty));
219 Some((None, make::param_list(params)))
220}
221
222/// Makes duplicate argument names unique by appending incrementing numbers.
223///
224/// ```
225/// let mut names: Vec<String> =
226/// vec!["foo".into(), "foo".into(), "bar".into(), "baz".into(), "bar".into()];
227/// deduplicate_arg_names(&mut names);
228/// let expected: Vec<String> =
229/// vec!["foo_1".into(), "foo_2".into(), "bar_1".into(), "baz".into(), "bar_2".into()];
230/// assert_eq!(names, expected);
231/// ```
232fn deduplicate_arg_names(arg_names: &mut Vec<String>) {
233 let arg_name_counts = arg_names.iter().fold(FxHashMap::default(), |mut m, name| {
234 *m.entry(name).or_insert(0) += 1;
235 m
236 });
237 let duplicate_arg_names: FxHashSet<String> = arg_name_counts
238 .into_iter()
239 .filter(|(_, count)| *count >= 2)
240 .map(|(name, _)| name.clone())
241 .collect();
242
243 let mut counter_per_name = FxHashMap::default();
244 for arg_name in arg_names.iter_mut() {
245 if duplicate_arg_names.contains(arg_name) {
246 let counter = counter_per_name.entry(arg_name.clone()).or_insert(1);
247 arg_name.push('_');
248 arg_name.push_str(&counter.to_string());
249 *counter += 1;
250 }
251 }
252}
253
254fn fn_arg_name(fn_arg: &ast::Expr) -> Option<String> {
255 match fn_arg {
256 ast::Expr::CastExpr(cast_expr) => fn_arg_name(&cast_expr.expr()?),
257 _ => Some(
258 fn_arg
259 .syntax()
260 .descendants()
261 .filter(|d| ast::NameRef::can_cast(d.kind()))
262 .last()?
263 .to_string(),
264 ),
265 }
266}
267
268fn fn_arg_type(
269 ctx: &AssistContext,
270 target_module: hir::Module,
271 fn_arg: &ast::Expr,
272) -> Option<String> {
273 let ty = ctx.sema.type_of_expr(fn_arg)?;
274 if ty.is_unknown() {
275 return None;
276 }
277
278 if let Ok(rendered) = ty.display_source_code(ctx.db(), target_module.into()) {
279 Some(rendered)
280 } else {
281 None
282 }
283}
284
285/// Returns the position inside the current mod or file
286/// directly after the current block
287/// We want to write the generated function directly after
288/// fns, impls or macro calls, but inside mods
289fn next_space_for_fn_after_call_site(expr: &ast::CallExpr) -> Option<GeneratedFunctionTarget> {
290 let mut ancestors = expr.syntax().ancestors().peekable();
291 let mut last_ancestor: Option<SyntaxNode> = None;
292 while let Some(next_ancestor) = ancestors.next() {
293 match next_ancestor.kind() {
294 SyntaxKind::SOURCE_FILE => {
295 break;
296 }
297 SyntaxKind::ITEM_LIST => {
298 if ancestors.peek().map(|a| a.kind()) == Some(SyntaxKind::MODULE) {
299 break;
300 }
301 }
302 _ => {}
303 }
304 last_ancestor = Some(next_ancestor);
305 }
306 last_ancestor.map(GeneratedFunctionTarget::BehindItem)
307}
308
309fn next_space_for_fn_in_module(
310 db: &dyn hir::db::AstDatabase,
311 module_source: &hir::InFile<hir::ModuleSource>,
312) -> Option<(FileId, GeneratedFunctionTarget)> {
313 let file = module_source.file_id.original_file(db);
314 let assist_item = match &module_source.value {
315 hir::ModuleSource::SourceFile(it) => {
316 if let Some(last_item) = it.items().last() {
317 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
318 } else {
319 GeneratedFunctionTarget::BehindItem(it.syntax().clone())
320 }
321 }
322 hir::ModuleSource::Module(it) => {
323 if let Some(last_item) = it.item_list().and_then(|it| it.items().last()) {
324 GeneratedFunctionTarget::BehindItem(last_item.syntax().clone())
325 } else {
326 GeneratedFunctionTarget::InEmptyItemList(it.item_list()?)
327 }
328 }
329 };
330 Some((file, assist_item))
331}
332
333#[cfg(test)]
334mod tests {
335 use crate::tests::{check_assist, check_assist_not_applicable};
336
337 use super::*;
338
339 #[test]
340 fn add_function_with_no_args() {
341 check_assist(
342 generate_function,
343 r"
344fn foo() {
345 bar$0();
346}
347",
348 r"
349fn foo() {
350 bar();
351}
352
353fn bar() ${0:-> ()} {
354 todo!()
355}
356",
357 )
358 }
359
360 #[test]
361 fn add_function_from_method() {
362 // This ensures that the function is correctly generated
363 // in the next outer mod or file
364 check_assist(
365 generate_function,
366 r"
367impl Foo {
368 fn foo() {
369 bar$0();
370 }
371}
372",
373 r"
374impl Foo {
375 fn foo() {
376 bar();
377 }
378}
379
380fn bar() ${0:-> ()} {
381 todo!()
382}
383",
384 )
385 }
386
387 #[test]
388 fn add_function_directly_after_current_block() {
389 // The new fn should not be created at the end of the file or module
390 check_assist(
391 generate_function,
392 r"
393fn foo1() {
394 bar$0();
395}
396
397fn foo2() {}
398",
399 r"
400fn foo1() {
401 bar();
402}
403
404fn bar() ${0:-> ()} {
405 todo!()
406}
407
408fn foo2() {}
409",
410 )
411 }
412
413 #[test]
414 fn add_function_with_no_args_in_same_module() {
415 check_assist(
416 generate_function,
417 r"
418mod baz {
419 fn foo() {
420 bar$0();
421 }
422}
423",
424 r"
425mod baz {
426 fn foo() {
427 bar();
428 }
429
430 fn bar() ${0:-> ()} {
431 todo!()
432 }
433}
434",
435 )
436 }
437
438 #[test]
439 fn add_function_with_function_call_arg() {
440 check_assist(
441 generate_function,
442 r"
443struct Baz;
444fn baz() -> Baz { todo!() }
445fn foo() {
446 bar$0(baz());
447}
448",
449 r"
450struct Baz;
451fn baz() -> Baz { todo!() }
452fn foo() {
453 bar(baz());
454}
455
456fn bar(baz: Baz) ${0:-> ()} {
457 todo!()
458}
459",
460 );
461 }
462
463 #[test]
464 fn add_function_with_method_call_arg() {
465 check_assist(
466 generate_function,
467 r"
468struct Baz;
469impl Baz {
470 fn foo(&self) -> Baz {
471 ba$0r(self.baz())
472 }
473 fn baz(&self) -> Baz {
474 Baz
475 }
476}
477",
478 r"
479struct Baz;
480impl Baz {
481 fn foo(&self) -> Baz {
482 bar(self.baz())
483 }
484 fn baz(&self) -> Baz {
485 Baz
486 }
487}
488
489fn bar(baz: Baz) ${0:-> ()} {
490 todo!()
491}
492",
493 )
494 }
495
496 #[test]
497 fn add_function_with_string_literal_arg() {
498 check_assist(
499 generate_function,
500 r#"
501fn foo() {
502 $0bar("bar")
503}
504"#,
505 r#"
506fn foo() {
507 bar("bar")
508}
509
510fn bar(arg: &str) ${0:-> ()} {
511 todo!()
512}
513"#,
514 )
515 }
516
517 #[test]
518 fn add_function_with_char_literal_arg() {
519 check_assist(
520 generate_function,
521 r#"
522fn foo() {
523 $0bar('x')
524}
525"#,
526 r#"
527fn foo() {
528 bar('x')
529}
530
531fn bar(arg: char) ${0:-> ()} {
532 todo!()
533}
534"#,
535 )
536 }
537
538 #[test]
539 fn add_function_with_int_literal_arg() {
540 check_assist(
541 generate_function,
542 r"
543fn foo() {
544 $0bar(42)
545}
546",
547 r"
548fn foo() {
549 bar(42)
550}
551
552fn bar(arg: i32) ${0:-> ()} {
553 todo!()
554}
555",
556 )
557 }
558
559 #[test]
560 fn add_function_with_cast_int_literal_arg() {
561 check_assist(
562 generate_function,
563 r"
564fn foo() {
565 $0bar(42 as u8)
566}
567",
568 r"
569fn foo() {
570 bar(42 as u8)
571}
572
573fn bar(arg: u8) ${0:-> ()} {
574 todo!()
575}
576",
577 )
578 }
579
580 #[test]
581 fn name_of_cast_variable_is_used() {
582 // Ensures that the name of the cast type isn't used
583 // in the generated function signature.
584 check_assist(
585 generate_function,
586 r"
587fn foo() {
588 let x = 42;
589 bar$0(x as u8)
590}
591",
592 r"
593fn foo() {
594 let x = 42;
595 bar(x as u8)
596}
597
598fn bar(x: u8) ${0:-> ()} {
599 todo!()
600}
601",
602 )
603 }
604
605 #[test]
606 fn add_function_with_variable_arg() {
607 check_assist(
608 generate_function,
609 r"
610fn foo() {
611 let worble = ();
612 $0bar(worble)
613}
614",
615 r"
616fn foo() {
617 let worble = ();
618 bar(worble)
619}
620
621fn bar(worble: ()) ${0:-> ()} {
622 todo!()
623}
624",
625 )
626 }
627
628 #[test]
629 fn add_function_with_impl_trait_arg() {
630 check_assist(
631 generate_function,
632 r"
633trait Foo {}
634fn foo() -> impl Foo {
635 todo!()
636}
637fn baz() {
638 $0bar(foo())
639}
640",
641 r"
642trait Foo {}
643fn foo() -> impl Foo {
644 todo!()
645}
646fn baz() {
647 bar(foo())
648}
649
650fn bar(foo: impl Foo) ${0:-> ()} {
651 todo!()
652}
653",
654 )
655 }
656
657 #[test]
658 fn borrowed_arg() {
659 check_assist(
660 generate_function,
661 r"
662struct Baz;
663fn baz() -> Baz { todo!() }
664
665fn foo() {
666 bar$0(&baz())
667}
668",
669 r"
670struct Baz;
671fn baz() -> Baz { todo!() }
672
673fn foo() {
674 bar(&baz())
675}
676
677fn bar(baz: &Baz) ${0:-> ()} {
678 todo!()
679}
680",
681 )
682 }
683
684 #[test]
685 fn add_function_with_qualified_path_arg() {
686 check_assist(
687 generate_function,
688 r"
689mod Baz {
690 pub struct Bof;
691 pub fn baz() -> Bof { Bof }
692}
693fn foo() {
694 $0bar(Baz::baz())
695}
696",
697 r"
698mod Baz {
699 pub struct Bof;
700 pub fn baz() -> Bof { Bof }
701}
702fn foo() {
703 bar(Baz::baz())
704}
705
706fn bar(baz: Baz::Bof) ${0:-> ()} {
707 todo!()
708}
709",
710 )
711 }
712
713 #[test]
714 #[ignore]
715 // FIXME fix printing the generics of a `Ty` to make this test pass
716 fn add_function_with_generic_arg() {
717 check_assist(
718 generate_function,
719 r"
720fn foo<T>(t: T) {
721 $0bar(t)
722}
723",
724 r"
725fn foo<T>(t: T) {
726 bar(t)
727}
728
729fn bar<T>(t: T) ${0:-> ()} {
730 todo!()
731}
732",
733 )
734 }
735
736 #[test]
737 #[ignore]
738 // FIXME Fix function type printing to make this test pass
739 fn add_function_with_fn_arg() {
740 check_assist(
741 generate_function,
742 r"
743struct Baz;
744impl Baz {
745 fn new() -> Self { Baz }
746}
747fn foo() {
748 $0bar(Baz::new);
749}
750",
751 r"
752struct Baz;
753impl Baz {
754 fn new() -> Self { Baz }
755}
756fn foo() {
757 bar(Baz::new);
758}
759
760fn bar(arg: fn() -> Baz) ${0:-> ()} {
761 todo!()
762}
763",
764 )
765 }
766
767 #[test]
768 #[ignore]
769 // FIXME Fix closure type printing to make this test pass
770 fn add_function_with_closure_arg() {
771 check_assist(
772 generate_function,
773 r"
774fn foo() {
775 let closure = |x: i64| x - 1;
776 $0bar(closure)
777}
778",
779 r"
780fn foo() {
781 let closure = |x: i64| x - 1;
782 bar(closure)
783}
784
785fn bar(closure: impl Fn(i64) -> i64) ${0:-> ()} {
786 todo!()
787}
788",
789 )
790 }
791
792 #[test]
793 fn unresolveable_types_default_to_unit() {
794 check_assist(
795 generate_function,
796 r"
797fn foo() {
798 $0bar(baz)
799}
800",
801 r"
802fn foo() {
803 bar(baz)
804}
805
806fn bar(baz: ()) ${0:-> ()} {
807 todo!()
808}
809",
810 )
811 }
812
813 #[test]
814 fn arg_names_dont_overlap() {
815 check_assist(
816 generate_function,
817 r"
818struct Baz;
819fn baz() -> Baz { Baz }
820fn foo() {
821 $0bar(baz(), baz())
822}
823",
824 r"
825struct Baz;
826fn baz() -> Baz { Baz }
827fn foo() {
828 bar(baz(), baz())
829}
830
831fn bar(baz_1: Baz, baz_2: Baz) ${0:-> ()} {
832 todo!()
833}
834",
835 )
836 }
837
838 #[test]
839 fn arg_name_counters_start_at_1_per_name() {
840 check_assist(
841 generate_function,
842 r#"
843struct Baz;
844fn baz() -> Baz { Baz }
845fn foo() {
846 $0bar(baz(), baz(), "foo", "bar")
847}
848"#,
849 r#"
850struct Baz;
851fn baz() -> Baz { Baz }
852fn foo() {
853 bar(baz(), baz(), "foo", "bar")
854}
855
856fn bar(baz_1: Baz, baz_2: Baz, arg_1: &str, arg_2: &str) ${0:-> ()} {
857 todo!()
858}
859"#,
860 )
861 }
862
863 #[test]
864 fn add_function_in_module() {
865 check_assist(
866 generate_function,
867 r"
868mod bar {}
869
870fn foo() {
871 bar::my_fn$0()
872}
873",
874 r"
875mod bar {
876 pub(crate) fn my_fn() ${0:-> ()} {
877 todo!()
878 }
879}
880
881fn foo() {
882 bar::my_fn()
883}
884",
885 )
886 }
887
888 #[test]
889 #[ignore]
890 // Ignored until local imports are supported.
891 // See https://github.com/rust-analyzer/rust-analyzer/issues/1165
892 fn qualified_path_uses_correct_scope() {
893 check_assist(
894 generate_function,
895 "
896mod foo {
897 pub struct Foo;
898}
899fn bar() {
900 use foo::Foo;
901 let foo = Foo;
902 baz$0(foo)
903}
904",
905 "
906mod foo {
907 pub struct Foo;
908}
909fn bar() {
910 use foo::Foo;
911 let foo = Foo;
912 baz(foo)
913}
914
915fn baz(foo: foo::Foo) ${0:-> ()} {
916 todo!()
917}
918",
919 )
920 }
921
922 #[test]
923 fn add_function_in_module_containing_other_items() {
924 check_assist(
925 generate_function,
926 r"
927mod bar {
928 fn something_else() {}
929}
930
931fn foo() {
932 bar::my_fn$0()
933}
934",
935 r"
936mod bar {
937 fn something_else() {}
938
939 pub(crate) fn my_fn() ${0:-> ()} {
940 todo!()
941 }
942}
943
944fn foo() {
945 bar::my_fn()
946}
947",
948 )
949 }
950
951 #[test]
952 fn add_function_in_nested_module() {
953 check_assist(
954 generate_function,
955 r"
956mod bar {
957 mod baz {}
958}
959
960fn foo() {
961 bar::baz::my_fn$0()
962}
963",
964 r"
965mod bar {
966 mod baz {
967 pub(crate) fn my_fn() ${0:-> ()} {
968 todo!()
969 }
970 }
971}
972
973fn foo() {
974 bar::baz::my_fn()
975}
976",
977 )
978 }
979
980 #[test]
981 fn add_function_in_another_file() {
982 check_assist(
983 generate_function,
984 r"
985//- /main.rs
986mod foo;
987
988fn main() {
989 foo::bar$0()
990}
991//- /foo.rs
992",
993 r"
994
995
996pub(crate) fn bar() ${0:-> ()} {
997 todo!()
998}",
999 )
1000 }
1001
1002 #[test]
1003 fn add_function_not_applicable_if_function_already_exists() {
1004 check_assist_not_applicable(
1005 generate_function,
1006 r"
1007fn foo() {
1008 bar$0();
1009}
1010
1011fn bar() {}
1012",
1013 )
1014 }
1015
1016 #[test]
1017 fn add_function_not_applicable_if_unresolved_variable_in_call_is_selected() {
1018 check_assist_not_applicable(
1019 // bar is resolved, but baz isn't.
1020 // The assist is only active if the cursor is on an unresolved path,
1021 // but the assist should only be offered if the path is a function call.
1022 generate_function,
1023 r"
1024fn foo() {
1025 bar(b$0az);
1026}
1027
1028fn bar(baz: ()) {}
1029",
1030 )
1031 }
1032
1033 #[test]
1034 #[ignore]
1035 fn create_method_with_no_args() {
1036 check_assist(
1037 generate_function,
1038 r"
1039struct Foo;
1040impl Foo {
1041 fn foo(&self) {
1042 self.bar()$0;
1043 }
1044}
1045 ",
1046 r"
1047struct Foo;
1048impl Foo {
1049 fn foo(&self) {
1050 self.bar();
1051 }
1052 fn bar(&self) {
1053 todo!();
1054 }
1055}
1056 ",
1057 )
1058 }
1059}
diff --git a/crates/assists/src/handlers/generate_impl.rs b/crates/assists/src/handlers/generate_impl.rs
deleted file mode 100644
index 9af45192b..000000000
--- a/crates/assists/src/handlers/generate_impl.rs
+++ /dev/null
@@ -1,148 +0,0 @@
1use itertools::Itertools;
2use stdx::format_to;
3use syntax::ast::{self, AstNode, AttrsOwner, GenericParamsOwner, NameOwner};
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: generate_impl
8//
9// Adds a new inherent impl for a type.
10//
11// ```
12// struct Ctx<T: Clone> {
13// data: T,$0
14// }
15// ```
16// ->
17// ```
18// struct Ctx<T: Clone> {
19// data: T,
20// }
21//
22// impl<T: Clone> Ctx<T> {
23// $0
24// }
25// ```
26pub(crate) fn generate_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let nominal = ctx.find_node_at_offset::<ast::AdtDef>()?;
28 let name = nominal.name()?;
29 let target = nominal.syntax().text_range();
30
31 acc.add(
32 AssistId("generate_impl", AssistKind::Generate),
33 format!("Generate impl for `{}`", name),
34 target,
35 |edit| {
36 let type_params = nominal.generic_param_list();
37 let start_offset = nominal.syntax().text_range().end();
38 let mut buf = String::new();
39 buf.push_str("\n\n");
40 nominal
41 .attrs()
42 .filter(|attr| {
43 attr.as_simple_call().map(|(name, _arg)| name == "cfg").unwrap_or(false)
44 })
45 .for_each(|attr| buf.push_str(format!("{}\n", attr.to_string()).as_str()));
46
47 buf.push_str("impl");
48 if let Some(type_params) = &type_params {
49 format_to!(buf, "{}", type_params.syntax());
50 }
51 buf.push_str(" ");
52 buf.push_str(name.text().as_str());
53 if let Some(type_params) = type_params {
54 let lifetime_params = type_params
55 .lifetime_params()
56 .filter_map(|it| it.lifetime())
57 .map(|it| it.text().clone());
58 let type_params = type_params
59 .type_params()
60 .filter_map(|it| it.name())
61 .map(|it| it.text().clone());
62
63 let generic_params = lifetime_params.chain(type_params).format(", ");
64 format_to!(buf, "<{}>", generic_params)
65 }
66 match ctx.config.snippet_cap {
67 Some(cap) => {
68 buf.push_str(" {\n $0\n}");
69 edit.insert_snippet(cap, start_offset, buf);
70 }
71 None => {
72 buf.push_str(" {\n}");
73 edit.insert(start_offset, buf);
74 }
75 }
76 },
77 )
78}
79
80#[cfg(test)]
81mod tests {
82 use crate::tests::{check_assist, check_assist_target};
83
84 use super::*;
85
86 #[test]
87 fn test_add_impl() {
88 check_assist(
89 generate_impl,
90 "struct Foo {$0}\n",
91 "struct Foo {}\n\nimpl Foo {\n $0\n}\n",
92 );
93 check_assist(
94 generate_impl,
95 "struct Foo<T: Clone> {$0}",
96 "struct Foo<T: Clone> {}\n\nimpl<T: Clone> Foo<T> {\n $0\n}",
97 );
98 check_assist(
99 generate_impl,
100 "struct Foo<'a, T: Foo<'a>> {$0}",
101 "struct Foo<'a, T: Foo<'a>> {}\n\nimpl<'a, T: Foo<'a>> Foo<'a, T> {\n $0\n}",
102 );
103 check_assist(
104 generate_impl,
105 r#"
106 #[cfg(feature = "foo")]
107 struct Foo<'a, T: Foo<'a>> {$0}"#,
108 r#"
109 #[cfg(feature = "foo")]
110 struct Foo<'a, T: Foo<'a>> {}
111
112 #[cfg(feature = "foo")]
113 impl<'a, T: Foo<'a>> Foo<'a, T> {
114 $0
115 }"#,
116 );
117
118 check_assist(
119 generate_impl,
120 r#"
121 #[cfg(not(feature = "foo"))]
122 struct Foo<'a, T: Foo<'a>> {$0}"#,
123 r#"
124 #[cfg(not(feature = "foo"))]
125 struct Foo<'a, T: Foo<'a>> {}
126
127 #[cfg(not(feature = "foo"))]
128 impl<'a, T: Foo<'a>> Foo<'a, T> {
129 $0
130 }"#,
131 );
132 }
133
134 #[test]
135 fn add_impl_target() {
136 check_assist_target(
137 generate_impl,
138 "
139struct SomeThingIrrelevant;
140/// Has a lifetime parameter
141struct Foo<'a, T: Foo<'a>> {$0}
142struct EvenMoreIrrelevant;
143",
144 "/// Has a lifetime parameter
145struct Foo<'a, T: Foo<'a>> {}",
146 );
147 }
148}
diff --git a/crates/assists/src/handlers/generate_new.rs b/crates/assists/src/handlers/generate_new.rs
deleted file mode 100644
index 5c52b2bc8..000000000
--- a/crates/assists/src/handlers/generate_new.rs
+++ /dev/null
@@ -1,421 +0,0 @@
1use hir::Adt;
2use itertools::Itertools;
3use stdx::format_to;
4use syntax::{
5 ast::{self, AstNode, GenericParamsOwner, NameOwner, StructKind, VisibilityOwner},
6 T,
7};
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: generate_new
12//
13// Adds a new inherent impl for a type.
14//
15// ```
16// struct Ctx<T: Clone> {
17// data: T,$0
18// }
19// ```
20// ->
21// ```
22// struct Ctx<T: Clone> {
23// data: T,
24// }
25//
26// impl<T: Clone> Ctx<T> {
27// fn $0new(data: T) -> Self { Self { data } }
28// }
29//
30// ```
31pub(crate) fn generate_new(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
32 let strukt = ctx.find_node_at_offset::<ast::Struct>()?;
33
34 // We want to only apply this to non-union structs with named fields
35 let field_list = match strukt.kind() {
36 StructKind::Record(named) => named,
37 _ => return None,
38 };
39
40 // Return early if we've found an existing new fn
41 let impl_def = find_struct_impl(&ctx, &strukt)?;
42
43 let target = strukt.syntax().text_range();
44 acc.add(AssistId("generate_new", AssistKind::Generate), "Generate `new`", target, |builder| {
45 let mut buf = String::with_capacity(512);
46
47 if impl_def.is_some() {
48 buf.push('\n');
49 }
50
51 let vis = strukt.visibility().map_or(String::new(), |v| format!("{} ", v));
52
53 let params = field_list
54 .fields()
55 .filter_map(|f| Some(format!("{}: {}", f.name()?.syntax(), f.ty()?.syntax())))
56 .format(", ");
57 let fields = field_list.fields().filter_map(|f| f.name()).format(", ");
58
59 format_to!(buf, " {}fn new({}) -> Self {{ Self {{ {} }} }}", vis, params, fields);
60
61 let start_offset = impl_def
62 .and_then(|impl_def| {
63 buf.push('\n');
64 let start = impl_def
65 .syntax()
66 .descendants_with_tokens()
67 .find(|t| t.kind() == T!['{'])?
68 .text_range()
69 .end();
70
71 Some(start)
72 })
73 .unwrap_or_else(|| {
74 buf = generate_impl_text(&strukt, &buf);
75 strukt.syntax().text_range().end()
76 });
77
78 match ctx.config.snippet_cap {
79 None => builder.insert(start_offset, buf),
80 Some(cap) => {
81 buf = buf.replace("fn new", "fn $0new");
82 builder.insert_snippet(cap, start_offset, buf);
83 }
84 }
85 })
86}
87
88// Generates the surrounding `impl Type { <code> }` including type and lifetime
89// parameters
90fn generate_impl_text(strukt: &ast::Struct, code: &str) -> String {
91 let type_params = strukt.generic_param_list();
92 let mut buf = String::with_capacity(code.len());
93 buf.push_str("\n\nimpl");
94 if let Some(type_params) = &type_params {
95 format_to!(buf, "{}", type_params.syntax());
96 }
97 buf.push_str(" ");
98 buf.push_str(strukt.name().unwrap().text().as_str());
99 if let Some(type_params) = type_params {
100 let lifetime_params = type_params
101 .lifetime_params()
102 .filter_map(|it| it.lifetime())
103 .map(|it| it.text().clone());
104 let type_params =
105 type_params.type_params().filter_map(|it| it.name()).map(|it| it.text().clone());
106 format_to!(buf, "<{}>", lifetime_params.chain(type_params).format(", "))
107 }
108
109 format_to!(buf, " {{\n{}\n}}\n", code);
110
111 buf
112}
113
114// Uses a syntax-driven approach to find any impl blocks for the struct that
115// exist within the module/file
116//
117// Returns `None` if we've found an existing `new` fn
118//
119// FIXME: change the new fn checking to a more semantic approach when that's more
120// viable (e.g. we process proc macros, etc)
121fn find_struct_impl(ctx: &AssistContext, strukt: &ast::Struct) -> Option<Option<ast::Impl>> {
122 let db = ctx.db();
123 let module = strukt.syntax().ancestors().find(|node| {
124 ast::Module::can_cast(node.kind()) || ast::SourceFile::can_cast(node.kind())
125 })?;
126
127 let struct_def = ctx.sema.to_def(strukt)?;
128
129 let block = module.descendants().filter_map(ast::Impl::cast).find_map(|impl_blk| {
130 let blk = ctx.sema.to_def(&impl_blk)?;
131
132 // FIXME: handle e.g. `struct S<T>; impl<U> S<U> {}`
133 // (we currently use the wrong type parameter)
134 // also we wouldn't want to use e.g. `impl S<u32>`
135 let same_ty = match blk.target_ty(db).as_adt() {
136 Some(def) => def == Adt::Struct(struct_def),
137 None => false,
138 };
139 let not_trait_impl = blk.target_trait(db).is_none();
140
141 if !(same_ty && not_trait_impl) {
142 None
143 } else {
144 Some(impl_blk)
145 }
146 });
147
148 if let Some(ref impl_blk) = block {
149 if has_new_fn(impl_blk) {
150 return None;
151 }
152 }
153
154 Some(block)
155}
156
157fn has_new_fn(imp: &ast::Impl) -> bool {
158 if let Some(il) = imp.assoc_item_list() {
159 for item in il.assoc_items() {
160 if let ast::AssocItem::Fn(f) = item {
161 if let Some(name) = f.name() {
162 if name.text().eq_ignore_ascii_case("new") {
163 return true;
164 }
165 }
166 }
167 }
168 }
169
170 false
171}
172
173#[cfg(test)]
174mod tests {
175 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
176
177 use super::*;
178
179 #[test]
180 #[rustfmt::skip]
181 fn test_generate_new() {
182 // Check output of generation
183 check_assist(
184 generate_new,
185"struct Foo {$0}",
186"struct Foo {}
187
188impl Foo {
189 fn $0new() -> Self { Self { } }
190}
191",
192 );
193 check_assist(
194 generate_new,
195"struct Foo<T: Clone> {$0}",
196"struct Foo<T: Clone> {}
197
198impl<T: Clone> Foo<T> {
199 fn $0new() -> Self { Self { } }
200}
201",
202 );
203 check_assist(
204 generate_new,
205"struct Foo<'a, T: Foo<'a>> {$0}",
206"struct Foo<'a, T: Foo<'a>> {}
207
208impl<'a, T: Foo<'a>> Foo<'a, T> {
209 fn $0new() -> Self { Self { } }
210}
211",
212 );
213 check_assist(
214 generate_new,
215"struct Foo { baz: String $0}",
216"struct Foo { baz: String }
217
218impl Foo {
219 fn $0new(baz: String) -> Self { Self { baz } }
220}
221",
222 );
223 check_assist(
224 generate_new,
225"struct Foo { baz: String, qux: Vec<i32> $0}",
226"struct Foo { baz: String, qux: Vec<i32> }
227
228impl Foo {
229 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
230}
231",
232 );
233
234 // Check that visibility modifiers don't get brought in for fields
235 check_assist(
236 generate_new,
237"struct Foo { pub baz: String, pub qux: Vec<i32> $0}",
238"struct Foo { pub baz: String, pub qux: Vec<i32> }
239
240impl Foo {
241 fn $0new(baz: String, qux: Vec<i32>) -> Self { Self { baz, qux } }
242}
243",
244 );
245
246 // Check that it reuses existing impls
247 check_assist(
248 generate_new,
249"struct Foo {$0}
250
251impl Foo {}
252",
253"struct Foo {}
254
255impl Foo {
256 fn $0new() -> Self { Self { } }
257}
258",
259 );
260 check_assist(
261 generate_new,
262"struct Foo {$0}
263
264impl Foo {
265 fn qux(&self) {}
266}
267",
268"struct Foo {}
269
270impl Foo {
271 fn $0new() -> Self { Self { } }
272
273 fn qux(&self) {}
274}
275",
276 );
277
278 check_assist(
279 generate_new,
280"struct Foo {$0}
281
282impl Foo {
283 fn qux(&self) {}
284 fn baz() -> i32 {
285 5
286 }
287}
288",
289"struct Foo {}
290
291impl Foo {
292 fn $0new() -> Self { Self { } }
293
294 fn qux(&self) {}
295 fn baz() -> i32 {
296 5
297 }
298}
299",
300 );
301
302 // Check visibility of new fn based on struct
303 check_assist(
304 generate_new,
305"pub struct Foo {$0}",
306"pub struct Foo {}
307
308impl Foo {
309 pub fn $0new() -> Self { Self { } }
310}
311",
312 );
313 check_assist(
314 generate_new,
315"pub(crate) struct Foo {$0}",
316"pub(crate) struct Foo {}
317
318impl Foo {
319 pub(crate) fn $0new() -> Self { Self { } }
320}
321",
322 );
323 }
324
325 #[test]
326 fn generate_new_not_applicable_if_fn_exists() {
327 check_assist_not_applicable(
328 generate_new,
329 "
330struct Foo {$0}
331
332impl Foo {
333 fn new() -> Self {
334 Self
335 }
336}",
337 );
338
339 check_assist_not_applicable(
340 generate_new,
341 "
342struct Foo {$0}
343
344impl Foo {
345 fn New() -> Self {
346 Self
347 }
348}",
349 );
350 }
351
352 #[test]
353 fn generate_new_target() {
354 check_assist_target(
355 generate_new,
356 "
357struct SomeThingIrrelevant;
358/// Has a lifetime parameter
359struct Foo<'a, T: Foo<'a>> {$0}
360struct EvenMoreIrrelevant;
361",
362 "/// Has a lifetime parameter
363struct Foo<'a, T: Foo<'a>> {}",
364 );
365 }
366
367 #[test]
368 fn test_unrelated_new() {
369 check_assist(
370 generate_new,
371 r##"
372pub struct AstId<N: AstNode> {
373 file_id: HirFileId,
374 file_ast_id: FileAstId<N>,
375}
376
377impl<N: AstNode> AstId<N> {
378 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
379 AstId { file_id, file_ast_id }
380 }
381}
382
383pub struct Source<T> {
384 pub file_id: HirFileId,$0
385 pub ast: T,
386}
387
388impl<T> Source<T> {
389 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
390 Source { file_id: self.file_id, ast: f(self.ast) }
391 }
392}
393"##,
394 r##"
395pub struct AstId<N: AstNode> {
396 file_id: HirFileId,
397 file_ast_id: FileAstId<N>,
398}
399
400impl<N: AstNode> AstId<N> {
401 pub fn new(file_id: HirFileId, file_ast_id: FileAstId<N>) -> AstId<N> {
402 AstId { file_id, file_ast_id }
403 }
404}
405
406pub struct Source<T> {
407 pub file_id: HirFileId,
408 pub ast: T,
409}
410
411impl<T> Source<T> {
412 pub fn $0new(file_id: HirFileId, ast: T) -> Self { Self { file_id, ast } }
413
414 pub fn map<F: FnOnce(T) -> U, U>(self, f: F) -> Source<U> {
415 Source { file_id: self.file_id, ast: f(self.ast) }
416 }
417}
418"##,
419 );
420 }
421}
diff --git a/crates/assists/src/handlers/infer_function_return_type.rs b/crates/assists/src/handlers/infer_function_return_type.rs
deleted file mode 100644
index 5279af1f3..000000000
--- a/crates/assists/src/handlers/infer_function_return_type.rs
+++ /dev/null
@@ -1,345 +0,0 @@
1use hir::HirDisplay;
2use syntax::{ast, AstNode, TextRange, TextSize};
3use test_utils::mark;
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: infer_function_return_type
8//
9// Adds the return type to a function or closure inferred from its tail expression if it doesn't have a return
10// type specified. This assists is useable in a functions or closures tail expression or return type position.
11//
12// ```
13// fn foo() { 4$02i32 }
14// ```
15// ->
16// ```
17// fn foo() -> i32 { 42i32 }
18// ```
19pub(crate) fn infer_function_return_type(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
20 let (fn_type, tail_expr, builder_edit_pos) = extract_tail(ctx)?;
21 let module = ctx.sema.scope(tail_expr.syntax()).module()?;
22 let ty = ctx.sema.type_of_expr(&tail_expr)?;
23 if ty.is_unit() {
24 return None;
25 }
26 let ty = ty.display_source_code(ctx.db(), module.into()).ok()?;
27
28 acc.add(
29 AssistId("infer_function_return_type", AssistKind::RefactorRewrite),
30 match fn_type {
31 FnType::Function => "Add this function's return type",
32 FnType::Closure { .. } => "Add this closure's return type",
33 },
34 tail_expr.syntax().text_range(),
35 |builder| {
36 match builder_edit_pos {
37 InsertOrReplace::Insert(insert_pos) => {
38 builder.insert(insert_pos, &format!("-> {} ", ty))
39 }
40 InsertOrReplace::Replace(text_range) => {
41 builder.replace(text_range, &format!("-> {}", ty))
42 }
43 }
44 if let FnType::Closure { wrap_expr: true } = fn_type {
45 mark::hit!(wrap_closure_non_block_expr);
46 // `|x| x` becomes `|x| -> T x` which is invalid, so wrap it in a block
47 builder.replace(tail_expr.syntax().text_range(), &format!("{{{}}}", tail_expr));
48 }
49 },
50 )
51}
52
53enum InsertOrReplace {
54 Insert(TextSize),
55 Replace(TextRange),
56}
57
58/// Check the potentially already specified return type and reject it or turn it into a builder command
59/// if allowed.
60fn ret_ty_to_action(ret_ty: Option<ast::RetType>, insert_pos: TextSize) -> Option<InsertOrReplace> {
61 match ret_ty {
62 Some(ret_ty) => match ret_ty.ty() {
63 Some(ast::Type::InferType(_)) | None => {
64 mark::hit!(existing_infer_ret_type);
65 mark::hit!(existing_infer_ret_type_closure);
66 Some(InsertOrReplace::Replace(ret_ty.syntax().text_range()))
67 }
68 _ => {
69 mark::hit!(existing_ret_type);
70 mark::hit!(existing_ret_type_closure);
71 None
72 }
73 },
74 None => Some(InsertOrReplace::Insert(insert_pos + TextSize::from(1))),
75 }
76}
77
78enum FnType {
79 Function,
80 Closure { wrap_expr: bool },
81}
82
83fn extract_tail(ctx: &AssistContext) -> Option<(FnType, ast::Expr, InsertOrReplace)> {
84 let (fn_type, tail_expr, return_type_range, action) =
85 if let Some(closure) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
86 let rpipe_pos = closure.param_list()?.syntax().last_token()?.text_range().end();
87 let action = ret_ty_to_action(closure.ret_type(), rpipe_pos)?;
88
89 let body = closure.body()?;
90 let body_start = body.syntax().first_token()?.text_range().start();
91 let (tail_expr, wrap_expr) = match body {
92 ast::Expr::BlockExpr(block) => (block.tail_expr()?, false),
93 body => (body, true),
94 };
95
96 let ret_range = TextRange::new(rpipe_pos, body_start);
97 (FnType::Closure { wrap_expr }, tail_expr, ret_range, action)
98 } else {
99 let func = ctx.find_node_at_offset::<ast::Fn>()?;
100 let rparen_pos = func.param_list()?.r_paren_token()?.text_range().end();
101 let action = ret_ty_to_action(func.ret_type(), rparen_pos)?;
102
103 let body = func.body()?;
104 let tail_expr = body.tail_expr()?;
105
106 let ret_range_end = body.l_curly_token()?.text_range().start();
107 let ret_range = TextRange::new(rparen_pos, ret_range_end);
108 (FnType::Function, tail_expr, ret_range, action)
109 };
110 let frange = ctx.frange.range;
111 if return_type_range.contains_range(frange) {
112 mark::hit!(cursor_in_ret_position);
113 mark::hit!(cursor_in_ret_position_closure);
114 } else if tail_expr.syntax().text_range().contains_range(frange) {
115 mark::hit!(cursor_on_tail);
116 mark::hit!(cursor_on_tail_closure);
117 } else {
118 return None;
119 }
120 Some((fn_type, tail_expr, action))
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn infer_return_type_specified_inferred() {
131 mark::check!(existing_infer_ret_type);
132 check_assist(
133 infer_function_return_type,
134 r#"fn foo() -> $0_ {
135 45
136}"#,
137 r#"fn foo() -> i32 {
138 45
139}"#,
140 );
141 }
142
143 #[test]
144 fn infer_return_type_specified_inferred_closure() {
145 mark::check!(existing_infer_ret_type_closure);
146 check_assist(
147 infer_function_return_type,
148 r#"fn foo() {
149 || -> _ {$045};
150}"#,
151 r#"fn foo() {
152 || -> i32 {45};
153}"#,
154 );
155 }
156
157 #[test]
158 fn infer_return_type_cursor_at_return_type_pos() {
159 mark::check!(cursor_in_ret_position);
160 check_assist(
161 infer_function_return_type,
162 r#"fn foo() $0{
163 45
164}"#,
165 r#"fn foo() -> i32 {
166 45
167}"#,
168 );
169 }
170
171 #[test]
172 fn infer_return_type_cursor_at_return_type_pos_closure() {
173 mark::check!(cursor_in_ret_position_closure);
174 check_assist(
175 infer_function_return_type,
176 r#"fn foo() {
177 || $045
178}"#,
179 r#"fn foo() {
180 || -> i32 {45}
181}"#,
182 );
183 }
184
185 #[test]
186 fn infer_return_type() {
187 mark::check!(cursor_on_tail);
188 check_assist(
189 infer_function_return_type,
190 r#"fn foo() {
191 45$0
192}"#,
193 r#"fn foo() -> i32 {
194 45
195}"#,
196 );
197 }
198
199 #[test]
200 fn infer_return_type_nested() {
201 check_assist(
202 infer_function_return_type,
203 r#"fn foo() {
204 if true {
205 3$0
206 } else {
207 5
208 }
209}"#,
210 r#"fn foo() -> i32 {
211 if true {
212 3
213 } else {
214 5
215 }
216}"#,
217 );
218 }
219
220 #[test]
221 fn not_applicable_ret_type_specified() {
222 mark::check!(existing_ret_type);
223 check_assist_not_applicable(
224 infer_function_return_type,
225 r#"fn foo() -> i32 {
226 ( 45$0 + 32 ) * 123
227}"#,
228 );
229 }
230
231 #[test]
232 fn not_applicable_non_tail_expr() {
233 check_assist_not_applicable(
234 infer_function_return_type,
235 r#"fn foo() {
236 let x = $03;
237 ( 45 + 32 ) * 123
238}"#,
239 );
240 }
241
242 #[test]
243 fn not_applicable_unit_return_type() {
244 check_assist_not_applicable(
245 infer_function_return_type,
246 r#"fn foo() {
247 ($0)
248}"#,
249 );
250 }
251
252 #[test]
253 fn infer_return_type_closure_block() {
254 mark::check!(cursor_on_tail_closure);
255 check_assist(
256 infer_function_return_type,
257 r#"fn foo() {
258 |x: i32| {
259 x$0
260 };
261}"#,
262 r#"fn foo() {
263 |x: i32| -> i32 {
264 x
265 };
266}"#,
267 );
268 }
269
270 #[test]
271 fn infer_return_type_closure() {
272 check_assist(
273 infer_function_return_type,
274 r#"fn foo() {
275 |x: i32| { x$0 };
276}"#,
277 r#"fn foo() {
278 |x: i32| -> i32 { x };
279}"#,
280 );
281 }
282
283 #[test]
284 fn infer_return_type_closure_wrap() {
285 mark::check!(wrap_closure_non_block_expr);
286 check_assist(
287 infer_function_return_type,
288 r#"fn foo() {
289 |x: i32| x$0;
290}"#,
291 r#"fn foo() {
292 |x: i32| -> i32 {x};
293}"#,
294 );
295 }
296
297 #[test]
298 fn infer_return_type_nested_closure() {
299 check_assist(
300 infer_function_return_type,
301 r#"fn foo() {
302 || {
303 if true {
304 3$0
305 } else {
306 5
307 }
308 }
309}"#,
310 r#"fn foo() {
311 || -> i32 {
312 if true {
313 3
314 } else {
315 5
316 }
317 }
318}"#,
319 );
320 }
321
322 #[test]
323 fn not_applicable_ret_type_specified_closure() {
324 mark::check!(existing_ret_type_closure);
325 check_assist_not_applicable(
326 infer_function_return_type,
327 r#"fn foo() {
328 || -> i32 { 3$0 }
329}"#,
330 );
331 }
332
333 #[test]
334 fn not_applicable_non_tail_expr_closure() {
335 check_assist_not_applicable(
336 infer_function_return_type,
337 r#"fn foo() {
338 || -> i32 {
339 let x = 3$0;
340 6
341 }
342}"#,
343 );
344 }
345}
diff --git a/crates/assists/src/handlers/inline_function.rs b/crates/assists/src/handlers/inline_function.rs
deleted file mode 100644
index 6ec99b09b..000000000
--- a/crates/assists/src/handlers/inline_function.rs
+++ /dev/null
@@ -1,202 +0,0 @@
1use ast::make;
2use hir::{HasSource, PathResolution};
3use syntax::{
4 ast::{self, edit::AstNodeEdit, ArgListOwner},
5 AstNode,
6};
7use test_utils::mark;
8
9use crate::{
10 assist_context::{AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: inline_function
15//
16// Inlines a function body.
17//
18// ```
19// fn add(a: u32, b: u32) -> u32 { a + b }
20// fn main() {
21// let x = add$0(1, 2);
22// }
23// ```
24// ->
25// ```
26// fn add(a: u32, b: u32) -> u32 { a + b }
27// fn main() {
28// let x = {
29// let a = 1;
30// let b = 2;
31// a + b
32// };
33// }
34// ```
35pub(crate) fn inline_function(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let path_expr: ast::PathExpr = ctx.find_node_at_offset()?;
37 let call = path_expr.syntax().parent().and_then(ast::CallExpr::cast)?;
38 let path = path_expr.path()?;
39
40 let function = match ctx.sema.resolve_path(&path)? {
41 PathResolution::Def(hir::ModuleDef::Function(f)) => f,
42 _ => return None,
43 };
44
45 let function_source = function.source(ctx.db())?;
46 let arguments: Vec<_> = call.arg_list()?.args().collect();
47 let parameters = function_parameter_patterns(&function_source.value)?;
48
49 if arguments.len() != parameters.len() {
50 // Can't inline the function because they've passed the wrong number of
51 // arguments to this function
52 mark::hit!(inline_function_incorrect_number_of_arguments);
53 return None;
54 }
55
56 let new_bindings = parameters.into_iter().zip(arguments);
57
58 let body = function_source.value.body()?;
59
60 acc.add(
61 AssistId("inline_function", AssistKind::RefactorInline),
62 format!("Inline `{}`", path),
63 call.syntax().text_range(),
64 |builder| {
65 let mut statements: Vec<ast::Stmt> = Vec::new();
66
67 for (pattern, value) in new_bindings {
68 statements.push(make::let_stmt(pattern, Some(value)).into());
69 }
70
71 statements.extend(body.statements());
72
73 let original_indentation = call.indent_level();
74 let replacement = make::block_expr(statements, body.tail_expr())
75 .reset_indent()
76 .indent(original_indentation);
77
78 builder.replace_ast(ast::Expr::CallExpr(call), ast::Expr::BlockExpr(replacement));
79 },
80 )
81}
82
83fn function_parameter_patterns(value: &ast::Fn) -> Option<Vec<ast::Pat>> {
84 let mut patterns = Vec::new();
85
86 for param in value.param_list()?.params() {
87 let pattern = param.pat()?;
88 patterns.push(pattern);
89 }
90
91 Some(patterns)
92}
93
94#[cfg(test)]
95mod tests {
96 use crate::tests::{check_assist, check_assist_not_applicable};
97
98 use super::*;
99
100 #[test]
101 fn no_args_or_return_value_gets_inlined_without_block() {
102 check_assist(
103 inline_function,
104 r#"
105fn foo() { println!("Hello, World!"); }
106fn main() {
107 fo$0o();
108}
109"#,
110 r#"
111fn foo() { println!("Hello, World!"); }
112fn main() {
113 {
114 println!("Hello, World!");
115 };
116}
117"#,
118 );
119 }
120
121 #[test]
122 fn args_with_side_effects() {
123 check_assist(
124 inline_function,
125 r#"
126fn foo(name: String) { println!("Hello, {}!", name); }
127fn main() {
128 foo$0(String::from("Michael"));
129}
130"#,
131 r#"
132fn foo(name: String) { println!("Hello, {}!", name); }
133fn main() {
134 {
135 let name = String::from("Michael");
136 println!("Hello, {}!", name);
137 };
138}
139"#,
140 );
141 }
142
143 #[test]
144 fn method_inlining_isnt_supported() {
145 check_assist_not_applicable(
146 inline_function,
147 r"
148struct Foo;
149impl Foo { fn bar(&self) {} }
150
151fn main() { Foo.bar$0(); }
152",
153 );
154 }
155
156 #[test]
157 fn not_applicable_when_incorrect_number_of_parameters_are_provided() {
158 mark::check!(inline_function_incorrect_number_of_arguments);
159 check_assist_not_applicable(
160 inline_function,
161 r#"
162fn add(a: u32, b: u32) -> u32 { a + b }
163fn main() { let x = add$0(42); }
164"#,
165 );
166 }
167
168 #[test]
169 fn function_with_multiple_statements() {
170 check_assist(
171 inline_function,
172 r#"
173fn foo(a: u32, b: u32) -> u32 {
174 let x = a + b;
175 let y = x - b;
176 x * y
177}
178
179fn main() {
180 let x = foo$0(1, 2);
181}
182"#,
183 r#"
184fn foo(a: u32, b: u32) -> u32 {
185 let x = a + b;
186 let y = x - b;
187 x * y
188}
189
190fn main() {
191 let x = {
192 let a = 1;
193 let b = 2;
194 let x = a + b;
195 let y = x - b;
196 x * y
197 };
198}
199"#,
200 );
201 }
202}
diff --git a/crates/assists/src/handlers/inline_local_variable.rs b/crates/assists/src/handlers/inline_local_variable.rs
deleted file mode 100644
index 0e63a60e8..000000000
--- a/crates/assists/src/handlers/inline_local_variable.rs
+++ /dev/null
@@ -1,724 +0,0 @@
1use ide_db::{
2 defs::Definition,
3 search::{FileReference, ReferenceKind},
4};
5use syntax::{
6 ast::{self, AstNode, AstToken},
7 TextRange,
8};
9use test_utils::mark;
10
11use crate::{
12 assist_context::{AssistContext, Assists},
13 AssistId, AssistKind,
14};
15
16// Assist: inline_local_variable
17//
18// Inlines local variable.
19//
20// ```
21// fn main() {
22// let x$0 = 1 + 2;
23// x * 4;
24// }
25// ```
26// ->
27// ```
28// fn main() {
29// (1 + 2) * 4;
30// }
31// ```
32pub(crate) fn inline_local_variable(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
33 let let_stmt = ctx.find_node_at_offset::<ast::LetStmt>()?;
34 let bind_pat = match let_stmt.pat()? {
35 ast::Pat::IdentPat(pat) => pat,
36 _ => return None,
37 };
38 if bind_pat.mut_token().is_some() {
39 mark::hit!(test_not_inline_mut_variable);
40 return None;
41 }
42 if !bind_pat.syntax().text_range().contains_inclusive(ctx.offset()) {
43 mark::hit!(not_applicable_outside_of_bind_pat);
44 return None;
45 }
46 let initializer_expr = let_stmt.initializer()?;
47
48 let def = ctx.sema.to_def(&bind_pat)?;
49 let def = Definition::Local(def);
50 let usages = def.usages(&ctx.sema).all();
51 if usages.is_empty() {
52 mark::hit!(test_not_applicable_if_variable_unused);
53 return None;
54 };
55
56 let delete_range = if let Some(whitespace) = let_stmt
57 .syntax()
58 .next_sibling_or_token()
59 .and_then(|it| ast::Whitespace::cast(it.as_token()?.clone()))
60 {
61 TextRange::new(
62 let_stmt.syntax().text_range().start(),
63 whitespace.syntax().text_range().end(),
64 )
65 } else {
66 let_stmt.syntax().text_range()
67 };
68
69 let wrap_in_parens = usages
70 .references
71 .values()
72 .flatten()
73 .map(|&FileReference { range, .. }| {
74 let usage_node =
75 ctx.covering_node_for_range(range).ancestors().find_map(ast::PathExpr::cast)?;
76 let usage_parent_option = usage_node.syntax().parent().and_then(ast::Expr::cast);
77 let usage_parent = match usage_parent_option {
78 Some(u) => u,
79 None => return Ok(false),
80 };
81
82 Ok(!matches!(
83 (&initializer_expr, usage_parent),
84 (ast::Expr::CallExpr(_), _)
85 | (ast::Expr::IndexExpr(_), _)
86 | (ast::Expr::MethodCallExpr(_), _)
87 | (ast::Expr::FieldExpr(_), _)
88 | (ast::Expr::TryExpr(_), _)
89 | (ast::Expr::RefExpr(_), _)
90 | (ast::Expr::Literal(_), _)
91 | (ast::Expr::TupleExpr(_), _)
92 | (ast::Expr::ArrayExpr(_), _)
93 | (ast::Expr::ParenExpr(_), _)
94 | (ast::Expr::PathExpr(_), _)
95 | (ast::Expr::BlockExpr(_), _)
96 | (ast::Expr::EffectExpr(_), _)
97 | (_, ast::Expr::CallExpr(_))
98 | (_, ast::Expr::TupleExpr(_))
99 | (_, ast::Expr::ArrayExpr(_))
100 | (_, ast::Expr::ParenExpr(_))
101 | (_, ast::Expr::ForExpr(_))
102 | (_, ast::Expr::WhileExpr(_))
103 | (_, ast::Expr::BreakExpr(_))
104 | (_, ast::Expr::ReturnExpr(_))
105 | (_, ast::Expr::MatchExpr(_))
106 ))
107 })
108 .collect::<Result<Vec<_>, _>>()?;
109
110 let init_str = initializer_expr.syntax().text().to_string();
111 let init_in_paren = format!("({})", &init_str);
112
113 let target = bind_pat.syntax().text_range();
114 acc.add(
115 AssistId("inline_local_variable", AssistKind::RefactorInline),
116 "Inline variable",
117 target,
118 move |builder| {
119 builder.delete(delete_range);
120 for (reference, should_wrap) in usages.references.values().flatten().zip(wrap_in_parens)
121 {
122 let replacement =
123 if should_wrap { init_in_paren.clone() } else { init_str.clone() };
124 match reference.kind {
125 ReferenceKind::FieldShorthandForLocal => {
126 mark::hit!(inline_field_shorthand);
127 builder.insert(reference.range.end(), format!(": {}", replacement))
128 }
129 _ => builder.replace(reference.range, replacement),
130 }
131 }
132 },
133 )
134}
135
136#[cfg(test)]
137mod tests {
138 use test_utils::mark;
139
140 use crate::tests::{check_assist, check_assist_not_applicable};
141
142 use super::*;
143
144 #[test]
145 fn test_inline_let_bind_literal_expr() {
146 check_assist(
147 inline_local_variable,
148 r"
149fn bar(a: usize) {}
150fn foo() {
151 let a$0 = 1;
152 a + 1;
153 if a > 10 {
154 }
155
156 while a > 10 {
157
158 }
159 let b = a * 10;
160 bar(a);
161}",
162 r"
163fn bar(a: usize) {}
164fn foo() {
165 1 + 1;
166 if 1 > 10 {
167 }
168
169 while 1 > 10 {
170
171 }
172 let b = 1 * 10;
173 bar(1);
174}",
175 );
176 }
177
178 #[test]
179 fn test_inline_let_bind_bin_expr() {
180 check_assist(
181 inline_local_variable,
182 r"
183fn bar(a: usize) {}
184fn foo() {
185 let a$0 = 1 + 1;
186 a + 1;
187 if a > 10 {
188 }
189
190 while a > 10 {
191
192 }
193 let b = a * 10;
194 bar(a);
195}",
196 r"
197fn bar(a: usize) {}
198fn foo() {
199 (1 + 1) + 1;
200 if (1 + 1) > 10 {
201 }
202
203 while (1 + 1) > 10 {
204
205 }
206 let b = (1 + 1) * 10;
207 bar(1 + 1);
208}",
209 );
210 }
211
212 #[test]
213 fn test_inline_let_bind_function_call_expr() {
214 check_assist(
215 inline_local_variable,
216 r"
217fn bar(a: usize) {}
218fn foo() {
219 let a$0 = bar(1);
220 a + 1;
221 if a > 10 {
222 }
223
224 while a > 10 {
225
226 }
227 let b = a * 10;
228 bar(a);
229}",
230 r"
231fn bar(a: usize) {}
232fn foo() {
233 bar(1) + 1;
234 if bar(1) > 10 {
235 }
236
237 while bar(1) > 10 {
238
239 }
240 let b = bar(1) * 10;
241 bar(bar(1));
242}",
243 );
244 }
245
246 #[test]
247 fn test_inline_let_bind_cast_expr() {
248 check_assist(
249 inline_local_variable,
250 r"
251fn bar(a: usize): usize { a }
252fn foo() {
253 let a$0 = bar(1) as u64;
254 a + 1;
255 if a > 10 {
256 }
257
258 while a > 10 {
259
260 }
261 let b = a * 10;
262 bar(a);
263}",
264 r"
265fn bar(a: usize): usize { a }
266fn foo() {
267 (bar(1) as u64) + 1;
268 if (bar(1) as u64) > 10 {
269 }
270
271 while (bar(1) as u64) > 10 {
272
273 }
274 let b = (bar(1) as u64) * 10;
275 bar(bar(1) as u64);
276}",
277 );
278 }
279
280 #[test]
281 fn test_inline_let_bind_block_expr() {
282 check_assist(
283 inline_local_variable,
284 r"
285fn foo() {
286 let a$0 = { 10 + 1 };
287 a + 1;
288 if a > 10 {
289 }
290
291 while a > 10 {
292
293 }
294 let b = a * 10;
295 bar(a);
296}",
297 r"
298fn foo() {
299 { 10 + 1 } + 1;
300 if { 10 + 1 } > 10 {
301 }
302
303 while { 10 + 1 } > 10 {
304
305 }
306 let b = { 10 + 1 } * 10;
307 bar({ 10 + 1 });
308}",
309 );
310 }
311
312 #[test]
313 fn test_inline_let_bind_paren_expr() {
314 check_assist(
315 inline_local_variable,
316 r"
317fn foo() {
318 let a$0 = ( 10 + 1 );
319 a + 1;
320 if a > 10 {
321 }
322
323 while a > 10 {
324
325 }
326 let b = a * 10;
327 bar(a);
328}",
329 r"
330fn foo() {
331 ( 10 + 1 ) + 1;
332 if ( 10 + 1 ) > 10 {
333 }
334
335 while ( 10 + 1 ) > 10 {
336
337 }
338 let b = ( 10 + 1 ) * 10;
339 bar(( 10 + 1 ));
340}",
341 );
342 }
343
344 #[test]
345 fn test_not_inline_mut_variable() {
346 mark::check!(test_not_inline_mut_variable);
347 check_assist_not_applicable(
348 inline_local_variable,
349 r"
350fn foo() {
351 let mut a$0 = 1 + 1;
352 a + 1;
353}",
354 );
355 }
356
357 #[test]
358 fn test_call_expr() {
359 check_assist(
360 inline_local_variable,
361 r"
362fn foo() {
363 let a$0 = bar(10 + 1);
364 let b = a * 10;
365 let c = a as usize;
366}",
367 r"
368fn foo() {
369 let b = bar(10 + 1) * 10;
370 let c = bar(10 + 1) as usize;
371}",
372 );
373 }
374
375 #[test]
376 fn test_index_expr() {
377 check_assist(
378 inline_local_variable,
379 r"
380fn foo() {
381 let x = vec![1, 2, 3];
382 let a$0 = x[0];
383 let b = a * 10;
384 let c = a as usize;
385}",
386 r"
387fn foo() {
388 let x = vec![1, 2, 3];
389 let b = x[0] * 10;
390 let c = x[0] as usize;
391}",
392 );
393 }
394
395 #[test]
396 fn test_method_call_expr() {
397 check_assist(
398 inline_local_variable,
399 r"
400fn foo() {
401 let bar = vec![1];
402 let a$0 = bar.len();
403 let b = a * 10;
404 let c = a as usize;
405}",
406 r"
407fn foo() {
408 let bar = vec![1];
409 let b = bar.len() * 10;
410 let c = bar.len() as usize;
411}",
412 );
413 }
414
415 #[test]
416 fn test_field_expr() {
417 check_assist(
418 inline_local_variable,
419 r"
420struct Bar {
421 foo: usize
422}
423
424fn foo() {
425 let bar = Bar { foo: 1 };
426 let a$0 = bar.foo;
427 let b = a * 10;
428 let c = a as usize;
429}",
430 r"
431struct Bar {
432 foo: usize
433}
434
435fn foo() {
436 let bar = Bar { foo: 1 };
437 let b = bar.foo * 10;
438 let c = bar.foo as usize;
439}",
440 );
441 }
442
443 #[test]
444 fn test_try_expr() {
445 check_assist(
446 inline_local_variable,
447 r"
448fn foo() -> Option<usize> {
449 let bar = Some(1);
450 let a$0 = bar?;
451 let b = a * 10;
452 let c = a as usize;
453 None
454}",
455 r"
456fn foo() -> Option<usize> {
457 let bar = Some(1);
458 let b = bar? * 10;
459 let c = bar? as usize;
460 None
461}",
462 );
463 }
464
465 #[test]
466 fn test_ref_expr() {
467 check_assist(
468 inline_local_variable,
469 r"
470fn foo() {
471 let bar = 10;
472 let a$0 = &bar;
473 let b = a * 10;
474}",
475 r"
476fn foo() {
477 let bar = 10;
478 let b = &bar * 10;
479}",
480 );
481 }
482
483 #[test]
484 fn test_tuple_expr() {
485 check_assist(
486 inline_local_variable,
487 r"
488fn foo() {
489 let a$0 = (10, 20);
490 let b = a[0];
491}",
492 r"
493fn foo() {
494 let b = (10, 20)[0];
495}",
496 );
497 }
498
499 #[test]
500 fn test_array_expr() {
501 check_assist(
502 inline_local_variable,
503 r"
504fn foo() {
505 let a$0 = [1, 2, 3];
506 let b = a.len();
507}",
508 r"
509fn foo() {
510 let b = [1, 2, 3].len();
511}",
512 );
513 }
514
515 #[test]
516 fn test_paren() {
517 check_assist(
518 inline_local_variable,
519 r"
520fn foo() {
521 let a$0 = (10 + 20);
522 let b = a * 10;
523 let c = a as usize;
524}",
525 r"
526fn foo() {
527 let b = (10 + 20) * 10;
528 let c = (10 + 20) as usize;
529}",
530 );
531 }
532
533 #[test]
534 fn test_path_expr() {
535 check_assist(
536 inline_local_variable,
537 r"
538fn foo() {
539 let d = 10;
540 let a$0 = d;
541 let b = a * 10;
542 let c = a as usize;
543}",
544 r"
545fn foo() {
546 let d = 10;
547 let b = d * 10;
548 let c = d as usize;
549}",
550 );
551 }
552
553 #[test]
554 fn test_block_expr() {
555 check_assist(
556 inline_local_variable,
557 r"
558fn foo() {
559 let a$0 = { 10 };
560 let b = a * 10;
561 let c = a as usize;
562}",
563 r"
564fn foo() {
565 let b = { 10 } * 10;
566 let c = { 10 } as usize;
567}",
568 );
569 }
570
571 #[test]
572 fn test_used_in_different_expr1() {
573 check_assist(
574 inline_local_variable,
575 r"
576fn foo() {
577 let a$0 = 10 + 20;
578 let b = a * 10;
579 let c = (a, 20);
580 let d = [a, 10];
581 let e = (a);
582}",
583 r"
584fn foo() {
585 let b = (10 + 20) * 10;
586 let c = (10 + 20, 20);
587 let d = [10 + 20, 10];
588 let e = (10 + 20);
589}",
590 );
591 }
592
593 #[test]
594 fn test_used_in_for_expr() {
595 check_assist(
596 inline_local_variable,
597 r"
598fn foo() {
599 let a$0 = vec![10, 20];
600 for i in a {}
601}",
602 r"
603fn foo() {
604 for i in vec![10, 20] {}
605}",
606 );
607 }
608
609 #[test]
610 fn test_used_in_while_expr() {
611 check_assist(
612 inline_local_variable,
613 r"
614fn foo() {
615 let a$0 = 1 > 0;
616 while a {}
617}",
618 r"
619fn foo() {
620 while 1 > 0 {}
621}",
622 );
623 }
624
625 #[test]
626 fn test_used_in_break_expr() {
627 check_assist(
628 inline_local_variable,
629 r"
630fn foo() {
631 let a$0 = 1 + 1;
632 loop {
633 break a;
634 }
635}",
636 r"
637fn foo() {
638 loop {
639 break 1 + 1;
640 }
641}",
642 );
643 }
644
645 #[test]
646 fn test_used_in_return_expr() {
647 check_assist(
648 inline_local_variable,
649 r"
650fn foo() {
651 let a$0 = 1 > 0;
652 return a;
653}",
654 r"
655fn foo() {
656 return 1 > 0;
657}",
658 );
659 }
660
661 #[test]
662 fn test_used_in_match_expr() {
663 check_assist(
664 inline_local_variable,
665 r"
666fn foo() {
667 let a$0 = 1 > 0;
668 match a {}
669}",
670 r"
671fn foo() {
672 match 1 > 0 {}
673}",
674 );
675 }
676
677 #[test]
678 fn inline_field_shorthand() {
679 mark::check!(inline_field_shorthand);
680 check_assist(
681 inline_local_variable,
682 r"
683struct S { foo: i32}
684fn main() {
685 let $0foo = 92;
686 S { foo }
687}
688",
689 r"
690struct S { foo: i32}
691fn main() {
692 S { foo: 92 }
693}
694",
695 );
696 }
697
698 #[test]
699 fn test_not_applicable_if_variable_unused() {
700 mark::check!(test_not_applicable_if_variable_unused);
701 check_assist_not_applicable(
702 inline_local_variable,
703 r"
704fn foo() {
705 let $0a = 0;
706}
707 ",
708 )
709 }
710
711 #[test]
712 fn not_applicable_outside_of_bind_pat() {
713 mark::check!(not_applicable_outside_of_bind_pat);
714 check_assist_not_applicable(
715 inline_local_variable,
716 r"
717fn main() {
718 let x = $01 + 2;
719 x * 4;
720}
721",
722 )
723 }
724}
diff --git a/crates/assists/src/handlers/introduce_named_lifetime.rs b/crates/assists/src/handlers/introduce_named_lifetime.rs
deleted file mode 100644
index 02782eb6d..000000000
--- a/crates/assists/src/handlers/introduce_named_lifetime.rs
+++ /dev/null
@@ -1,315 +0,0 @@
1use rustc_hash::FxHashSet;
2use syntax::{
3 ast::{self, GenericParamsOwner, NameOwner},
4 AstNode, TextRange, TextSize,
5};
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<'_$0> {
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) -> &$0Foo
37pub(crate) fn introduce_named_lifetime(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
38 let lifetime =
39 ctx.find_node_at_offset::<ast::Lifetime>().filter(|lifetime| lifetime.text() == "'_")?;
40 if let Some(fn_def) = lifetime.syntax().ancestors().find_map(ast::Fn::cast) {
41 generate_fn_def_assist(acc, &fn_def, lifetime.lifetime_ident_token()?.text_range())
42 } else if let Some(impl_def) = lifetime.syntax().ancestors().find_map(ast::Impl::cast) {
43 generate_impl_def_assist(acc, &impl_def, lifetime.lifetime_ident_token()?.text_range())
44 } else {
45 None
46 }
47}
48
49/// Generate the assist for the fn def case
50fn generate_fn_def_assist(
51 acc: &mut Assists,
52 fn_def: &ast::Fn,
53 lifetime_loc: TextRange,
54) -> Option<()> {
55 let param_list: ast::ParamList = fn_def.param_list()?;
56 let new_lifetime_param = generate_unique_lifetime_param_name(&fn_def.generic_param_list())?;
57 let end_of_fn_ident = fn_def.name()?.ident_token()?.text_range().end();
58 let self_param =
59 // use the self if it's a reference and has no explicit lifetime
60 param_list.self_param().filter(|p| p.lifetime().is_none() && p.amp_token().is_some());
61 // compute the location which implicitly has the same lifetime as the anonymous lifetime
62 let loc_needing_lifetime = if let Some(self_param) = self_param {
63 // if we have a self reference, use that
64 Some(self_param.name()?.syntax().text_range().start())
65 } else {
66 // otherwise, if there's a single reference parameter without a named liftime, use that
67 let fn_params_without_lifetime: Vec<_> = param_list
68 .params()
69 .filter_map(|param| match param.ty() {
70 Some(ast::Type::RefType(ascribed_type)) if ascribed_type.lifetime().is_none() => {
71 Some(ascribed_type.amp_token()?.text_range().end())
72 }
73 _ => None,
74 })
75 .collect();
76 match fn_params_without_lifetime.len() {
77 1 => Some(fn_params_without_lifetime.into_iter().nth(0)?),
78 0 => None,
79 // multiple unnnamed is invalid. assist is not applicable
80 _ => return None,
81 }
82 };
83 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
84 add_lifetime_param(fn_def, builder, end_of_fn_ident, new_lifetime_param);
85 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
86 loc_needing_lifetime.map(|loc| builder.insert(loc, format!("'{} ", new_lifetime_param)));
87 })
88}
89
90/// Generate the assist for the impl def case
91fn generate_impl_def_assist(
92 acc: &mut Assists,
93 impl_def: &ast::Impl,
94 lifetime_loc: TextRange,
95) -> Option<()> {
96 let new_lifetime_param = generate_unique_lifetime_param_name(&impl_def.generic_param_list())?;
97 let end_of_impl_kw = impl_def.impl_token()?.text_range().end();
98 acc.add(AssistId(ASSIST_NAME, AssistKind::Refactor), ASSIST_LABEL, lifetime_loc, |builder| {
99 add_lifetime_param(impl_def, builder, end_of_impl_kw, new_lifetime_param);
100 builder.replace(lifetime_loc, format!("'{}", new_lifetime_param));
101 })
102}
103
104/// Given a type parameter list, generate a unique lifetime parameter name
105/// which is not in the list
106fn generate_unique_lifetime_param_name(
107 existing_type_param_list: &Option<ast::GenericParamList>,
108) -> Option<char> {
109 match existing_type_param_list {
110 Some(type_params) => {
111 let used_lifetime_params: FxHashSet<_> = type_params
112 .lifetime_params()
113 .map(|p| p.syntax().text().to_string()[1..].to_owned())
114 .collect();
115 (b'a'..=b'z').map(char::from).find(|c| !used_lifetime_params.contains(&c.to_string()))
116 }
117 None => Some('a'),
118 }
119}
120
121/// Add the lifetime param to `builder`. If there are type parameters in `type_params_owner`, add it to the end. Otherwise
122/// add new type params brackets with the lifetime parameter at `new_type_params_loc`.
123fn add_lifetime_param<TypeParamsOwner: ast::GenericParamsOwner>(
124 type_params_owner: &TypeParamsOwner,
125 builder: &mut AssistBuilder,
126 new_type_params_loc: TextSize,
127 new_lifetime_param: char,
128) {
129 match type_params_owner.generic_param_list() {
130 // add the new lifetime parameter to an existing type param list
131 Some(type_params) => {
132 builder.insert(
133 (u32::from(type_params.syntax().text_range().end()) - 1).into(),
134 format!(", '{}", new_lifetime_param),
135 );
136 }
137 // create a new type param list containing only the new lifetime parameter
138 None => {
139 builder.insert(new_type_params_loc, format!("<'{}>", new_lifetime_param));
140 }
141 }
142}
143
144#[cfg(test)]
145mod tests {
146 use super::*;
147 use crate::tests::{check_assist, check_assist_not_applicable};
148
149 #[test]
150 fn test_example_case() {
151 check_assist(
152 introduce_named_lifetime,
153 r#"impl Cursor<'_$0> {
154 fn node(self) -> &SyntaxNode {
155 match self {
156 Cursor::Replace(node) | Cursor::Before(node) => node,
157 }
158 }
159 }"#,
160 r#"impl<'a> Cursor<'a> {
161 fn node(self) -> &SyntaxNode {
162 match self {
163 Cursor::Replace(node) | Cursor::Before(node) => node,
164 }
165 }
166 }"#,
167 );
168 }
169
170 #[test]
171 fn test_example_case_simplified() {
172 check_assist(
173 introduce_named_lifetime,
174 r#"impl Cursor<'_$0> {"#,
175 r#"impl<'a> Cursor<'a> {"#,
176 );
177 }
178
179 #[test]
180 fn test_example_case_cursor_after_tick() {
181 check_assist(
182 introduce_named_lifetime,
183 r#"impl Cursor<'$0_> {"#,
184 r#"impl<'a> Cursor<'a> {"#,
185 );
186 }
187
188 #[test]
189 fn test_impl_with_other_type_param() {
190 check_assist(
191 introduce_named_lifetime,
192 "impl<I> fmt::Display for SepByBuilder<'_$0, I>
193 where
194 I: Iterator,
195 I::Item: fmt::Display,
196 {",
197 "impl<I, 'a> fmt::Display for SepByBuilder<'a, I>
198 where
199 I: Iterator,
200 I::Item: fmt::Display,
201 {",
202 )
203 }
204
205 #[test]
206 fn test_example_case_cursor_before_tick() {
207 check_assist(
208 introduce_named_lifetime,
209 r#"impl Cursor<$0'_> {"#,
210 r#"impl<'a> Cursor<'a> {"#,
211 );
212 }
213
214 #[test]
215 fn test_not_applicable_cursor_position() {
216 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'_>$0 {"#);
217 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor$0<'_> {"#);
218 }
219
220 #[test]
221 fn test_not_applicable_lifetime_already_name() {
222 check_assist_not_applicable(introduce_named_lifetime, r#"impl Cursor<'a$0> {"#);
223 check_assist_not_applicable(introduce_named_lifetime, r#"fn my_fun<'a>() -> X<'a$0>"#);
224 }
225
226 #[test]
227 fn test_with_type_parameter() {
228 check_assist(
229 introduce_named_lifetime,
230 r#"impl<T> Cursor<T, '_$0>"#,
231 r#"impl<T, 'a> Cursor<T, 'a>"#,
232 );
233 }
234
235 #[test]
236 fn test_with_existing_lifetime_name_conflict() {
237 check_assist(
238 introduce_named_lifetime,
239 r#"impl<'a, 'b> Cursor<'a, 'b, '_$0>"#,
240 r#"impl<'a, 'b, 'c> Cursor<'a, 'b, 'c>"#,
241 );
242 }
243
244 #[test]
245 fn test_function_return_value_anon_lifetime_param() {
246 check_assist(
247 introduce_named_lifetime,
248 r#"fn my_fun() -> X<'_$0>"#,
249 r#"fn my_fun<'a>() -> X<'a>"#,
250 );
251 }
252
253 #[test]
254 fn test_function_return_value_anon_reference_lifetime() {
255 check_assist(
256 introduce_named_lifetime,
257 r#"fn my_fun() -> &'_$0 X"#,
258 r#"fn my_fun<'a>() -> &'a X"#,
259 );
260 }
261
262 #[test]
263 fn test_function_param_anon_lifetime() {
264 check_assist(
265 introduce_named_lifetime,
266 r#"fn my_fun(x: X<'_$0>)"#,
267 r#"fn my_fun<'a>(x: X<'a>)"#,
268 );
269 }
270
271 #[test]
272 fn test_function_add_lifetime_to_params() {
273 check_assist(
274 introduce_named_lifetime,
275 r#"fn my_fun(f: &Foo) -> X<'_$0>"#,
276 r#"fn my_fun<'a>(f: &'a Foo) -> X<'a>"#,
277 );
278 }
279
280 #[test]
281 fn test_function_add_lifetime_to_params_in_presence_of_other_lifetime() {
282 check_assist(
283 introduce_named_lifetime,
284 r#"fn my_fun<'other>(f: &Foo, b: &'other Bar) -> X<'_$0>"#,
285 r#"fn my_fun<'other, 'a>(f: &'a Foo, b: &'other Bar) -> X<'a>"#,
286 );
287 }
288
289 #[test]
290 fn test_function_not_applicable_without_self_and_multiple_unnamed_param_lifetimes() {
291 // this is not permitted under lifetime elision rules
292 check_assist_not_applicable(
293 introduce_named_lifetime,
294 r#"fn my_fun(f: &Foo, b: &Bar) -> X<'_$0>"#,
295 );
296 }
297
298 #[test]
299 fn test_function_add_lifetime_to_self_ref_param() {
300 check_assist(
301 introduce_named_lifetime,
302 r#"fn my_fun<'other>(&self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
303 r#"fn my_fun<'other, 'a>(&'a self, f: &Foo, b: &'other Bar) -> X<'a>"#,
304 );
305 }
306
307 #[test]
308 fn test_function_add_lifetime_to_param_with_non_ref_self() {
309 check_assist(
310 introduce_named_lifetime,
311 r#"fn my_fun<'other>(self, f: &Foo, b: &'other Bar) -> X<'_$0>"#,
312 r#"fn my_fun<'other, 'a>(self, f: &'a Foo, b: &'other Bar) -> X<'a>"#,
313 );
314 }
315}
diff --git a/crates/assists/src/handlers/invert_if.rs b/crates/assists/src/handlers/invert_if.rs
deleted file mode 100644
index 5b69dafd4..000000000
--- a/crates/assists/src/handlers/invert_if.rs
+++ /dev/null
@@ -1,146 +0,0 @@
1use 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$0 !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_syntax_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 acc.add(AssistId("invert_if", AssistKind::RefactorRewrite), "Invert if", if_range, |edit| {
53 let flip_cond = invert_boolean_expression(cond.clone());
54 edit.replace_ast(cond, flip_cond);
55
56 let else_node = else_block.syntax();
57 let else_range = else_node.text_range();
58 let then_range = then_node.text_range();
59
60 edit.replace(else_range, then_node.text());
61 edit.replace(then_range, else_node.text());
62 })
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68
69 use crate::tests::{check_assist, check_assist_not_applicable};
70
71 #[test]
72 fn invert_if_composite_condition() {
73 check_assist(
74 invert_if,
75 "fn f() { i$0f x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
76 "fn f() { if !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
77 )
78 }
79
80 #[test]
81 fn invert_if_remove_not_parentheses() {
82 check_assist(
83 invert_if,
84 "fn f() { i$0f !(x == 3 || x == 4 || x == 5) { 3 * 2 } else { 1 } }",
85 "fn f() { if x == 3 || x == 4 || x == 5 { 1 } else { 3 * 2 } }",
86 )
87 }
88
89 #[test]
90 fn invert_if_remove_inequality() {
91 check_assist(
92 invert_if,
93 "fn f() { i$0f x != 3 { 1 } else { 3 + 2 } }",
94 "fn f() { if x == 3 { 3 + 2 } else { 1 } }",
95 )
96 }
97
98 #[test]
99 fn invert_if_remove_not() {
100 check_assist(
101 invert_if,
102 "fn f() { $0if !cond { 3 * 2 } else { 1 } }",
103 "fn f() { if cond { 1 } else { 3 * 2 } }",
104 )
105 }
106
107 #[test]
108 fn invert_if_general_case() {
109 check_assist(
110 invert_if,
111 "fn f() { i$0f cond { 3 * 2 } else { 1 } }",
112 "fn f() { if !cond { 1 } else { 3 * 2 } }",
113 )
114 }
115
116 #[test]
117 fn invert_if_doesnt_apply_with_cursor_not_on_if() {
118 check_assist_not_applicable(invert_if, "fn f() { if !$0cond { 3 * 2 } else { 1 } }")
119 }
120
121 #[test]
122 fn invert_if_doesnt_apply_with_if_let() {
123 check_assist_not_applicable(
124 invert_if,
125 "fn f() { i$0f let Some(_) = Some(1) { 1 } else { 0 } }",
126 )
127 }
128
129 #[test]
130 fn invert_if_option_case() {
131 check_assist(
132 invert_if,
133 "fn f() { if$0 doc_style.is_some() { Class::DocComment } else { Class::Comment } }",
134 "fn f() { if doc_style.is_none() { Class::Comment } else { Class::DocComment } }",
135 )
136 }
137
138 #[test]
139 fn invert_if_result_case() {
140 check_assist(
141 invert_if,
142 "fn f() { i$0f doc_style.is_err() { Class::Err } else { Class::Ok } }",
143 "fn f() { if doc_style.is_ok() { Class::Ok } else { Class::Err } }",
144 )
145 }
146}
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
deleted file mode 100644
index 7bd7e1e36..000000000
--- a/crates/assists/src/handlers/merge_imports.rs
+++ /dev/null
@@ -1,343 +0,0 @@
1use ide_db::helpers::insert_use::{try_merge_imports, try_merge_trees, MergeBehavior};
2use syntax::{
3 algo::{neighbor, SyntaxRewriter},
4 ast, AstNode,
5};
6
7use crate::{
8 assist_context::{AssistContext, Assists},
9 utils::next_prev,
10 AssistId, AssistKind,
11};
12
13// Assist: merge_imports
14//
15// Merges two imports with a common prefix.
16//
17// ```
18// use std::$0fmt::Formatter;
19// use std::io;
20// ```
21// ->
22// ```
23// use std::{fmt::Formatter, io};
24// ```
25pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let tree: ast::UseTree = ctx.find_node_at_offset()?;
27 let mut rewriter = SyntaxRewriter::default();
28 let mut offset = ctx.offset();
29
30 if let Some(use_item) = tree.syntax().parent().and_then(ast::Use::cast) {
31 let (merged, to_delete) =
32 next_prev().filter_map(|dir| neighbor(&use_item, dir)).find_map(|use_item2| {
33 try_merge_imports(&use_item, &use_item2, MergeBehavior::Full).zip(Some(use_item2))
34 })?;
35
36 rewriter.replace_ast(&use_item, &merged);
37 rewriter += to_delete.remove();
38
39 if to_delete.syntax().text_range().end() < offset {
40 offset -= to_delete.syntax().text_range().len();
41 }
42 } else {
43 let (merged, to_delete) =
44 next_prev().filter_map(|dir| neighbor(&tree, dir)).find_map(|use_tree| {
45 try_merge_trees(&tree, &use_tree, MergeBehavior::Full).zip(Some(use_tree))
46 })?;
47
48 rewriter.replace_ast(&tree, &merged);
49 rewriter += to_delete.remove();
50
51 if to_delete.syntax().text_range().end() < offset {
52 offset -= to_delete.syntax().text_range().len();
53 }
54 };
55
56 let target = tree.syntax().text_range();
57 acc.add(
58 AssistId("merge_imports", AssistKind::RefactorRewrite),
59 "Merge imports",
60 target,
61 |builder| {
62 builder.rewrite(rewriter);
63 },
64 )
65}
66
67#[cfg(test)]
68mod tests {
69 use crate::tests::{check_assist, check_assist_not_applicable};
70
71 use super::*;
72
73 #[test]
74 fn test_merge_equal() {
75 check_assist(
76 merge_imports,
77 r"
78use std::fmt$0::{Display, Debug};
79use std::fmt::{Display, Debug};
80",
81 r"
82use std::fmt::{Debug, Display};
83",
84 )
85 }
86
87 #[test]
88 fn test_merge_first() {
89 check_assist(
90 merge_imports,
91 r"
92use std::fmt$0::Debug;
93use std::fmt::Display;
94",
95 r"
96use std::fmt::{Debug, Display};
97",
98 )
99 }
100
101 #[test]
102 fn test_merge_second() {
103 check_assist(
104 merge_imports,
105 r"
106use std::fmt::Debug;
107use std::fmt$0::Display;
108",
109 r"
110use std::fmt::{Debug, Display};
111",
112 );
113 }
114
115 #[test]
116 fn merge_self1() {
117 check_assist(
118 merge_imports,
119 r"
120use std::fmt$0;
121use std::fmt::Display;
122",
123 r"
124use std::fmt::{self, Display};
125",
126 );
127 }
128
129 #[test]
130 fn merge_self2() {
131 check_assist(
132 merge_imports,
133 r"
134use std::{fmt, $0fmt::Display};
135",
136 r"
137use std::{fmt::{self, Display}};
138",
139 );
140 }
141
142 #[test]
143 fn skip_pub1() {
144 check_assist_not_applicable(
145 merge_imports,
146 r"
147pub use std::fmt$0::Debug;
148use std::fmt::Display;
149",
150 );
151 }
152
153 #[test]
154 fn skip_pub_last() {
155 check_assist_not_applicable(
156 merge_imports,
157 r"
158use std::fmt$0::Debug;
159pub use std::fmt::Display;
160",
161 );
162 }
163
164 #[test]
165 fn skip_pub_crate_pub() {
166 check_assist_not_applicable(
167 merge_imports,
168 r"
169pub(crate) use std::fmt$0::Debug;
170pub use std::fmt::Display;
171",
172 );
173 }
174
175 #[test]
176 fn skip_pub_pub_crate() {
177 check_assist_not_applicable(
178 merge_imports,
179 r"
180pub use std::fmt$0::Debug;
181pub(crate) use std::fmt::Display;
182",
183 );
184 }
185
186 #[test]
187 fn merge_pub() {
188 check_assist(
189 merge_imports,
190 r"
191pub use std::fmt$0::Debug;
192pub use std::fmt::Display;
193",
194 r"
195pub use std::fmt::{Debug, Display};
196",
197 )
198 }
199
200 #[test]
201 fn merge_pub_crate() {
202 check_assist(
203 merge_imports,
204 r"
205pub(crate) use std::fmt$0::Debug;
206pub(crate) use std::fmt::Display;
207",
208 r"
209pub(crate) use std::fmt::{Debug, Display};
210",
211 )
212 }
213
214 #[test]
215 fn test_merge_nested() {
216 check_assist(
217 merge_imports,
218 r"
219use std::{fmt$0::Debug, fmt::Display};
220",
221 r"
222use std::{fmt::{Debug, Display}};
223",
224 );
225 }
226
227 #[test]
228 fn test_merge_nested2() {
229 check_assist(
230 merge_imports,
231 r"
232use std::{fmt::Debug, fmt$0::Display};
233",
234 r"
235use std::{fmt::{Debug, Display}};
236",
237 );
238 }
239
240 #[test]
241 fn test_merge_single_wildcard_diff_prefixes() {
242 check_assist(
243 merge_imports,
244 r"
245use std$0::cell::*;
246use std::str;
247",
248 r"
249use std::{cell::*, str};
250",
251 )
252 }
253
254 #[test]
255 fn test_merge_both_wildcard_diff_prefixes() {
256 check_assist(
257 merge_imports,
258 r"
259use std$0::cell::*;
260use std::str::*;
261",
262 r"
263use std::{cell::*, str::*};
264",
265 )
266 }
267
268 #[test]
269 fn removes_just_enough_whitespace() {
270 check_assist(
271 merge_imports,
272 r"
273use foo$0::bar;
274use foo::baz;
275
276/// Doc comment
277",
278 r"
279use foo::{bar, baz};
280
281/// Doc comment
282",
283 );
284 }
285
286 #[test]
287 fn works_with_trailing_comma() {
288 check_assist(
289 merge_imports,
290 r"
291use {
292 foo$0::bar,
293 foo::baz,
294};
295",
296 r"
297use {
298 foo::{bar, baz},
299};
300",
301 );
302 check_assist(
303 merge_imports,
304 r"
305use {
306 foo::baz,
307 foo$0::bar,
308};
309",
310 r"
311use {
312 foo::{bar, baz},
313};
314",
315 );
316 }
317
318 #[test]
319 fn test_double_comma() {
320 check_assist(
321 merge_imports,
322 r"
323use foo::bar::baz;
324use foo::$0{
325 FooBar,
326};
327",
328 r"
329use foo::{FooBar, bar::baz};
330",
331 )
332 }
333
334 #[test]
335 fn test_empty_use() {
336 check_assist_not_applicable(
337 merge_imports,
338 r"
339use std::$0
340fn main() {}",
341 );
342 }
343}
diff --git a/crates/assists/src/handlers/merge_match_arms.rs b/crates/assists/src/handlers/merge_match_arms.rs
deleted file mode 100644
index 9bf076cb9..000000000
--- a/crates/assists/src/handlers/merge_match_arms.rs
+++ /dev/null
@@ -1,248 +0,0 @@
1use std::iter::successors;
2
3use 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// $0Action::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::WildcardPat(..)))
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$0 }
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 => {$0 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$032 },
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$0 => 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 => { $01i32 },
241 X::B => { 1i32 },
242 X::C => { 2i32 }
243 }
244 }
245 "#,
246 );
247 }
248}
diff --git a/crates/assists/src/handlers/move_bounds.rs b/crates/assists/src/handlers/move_bounds.rs
deleted file mode 100644
index cf260c6f8..000000000
--- a/crates/assists/src/handlers/move_bounds.rs
+++ /dev/null
@@ -1,152 +0,0 @@
1use 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, $0F: 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, $0F: 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, $0T> 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<$0T: 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<$0T: u32>(T, T);
146 "#,
147 r#"
148 struct Pair<T>(T, T) where T: u32;
149 "#,
150 );
151 }
152}
diff --git a/crates/assists/src/handlers/move_guard.rs b/crates/assists/src/handlers/move_guard.rs
deleted file mode 100644
index 3f22302a9..000000000
--- a/crates/assists/src/handlers/move_guard.rs
+++ /dev/null
@@ -1,367 +0,0 @@
1use syntax::{
2 ast::{edit::AstNodeEdit, make, AstNode, BlockExpr, Expr, 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 } $0if 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 {
29// foo()
30// },
31// _ => (),
32// }
33// }
34// ```
35pub(crate) fn move_guard_to_arm_body(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
36 let match_arm = ctx.find_node_at_offset::<MatchArm>()?;
37 let guard = match_arm.guard()?;
38 let space_before_guard = guard.syntax().prev_sibling_or_token();
39
40 let guard_condition = guard.expr()?;
41 let arm_expr = match_arm.expr()?;
42 let if_expr = make::expr_if(
43 make::condition(guard_condition, None),
44 make::block_expr(None, Some(arm_expr.clone())),
45 None,
46 )
47 .indent(arm_expr.indent_level());
48
49 let target = guard.syntax().text_range();
50 acc.add(
51 AssistId("move_guard_to_arm_body", AssistKind::RefactorRewrite),
52 "Move guard to arm body",
53 target,
54 |edit| {
55 match space_before_guard {
56 Some(element) if element.kind() == WHITESPACE => {
57 edit.delete(element.text_range());
58 }
59 _ => (),
60 };
61
62 edit.delete(guard.syntax().text_range());
63 edit.replace_ast(arm_expr, if_expr);
64 },
65 )
66}
67
68// Assist: move_arm_cond_to_match_guard
69//
70// Moves if expression from match arm body into a guard.
71//
72// ```
73// enum Action { Move { distance: u32 }, Stop }
74//
75// fn handle(action: Action) {
76// match action {
77// Action::Move { distance } => $0if distance > 10 { foo() },
78// _ => (),
79// }
80// }
81// ```
82// ->
83// ```
84// enum Action { Move { distance: u32 }, Stop }
85//
86// fn handle(action: Action) {
87// match action {
88// Action::Move { distance } if distance > 10 => foo(),
89// _ => (),
90// }
91// }
92// ```
93pub(crate) fn move_arm_cond_to_match_guard(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
94 let match_arm: MatchArm = ctx.find_node_at_offset::<MatchArm>()?;
95 let match_pat = match_arm.pat()?;
96 let arm_body = match_arm.expr()?;
97
98 let mut replace_node = None;
99 let if_expr: IfExpr = IfExpr::cast(arm_body.syntax().clone()).or_else(|| {
100 let block_expr = BlockExpr::cast(arm_body.syntax().clone())?;
101 if let Expr::IfExpr(e) = block_expr.tail_expr()? {
102 replace_node = Some(block_expr.syntax().clone());
103 Some(e)
104 } else {
105 None
106 }
107 })?;
108 let replace_node = replace_node.unwrap_or_else(|| if_expr.syntax().clone());
109
110 let cond = if_expr.condition()?;
111 let then_block = if_expr.then_branch()?;
112
113 // Not support if with else branch
114 if if_expr.else_branch().is_some() {
115 return None;
116 }
117 // Not support moving if let to arm guard
118 if cond.pat().is_some() {
119 return None;
120 }
121
122 let buf = format!(" if {}", cond.syntax().text());
123
124 acc.add(
125 AssistId("move_arm_cond_to_match_guard", AssistKind::RefactorRewrite),
126 "Move condition to match guard",
127 replace_node.text_range(),
128 |edit| {
129 let then_only_expr = then_block.statements().next().is_none();
130
131 match &then_block.tail_expr() {
132 Some(then_expr) if then_only_expr => {
133 edit.replace(replace_node.text_range(), then_expr.syntax().text())
134 }
135 _ if replace_node != *if_expr.syntax() => {
136 // Dedent because if_expr is in a BlockExpr
137 let replace_with = then_block.dedent(1.into()).syntax().text();
138 edit.replace(replace_node.text_range(), replace_with)
139 }
140 _ => edit.replace(replace_node.text_range(), then_block.syntax().text()),
141 }
142
143 edit.insert(match_pat.syntax().text_range().end(), buf);
144 },
145 )
146}
147
148#[cfg(test)]
149mod tests {
150 use super::*;
151
152 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
153
154 #[test]
155 fn move_guard_to_arm_body_target() {
156 check_assist_target(
157 move_guard_to_arm_body,
158 r#"
159fn main() {
160 match 92 {
161 x $0if x > 10 => false,
162 _ => true
163 }
164}
165"#,
166 r#"if x > 10"#,
167 );
168 }
169
170 #[test]
171 fn move_guard_to_arm_body_works() {
172 check_assist(
173 move_guard_to_arm_body,
174 r#"
175fn main() {
176 match 92 {
177 x $0if x > 10 => false,
178 _ => true
179 }
180}
181"#,
182 r#"
183fn main() {
184 match 92 {
185 x => if x > 10 {
186 false
187 },
188 _ => true
189 }
190}
191"#,
192 );
193 }
194
195 #[test]
196 fn move_guard_to_arm_body_works_complex_match() {
197 check_assist(
198 move_guard_to_arm_body,
199 r#"
200fn main() {
201 match 92 {
202 $0x @ 4 | x @ 5 if x > 5 => true,
203 _ => false
204 }
205}
206"#,
207 r#"
208fn main() {
209 match 92 {
210 x @ 4 | x @ 5 => if x > 5 {
211 true
212 },
213 _ => false
214 }
215}
216"#,
217 );
218 }
219
220 #[test]
221 fn move_arm_cond_to_match_guard_works() {
222 check_assist(
223 move_arm_cond_to_match_guard,
224 r#"
225fn main() {
226 match 92 {
227 x => if x > 10 { $0false },
228 _ => true
229 }
230}
231"#,
232 r#"
233fn main() {
234 match 92 {
235 x if x > 10 => false,
236 _ => true
237 }
238}
239"#,
240 );
241 }
242
243 #[test]
244 fn move_arm_cond_in_block_to_match_guard_works() {
245 check_assist(
246 move_arm_cond_to_match_guard,
247 r#"
248fn main() {
249 match 92 {
250 x => {
251 $0if x > 10 {
252 false
253 }
254 },
255 _ => true
256 }
257}
258"#,
259 r#"
260fn main() {
261 match 92 {
262 x if x > 10 => false,
263 _ => true
264 }
265}
266"#,
267 );
268 }
269
270 #[test]
271 fn move_arm_cond_to_match_guard_if_let_not_works() {
272 check_assist_not_applicable(
273 move_arm_cond_to_match_guard,
274 r#"
275fn main() {
276 match 92 {
277 x => if let 62 = x { $0false },
278 _ => true
279 }
280}
281"#,
282 );
283 }
284
285 #[test]
286 fn move_arm_cond_to_match_guard_if_empty_body_works() {
287 check_assist(
288 move_arm_cond_to_match_guard,
289 r#"
290fn main() {
291 match 92 {
292 x => if x > 10 { $0 },
293 _ => true
294 }
295}
296"#,
297 r#"
298fn main() {
299 match 92 {
300 x if x > 10 => { },
301 _ => true
302 }
303}
304"#,
305 );
306 }
307
308 #[test]
309 fn move_arm_cond_to_match_guard_if_multiline_body_works() {
310 check_assist(
311 move_arm_cond_to_match_guard,
312 r#"
313fn main() {
314 match 92 {
315 x => if x > 10 {
316 92;$0
317 false
318 },
319 _ => true
320 }
321}
322"#,
323 r#"
324fn main() {
325 match 92 {
326 x if x > 10 => {
327 92;
328 false
329 },
330 _ => true
331 }
332}
333"#,
334 );
335 }
336
337 #[test]
338 fn move_arm_cond_in_block_to_match_guard_if_multiline_body_works() {
339 check_assist(
340 move_arm_cond_to_match_guard,
341 r#"
342fn main() {
343 match 92 {
344 x => {
345 if x > 10 {
346 92;$0
347 false
348 }
349 }
350 _ => true
351 }
352}
353"#,
354 r#"
355fn main() {
356 match 92 {
357 x if x > 10 => {
358 92;
359 false
360 }
361 _ => true
362 }
363}
364"#,
365 )
366 }
367}
diff --git a/crates/assists/src/handlers/move_module_to_file.rs b/crates/assists/src/handlers/move_module_to_file.rs
deleted file mode 100644
index 9d8579f47..000000000
--- a/crates/assists/src/handlers/move_module_to_file.rs
+++ /dev/null
@@ -1,145 +0,0 @@
1use ast::edit::IndentLevel;
2use ide_db::base_db::AnchoredPathBuf;
3use syntax::{
4 ast::{self, edit::AstNodeEdit, NameOwner},
5 AstNode, TextRange,
6};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: move_module_to_file
12//
13// Moves inline module's contents to a separate file.
14//
15// ```
16// mod $0foo {
17// fn t() {}
18// }
19// ```
20// ->
21// ```
22// mod foo;
23// ```
24pub(crate) fn move_module_to_file(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
25 let module_ast = ctx.find_node_at_offset::<ast::Module>()?;
26 let module_items = module_ast.item_list()?;
27
28 let l_curly_offset = module_items.syntax().text_range().start();
29 if l_curly_offset <= ctx.offset() {
30 mark::hit!(available_before_curly);
31 return None;
32 }
33 let target = TextRange::new(module_ast.syntax().text_range().start(), l_curly_offset);
34
35 let module_name = module_ast.name()?;
36
37 let module_def = ctx.sema.to_def(&module_ast)?;
38 let parent_module = module_def.parent(ctx.db())?;
39
40 acc.add(
41 AssistId("move_module_to_file", AssistKind::RefactorExtract),
42 "Extract module to file",
43 target,
44 |builder| {
45 let path = {
46 let dir = match parent_module.name(ctx.db()) {
47 Some(name) if !parent_module.is_mod_rs(ctx.db()) => format!("{}/", name),
48 _ => String::new(),
49 };
50 format!("./{}{}.rs", dir, module_name)
51 };
52 let contents = {
53 let items = module_items.dedent(IndentLevel(1)).to_string();
54 let mut items =
55 items.trim_start_matches('{').trim_end_matches('}').trim().to_string();
56 if !items.is_empty() {
57 items.push('\n');
58 }
59 items
60 };
61
62 builder.replace(module_ast.syntax().text_range(), format!("mod {};", module_name));
63
64 let dst = AnchoredPathBuf { anchor: ctx.frange.file_id, path };
65 builder.create_file(dst, contents);
66 },
67 )
68}
69
70#[cfg(test)]
71mod tests {
72 use crate::tests::{check_assist, check_assist_not_applicable};
73
74 use super::*;
75
76 #[test]
77 fn extract_from_root() {
78 check_assist(
79 move_module_to_file,
80 r#"
81mod $0tests {
82 #[test] fn t() {}
83}
84"#,
85 r#"
86//- /main.rs
87mod tests;
88//- /tests.rs
89#[test] fn t() {}
90"#,
91 );
92 }
93
94 #[test]
95 fn extract_from_submodule() {
96 check_assist(
97 move_module_to_file,
98 r#"
99//- /main.rs
100mod submod;
101//- /submod.rs
102$0mod inner {
103 fn f() {}
104}
105fn g() {}
106"#,
107 r#"
108//- /submod.rs
109mod inner;
110fn g() {}
111//- /submod/inner.rs
112fn f() {}
113"#,
114 );
115 }
116
117 #[test]
118 fn extract_from_mod_rs() {
119 check_assist(
120 move_module_to_file,
121 r#"
122//- /main.rs
123mod submodule;
124//- /submodule/mod.rs
125mod inner$0 {
126 fn f() {}
127}
128fn g() {}
129"#,
130 r#"
131//- /submodule/mod.rs
132mod inner;
133fn g() {}
134//- /submodule/inner.rs
135fn f() {}
136"#,
137 );
138 }
139
140 #[test]
141 fn available_before_curly() {
142 mark::check!(available_before_curly);
143 check_assist_not_applicable(move_module_to_file, r#"mod m { $0 }"#);
144 }
145}
diff --git a/crates/assists/src/handlers/pull_assignment_up.rs b/crates/assists/src/handlers/pull_assignment_up.rs
deleted file mode 100644
index 13e1cb754..000000000
--- a/crates/assists/src/handlers/pull_assignment_up.rs
+++ /dev/null
@@ -1,400 +0,0 @@
1use syntax::{
2 ast::{self, edit::AstNodeEdit, make},
3 AstNode,
4};
5use test_utils::mark;
6
7use crate::{
8 assist_context::{AssistContext, Assists},
9 AssistId, AssistKind,
10};
11
12// Assist: pull_assignment_up
13//
14// Extracts variable assignment to outside an if or match statement.
15//
16// ```
17// fn main() {
18// let mut foo = 6;
19//
20// if true {
21// $0foo = 5;
22// } else {
23// foo = 4;
24// }
25// }
26// ```
27// ->
28// ```
29// fn main() {
30// let mut foo = 6;
31//
32// foo = if true {
33// 5
34// } else {
35// 4
36// };
37// }
38// ```
39pub(crate) fn pull_assignment_up(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let assign_expr = ctx.find_node_at_offset::<ast::BinExpr>()?;
41 let name_expr = if assign_expr.op_kind()? == ast::BinOp::Assignment {
42 assign_expr.lhs()?
43 } else {
44 return None;
45 };
46
47 let (old_stmt, new_stmt) = if let Some(if_expr) = ctx.find_node_at_offset::<ast::IfExpr>() {
48 (
49 ast::Expr::cast(if_expr.syntax().to_owned())?,
50 exprify_if(&if_expr, &ctx.sema, &name_expr)?.indent(if_expr.indent_level()),
51 )
52 } else if let Some(match_expr) = ctx.find_node_at_offset::<ast::MatchExpr>() {
53 (
54 ast::Expr::cast(match_expr.syntax().to_owned())?,
55 exprify_match(&match_expr, &ctx.sema, &name_expr)?,
56 )
57 } else {
58 return None;
59 };
60
61 let expr_stmt = make::expr_stmt(new_stmt);
62
63 acc.add(
64 AssistId("pull_assignment_up", AssistKind::RefactorExtract),
65 "Pull assignment up",
66 old_stmt.syntax().text_range(),
67 move |edit| {
68 edit.replace(old_stmt.syntax().text_range(), format!("{} = {};", name_expr, expr_stmt));
69 },
70 )
71}
72
73fn exprify_match(
74 match_expr: &ast::MatchExpr,
75 sema: &hir::Semantics<ide_db::RootDatabase>,
76 name: &ast::Expr,
77) -> Option<ast::Expr> {
78 let new_arm_list = match_expr
79 .match_arm_list()?
80 .arms()
81 .map(|arm| {
82 if let ast::Expr::BlockExpr(block) = arm.expr()? {
83 let new_block = exprify_block(&block, sema, name)?.indent(block.indent_level());
84 Some(arm.replace_descendant(block, new_block))
85 } else {
86 None
87 }
88 })
89 .collect::<Option<Vec<_>>>()?;
90 let new_arm_list = match_expr
91 .match_arm_list()?
92 .replace_descendants(match_expr.match_arm_list()?.arms().zip(new_arm_list));
93 Some(make::expr_match(match_expr.expr()?, new_arm_list))
94}
95
96fn exprify_if(
97 statement: &ast::IfExpr,
98 sema: &hir::Semantics<ide_db::RootDatabase>,
99 name: &ast::Expr,
100) -> Option<ast::Expr> {
101 let then_branch = exprify_block(&statement.then_branch()?, sema, name)?;
102 let else_branch = match statement.else_branch()? {
103 ast::ElseBranch::Block(ref block) => {
104 ast::ElseBranch::Block(exprify_block(block, sema, name)?)
105 }
106 ast::ElseBranch::IfExpr(expr) => {
107 mark::hit!(test_pull_assignment_up_chained_if);
108 ast::ElseBranch::IfExpr(ast::IfExpr::cast(
109 exprify_if(&expr, sema, name)?.syntax().to_owned(),
110 )?)
111 }
112 };
113 Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
114}
115
116fn exprify_block(
117 block: &ast::BlockExpr,
118 sema: &hir::Semantics<ide_db::RootDatabase>,
119 name: &ast::Expr,
120) -> Option<ast::BlockExpr> {
121 if block.tail_expr().is_some() {
122 return None;
123 }
124
125 let mut stmts: Vec<_> = block.statements().collect();
126 let stmt = stmts.pop()?;
127
128 if let ast::Stmt::ExprStmt(stmt) = stmt {
129 if let ast::Expr::BinExpr(expr) = stmt.expr()? {
130 if expr.op_kind()? == ast::BinOp::Assignment && is_equivalent(sema, &expr.lhs()?, name)
131 {
132 // The last statement in the block is an assignment to the name we want
133 return Some(make::block_expr(stmts, Some(expr.rhs()?)));
134 }
135 }
136 }
137 None
138}
139
140fn is_equivalent(
141 sema: &hir::Semantics<ide_db::RootDatabase>,
142 expr0: &ast::Expr,
143 expr1: &ast::Expr,
144) -> bool {
145 match (expr0, expr1) {
146 (ast::Expr::FieldExpr(field_expr0), ast::Expr::FieldExpr(field_expr1)) => {
147 mark::hit!(test_pull_assignment_up_field_assignment);
148 sema.resolve_field(field_expr0) == sema.resolve_field(field_expr1)
149 }
150 (ast::Expr::PathExpr(path0), ast::Expr::PathExpr(path1)) => {
151 let path0 = path0.path();
152 let path1 = path1.path();
153 if let (Some(path0), Some(path1)) = (path0, path1) {
154 sema.resolve_path(&path0) == sema.resolve_path(&path1)
155 } else {
156 false
157 }
158 }
159 _ => false,
160 }
161}
162
163#[cfg(test)]
164mod tests {
165 use super::*;
166
167 use crate::tests::{check_assist, check_assist_not_applicable};
168
169 #[test]
170 fn test_pull_assignment_up_if() {
171 check_assist(
172 pull_assignment_up,
173 r#"
174fn foo() {
175 let mut a = 1;
176
177 if true {
178 $0a = 2;
179 } else {
180 a = 3;
181 }
182}"#,
183 r#"
184fn foo() {
185 let mut a = 1;
186
187 a = if true {
188 2
189 } else {
190 3
191 };
192}"#,
193 );
194 }
195
196 #[test]
197 fn test_pull_assignment_up_match() {
198 check_assist(
199 pull_assignment_up,
200 r#"
201fn foo() {
202 let mut a = 1;
203
204 match 1 {
205 1 => {
206 $0a = 2;
207 },
208 2 => {
209 a = 3;
210 },
211 3 => {
212 a = 4;
213 }
214 }
215}"#,
216 r#"
217fn foo() {
218 let mut a = 1;
219
220 a = match 1 {
221 1 => {
222 2
223 },
224 2 => {
225 3
226 },
227 3 => {
228 4
229 }
230 };
231}"#,
232 );
233 }
234
235 #[test]
236 fn test_pull_assignment_up_not_last_not_applicable() {
237 check_assist_not_applicable(
238 pull_assignment_up,
239 r#"
240fn foo() {
241 let mut a = 1;
242
243 if true {
244 $0a = 2;
245 b = a;
246 } else {
247 a = 3;
248 }
249}"#,
250 )
251 }
252
253 #[test]
254 fn test_pull_assignment_up_chained_if() {
255 mark::check!(test_pull_assignment_up_chained_if);
256 check_assist(
257 pull_assignment_up,
258 r#"
259fn foo() {
260 let mut a = 1;
261
262 if true {
263 $0a = 2;
264 } else if false {
265 a = 3;
266 } else {
267 a = 4;
268 }
269}"#,
270 r#"
271fn foo() {
272 let mut a = 1;
273
274 a = if true {
275 2
276 } else if false {
277 3
278 } else {
279 4
280 };
281}"#,
282 );
283 }
284
285 #[test]
286 fn test_pull_assignment_up_retains_stmts() {
287 check_assist(
288 pull_assignment_up,
289 r#"
290fn foo() {
291 let mut a = 1;
292
293 if true {
294 let b = 2;
295 $0a = 2;
296 } else {
297 let b = 3;
298 a = 3;
299 }
300}"#,
301 r#"
302fn foo() {
303 let mut a = 1;
304
305 a = if true {
306 let b = 2;
307 2
308 } else {
309 let b = 3;
310 3
311 };
312}"#,
313 )
314 }
315
316 #[test]
317 fn pull_assignment_up_let_stmt_not_applicable() {
318 check_assist_not_applicable(
319 pull_assignment_up,
320 r#"
321fn foo() {
322 let mut a = 1;
323
324 let b = if true {
325 $0a = 2
326 } else {
327 a = 3
328 };
329}"#,
330 )
331 }
332
333 #[test]
334 fn pull_assignment_up_if_missing_assigment_not_applicable() {
335 check_assist_not_applicable(
336 pull_assignment_up,
337 r#"
338fn foo() {
339 let mut a = 1;
340
341 if true {
342 $0a = 2;
343 } else {}
344}"#,
345 )
346 }
347
348 #[test]
349 fn pull_assignment_up_match_missing_assigment_not_applicable() {
350 check_assist_not_applicable(
351 pull_assignment_up,
352 r#"
353fn foo() {
354 let mut a = 1;
355
356 match 1 {
357 1 => {
358 $0a = 2;
359 },
360 2 => {
361 a = 3;
362 },
363 3 => {},
364 }
365}"#,
366 )
367 }
368
369 #[test]
370 fn test_pull_assignment_up_field_assignment() {
371 mark::check!(test_pull_assignment_up_field_assignment);
372 check_assist(
373 pull_assignment_up,
374 r#"
375struct A(usize);
376
377fn foo() {
378 let mut a = A(1);
379
380 if true {
381 $0a.0 = 2;
382 } else {
383 a.0 = 3;
384 }
385}"#,
386 r#"
387struct A(usize);
388
389fn foo() {
390 let mut a = A(1);
391
392 a.0 = if true {
393 2
394 } else {
395 3
396 };
397}"#,
398 )
399 }
400}
diff --git a/crates/assists/src/handlers/qualify_path.rs b/crates/assists/src/handlers/qualify_path.rs
deleted file mode 100644
index a7d9fd4dc..000000000
--- a/crates/assists/src/handlers/qualify_path.rs
+++ /dev/null
@@ -1,1212 +0,0 @@
1use std::iter;
2
3use hir::AsName;
4use ide_db::helpers::{
5 import_assets::{ImportAssets, ImportCandidate},
6 mod_path_to_ast,
7};
8use ide_db::RootDatabase;
9use syntax::{
10 ast,
11 ast::{make, ArgListOwner},
12 AstNode,
13};
14use test_utils::mark;
15
16use crate::{
17 assist_context::{AssistContext, Assists},
18 AssistId, AssistKind, GroupLabel,
19};
20
21// Assist: qualify_path
22//
23// If the name is unresolved, provides all possible qualified paths for it.
24//
25// ```
26// fn main() {
27// let map = HashMap$0::new();
28// }
29// # pub mod std { pub mod collections { pub struct HashMap { } } }
30// ```
31// ->
32// ```
33// fn main() {
34// let map = std::collections::HashMap::new();
35// }
36// # pub mod std { pub mod collections { pub struct HashMap { } } }
37// ```
38pub(crate) fn qualify_path(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39 let import_assets =
40 if let Some(path_under_caret) = ctx.find_node_at_offset_with_descend::<ast::Path>() {
41 ImportAssets::for_regular_path(path_under_caret, &ctx.sema)
42 } else if let Some(method_under_caret) =
43 ctx.find_node_at_offset_with_descend::<ast::MethodCallExpr>()
44 {
45 ImportAssets::for_method_call(method_under_caret, &ctx.sema)
46 } else {
47 None
48 }?;
49 let proposed_imports = import_assets.search_for_relative_paths(&ctx.sema);
50 if proposed_imports.is_empty() {
51 return None;
52 }
53
54 let candidate = import_assets.import_candidate();
55 let range = ctx.sema.original_range(import_assets.syntax_under_caret()).range;
56
57 let qualify_candidate = match candidate {
58 ImportCandidate::Path(candidate) => {
59 if candidate.qualifier.is_some() {
60 mark::hit!(qualify_path_qualifier_start);
61 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
62 let (prev_segment, segment) = (path.qualifier()?.segment()?, path.segment()?);
63 QualifyCandidate::QualifierStart(segment, prev_segment.generic_arg_list())
64 } else {
65 mark::hit!(qualify_path_unqualified_name);
66 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
67 let generics = path.segment()?.generic_arg_list();
68 QualifyCandidate::UnqualifiedName(generics)
69 }
70 }
71 ImportCandidate::TraitAssocItem(_) => {
72 mark::hit!(qualify_path_trait_assoc_item);
73 let path = ast::Path::cast(import_assets.syntax_under_caret().clone())?;
74 let (qualifier, segment) = (path.qualifier()?, path.segment()?);
75 QualifyCandidate::TraitAssocItem(qualifier, segment)
76 }
77 ImportCandidate::TraitMethod(_) => {
78 mark::hit!(qualify_path_trait_method);
79 let mcall_expr = ast::MethodCallExpr::cast(import_assets.syntax_under_caret().clone())?;
80 QualifyCandidate::TraitMethod(ctx.sema.db, mcall_expr)
81 }
82 };
83
84 let group_label = group_label(candidate);
85 for (import, item) in proposed_imports {
86 acc.add_group(
87 &group_label,
88 AssistId("qualify_path", AssistKind::QuickFix),
89 label(candidate, &import),
90 range,
91 |builder| {
92 qualify_candidate.qualify(
93 |replace_with: String| builder.replace(range, replace_with),
94 import,
95 item,
96 )
97 },
98 );
99 }
100 Some(())
101}
102
103enum QualifyCandidate<'db> {
104 QualifierStart(ast::PathSegment, Option<ast::GenericArgList>),
105 UnqualifiedName(Option<ast::GenericArgList>),
106 TraitAssocItem(ast::Path, ast::PathSegment),
107 TraitMethod(&'db RootDatabase, ast::MethodCallExpr),
108}
109
110impl QualifyCandidate<'_> {
111 fn qualify(&self, mut replacer: impl FnMut(String), import: hir::ModPath, item: hir::ItemInNs) {
112 let import = mod_path_to_ast(&import);
113 match self {
114 QualifyCandidate::QualifierStart(segment, generics) => {
115 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
116 replacer(format!("{}{}::{}", import, generics, segment));
117 }
118 QualifyCandidate::UnqualifiedName(generics) => {
119 let generics = generics.as_ref().map_or_else(String::new, ToString::to_string);
120 replacer(format!("{}{}", import.to_string(), generics));
121 }
122 QualifyCandidate::TraitAssocItem(qualifier, segment) => {
123 replacer(format!("<{} as {}>::{}", qualifier, import, segment));
124 }
125 &QualifyCandidate::TraitMethod(db, ref mcall_expr) => {
126 Self::qualify_trait_method(db, mcall_expr, replacer, import, item);
127 }
128 }
129 }
130
131 fn qualify_trait_method(
132 db: &RootDatabase,
133 mcall_expr: &ast::MethodCallExpr,
134 mut replacer: impl FnMut(String),
135 import: ast::Path,
136 item: hir::ItemInNs,
137 ) -> Option<()> {
138 let receiver = mcall_expr.receiver()?;
139 let trait_method_name = mcall_expr.name_ref()?;
140 let generics =
141 mcall_expr.generic_arg_list().as_ref().map_or_else(String::new, ToString::to_string);
142 let arg_list = mcall_expr.arg_list().map(|arg_list| arg_list.args());
143 let trait_ = item_as_trait(item)?;
144 let method = find_trait_method(db, trait_, &trait_method_name)?;
145 if let Some(self_access) = method.self_param(db).map(|sp| sp.access(db)) {
146 let receiver = match self_access {
147 hir::Access::Shared => make::expr_ref(receiver, false),
148 hir::Access::Exclusive => make::expr_ref(receiver, true),
149 hir::Access::Owned => receiver,
150 };
151 replacer(format!(
152 "{}::{}{}{}",
153 import,
154 trait_method_name,
155 generics,
156 match arg_list {
157 Some(args) => make::arg_list(iter::once(receiver).chain(args)),
158 None => make::arg_list(iter::once(receiver)),
159 }
160 ));
161 }
162 Some(())
163 }
164}
165
166fn find_trait_method(
167 db: &RootDatabase,
168 trait_: hir::Trait,
169 trait_method_name: &ast::NameRef,
170) -> Option<hir::Function> {
171 if let Some(hir::AssocItem::Function(method)) =
172 trait_.items(db).into_iter().find(|item: &hir::AssocItem| {
173 item.name(db).map(|name| name == trait_method_name.as_name()).unwrap_or(false)
174 })
175 {
176 Some(method)
177 } else {
178 None
179 }
180}
181
182fn item_as_trait(item: hir::ItemInNs) -> Option<hir::Trait> {
183 if let hir::ModuleDef::Trait(trait_) = hir::ModuleDef::from(item.as_module_def_id()?) {
184 Some(trait_)
185 } else {
186 None
187 }
188}
189
190fn group_label(candidate: &ImportCandidate) -> GroupLabel {
191 let name = match candidate {
192 ImportCandidate::Path(it) => &it.name,
193 ImportCandidate::TraitAssocItem(it) | ImportCandidate::TraitMethod(it) => &it.name,
194 };
195 GroupLabel(format!("Qualify {}", name))
196}
197
198fn label(candidate: &ImportCandidate, import: &hir::ModPath) -> String {
199 match candidate {
200 ImportCandidate::Path(candidate) => {
201 if candidate.qualifier.is_some() {
202 format!("Qualify with `{}`", &import)
203 } else {
204 format!("Qualify as `{}`", &import)
205 }
206 }
207 ImportCandidate::TraitAssocItem(_) => format!("Qualify `{}`", &import),
208 ImportCandidate::TraitMethod(_) => format!("Qualify with cast as `{}`", &import),
209 }
210}
211
212#[cfg(test)]
213mod tests {
214 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
215
216 use super::*;
217
218 #[test]
219 fn applicable_when_found_an_import_partial() {
220 mark::check!(qualify_path_unqualified_name);
221 check_assist(
222 qualify_path,
223 r"
224 mod std {
225 pub mod fmt {
226 pub struct Formatter;
227 }
228 }
229
230 use std::fmt;
231
232 $0Formatter
233 ",
234 r"
235 mod std {
236 pub mod fmt {
237 pub struct Formatter;
238 }
239 }
240
241 use std::fmt;
242
243 fmt::Formatter
244 ",
245 );
246 }
247
248 #[test]
249 fn applicable_when_found_an_import() {
250 check_assist(
251 qualify_path,
252 r"
253 $0PubStruct
254
255 pub mod PubMod {
256 pub struct PubStruct;
257 }
258 ",
259 r"
260 PubMod::PubStruct
261
262 pub mod PubMod {
263 pub struct PubStruct;
264 }
265 ",
266 );
267 }
268
269 #[test]
270 fn applicable_in_macros() {
271 check_assist(
272 qualify_path,
273 r"
274 macro_rules! foo {
275 ($i:ident) => { fn foo(a: $i) {} }
276 }
277 foo!(Pub$0Struct);
278
279 pub mod PubMod {
280 pub struct PubStruct;
281 }
282 ",
283 r"
284 macro_rules! foo {
285 ($i:ident) => { fn foo(a: $i) {} }
286 }
287 foo!(PubMod::PubStruct);
288
289 pub mod PubMod {
290 pub struct PubStruct;
291 }
292 ",
293 );
294 }
295
296 #[test]
297 fn applicable_when_found_multiple_imports() {
298 check_assist(
299 qualify_path,
300 r"
301 PubSt$0ruct
302
303 pub mod PubMod1 {
304 pub struct PubStruct;
305 }
306 pub mod PubMod2 {
307 pub struct PubStruct;
308 }
309 pub mod PubMod3 {
310 pub struct PubStruct;
311 }
312 ",
313 r"
314 PubMod3::PubStruct
315
316 pub mod PubMod1 {
317 pub struct PubStruct;
318 }
319 pub mod PubMod2 {
320 pub struct PubStruct;
321 }
322 pub mod PubMod3 {
323 pub struct PubStruct;
324 }
325 ",
326 );
327 }
328
329 #[test]
330 fn not_applicable_for_already_imported_types() {
331 check_assist_not_applicable(
332 qualify_path,
333 r"
334 use PubMod::PubStruct;
335
336 PubStruct$0
337
338 pub mod PubMod {
339 pub struct PubStruct;
340 }
341 ",
342 );
343 }
344
345 #[test]
346 fn not_applicable_for_types_with_private_paths() {
347 check_assist_not_applicable(
348 qualify_path,
349 r"
350 PrivateStruct$0
351
352 pub mod PubMod {
353 struct PrivateStruct;
354 }
355 ",
356 );
357 }
358
359 #[test]
360 fn not_applicable_when_no_imports_found() {
361 check_assist_not_applicable(
362 qualify_path,
363 "
364 PubStruct$0",
365 );
366 }
367
368 #[test]
369 fn not_applicable_in_import_statements() {
370 check_assist_not_applicable(
371 qualify_path,
372 r"
373 use PubStruct$0;
374
375 pub mod PubMod {
376 pub struct PubStruct;
377 }",
378 );
379 }
380
381 #[test]
382 fn qualify_function() {
383 check_assist(
384 qualify_path,
385 r"
386 test_function$0
387
388 pub mod PubMod {
389 pub fn test_function() {};
390 }
391 ",
392 r"
393 PubMod::test_function
394
395 pub mod PubMod {
396 pub fn test_function() {};
397 }
398 ",
399 );
400 }
401
402 #[test]
403 fn qualify_macro() {
404 check_assist(
405 qualify_path,
406 r"
407//- /lib.rs crate:crate_with_macro
408#[macro_export]
409macro_rules! foo {
410 () => ()
411}
412
413//- /main.rs crate:main deps:crate_with_macro
414fn main() {
415 foo$0
416}
417",
418 r"
419fn main() {
420 crate_with_macro::foo
421}
422",
423 );
424 }
425
426 #[test]
427 fn qualify_path_target() {
428 check_assist_target(
429 qualify_path,
430 r"
431 struct AssistInfo {
432 group_label: Option<$0GroupLabel>,
433 }
434
435 mod m { pub struct GroupLabel; }
436 ",
437 "GroupLabel",
438 )
439 }
440
441 #[test]
442 fn not_applicable_when_path_start_is_imported() {
443 check_assist_not_applicable(
444 qualify_path,
445 r"
446 pub mod mod1 {
447 pub mod mod2 {
448 pub mod mod3 {
449 pub struct TestStruct;
450 }
451 }
452 }
453
454 use mod1::mod2;
455 fn main() {
456 mod2::mod3::TestStruct$0
457 }
458 ",
459 );
460 }
461
462 #[test]
463 fn not_applicable_for_imported_function() {
464 check_assist_not_applicable(
465 qualify_path,
466 r"
467 pub mod test_mod {
468 pub fn test_function() {}
469 }
470
471 use test_mod::test_function;
472 fn main() {
473 test_function$0
474 }
475 ",
476 );
477 }
478
479 #[test]
480 fn associated_struct_function() {
481 check_assist(
482 qualify_path,
483 r"
484 mod test_mod {
485 pub struct TestStruct {}
486 impl TestStruct {
487 pub fn test_function() {}
488 }
489 }
490
491 fn main() {
492 TestStruct::test_function$0
493 }
494 ",
495 r"
496 mod test_mod {
497 pub struct TestStruct {}
498 impl TestStruct {
499 pub fn test_function() {}
500 }
501 }
502
503 fn main() {
504 test_mod::TestStruct::test_function
505 }
506 ",
507 );
508 }
509
510 #[test]
511 fn associated_struct_const() {
512 mark::check!(qualify_path_qualifier_start);
513 check_assist(
514 qualify_path,
515 r"
516 mod test_mod {
517 pub struct TestStruct {}
518 impl TestStruct {
519 const TEST_CONST: u8 = 42;
520 }
521 }
522
523 fn main() {
524 TestStruct::TEST_CONST$0
525 }
526 ",
527 r"
528 mod test_mod {
529 pub struct TestStruct {}
530 impl TestStruct {
531 const TEST_CONST: u8 = 42;
532 }
533 }
534
535 fn main() {
536 test_mod::TestStruct::TEST_CONST
537 }
538 ",
539 );
540 }
541
542 #[test]
543 fn associated_trait_function() {
544 check_assist(
545 qualify_path,
546 r"
547 mod test_mod {
548 pub trait TestTrait {
549 fn test_function();
550 }
551 pub struct TestStruct {}
552 impl TestTrait for TestStruct {
553 fn test_function() {}
554 }
555 }
556
557 fn main() {
558 test_mod::TestStruct::test_function$0
559 }
560 ",
561 r"
562 mod test_mod {
563 pub trait TestTrait {
564 fn test_function();
565 }
566 pub struct TestStruct {}
567 impl TestTrait for TestStruct {
568 fn test_function() {}
569 }
570 }
571
572 fn main() {
573 <test_mod::TestStruct as test_mod::TestTrait>::test_function
574 }
575 ",
576 );
577 }
578
579 #[test]
580 fn not_applicable_for_imported_trait_for_function() {
581 check_assist_not_applicable(
582 qualify_path,
583 r"
584 mod test_mod {
585 pub trait TestTrait {
586 fn test_function();
587 }
588 pub trait TestTrait2 {
589 fn test_function();
590 }
591 pub enum TestEnum {
592 One,
593 Two,
594 }
595 impl TestTrait2 for TestEnum {
596 fn test_function() {}
597 }
598 impl TestTrait for TestEnum {
599 fn test_function() {}
600 }
601 }
602
603 use test_mod::TestTrait2;
604 fn main() {
605 test_mod::TestEnum::test_function$0;
606 }
607 ",
608 )
609 }
610
611 #[test]
612 fn associated_trait_const() {
613 mark::check!(qualify_path_trait_assoc_item);
614 check_assist(
615 qualify_path,
616 r"
617 mod test_mod {
618 pub trait TestTrait {
619 const TEST_CONST: u8;
620 }
621 pub struct TestStruct {}
622 impl TestTrait for TestStruct {
623 const TEST_CONST: u8 = 42;
624 }
625 }
626
627 fn main() {
628 test_mod::TestStruct::TEST_CONST$0
629 }
630 ",
631 r"
632 mod test_mod {
633 pub trait TestTrait {
634 const TEST_CONST: u8;
635 }
636 pub struct TestStruct {}
637 impl TestTrait for TestStruct {
638 const TEST_CONST: u8 = 42;
639 }
640 }
641
642 fn main() {
643 <test_mod::TestStruct as test_mod::TestTrait>::TEST_CONST
644 }
645 ",
646 );
647 }
648
649 #[test]
650 fn not_applicable_for_imported_trait_for_const() {
651 check_assist_not_applicable(
652 qualify_path,
653 r"
654 mod test_mod {
655 pub trait TestTrait {
656 const TEST_CONST: u8;
657 }
658 pub trait TestTrait2 {
659 const TEST_CONST: f64;
660 }
661 pub enum TestEnum {
662 One,
663 Two,
664 }
665 impl TestTrait2 for TestEnum {
666 const TEST_CONST: f64 = 42.0;
667 }
668 impl TestTrait for TestEnum {
669 const TEST_CONST: u8 = 42;
670 }
671 }
672
673 use test_mod::TestTrait2;
674 fn main() {
675 test_mod::TestEnum::TEST_CONST$0;
676 }
677 ",
678 )
679 }
680
681 #[test]
682 fn trait_method() {
683 mark::check!(qualify_path_trait_method);
684 check_assist(
685 qualify_path,
686 r"
687 mod test_mod {
688 pub trait TestTrait {
689 fn test_method(&self);
690 }
691 pub struct TestStruct {}
692 impl TestTrait for TestStruct {
693 fn test_method(&self) {}
694 }
695 }
696
697 fn main() {
698 let test_struct = test_mod::TestStruct {};
699 test_struct.test_meth$0od()
700 }
701 ",
702 r"
703 mod test_mod {
704 pub trait TestTrait {
705 fn test_method(&self);
706 }
707 pub struct TestStruct {}
708 impl TestTrait for TestStruct {
709 fn test_method(&self) {}
710 }
711 }
712
713 fn main() {
714 let test_struct = test_mod::TestStruct {};
715 test_mod::TestTrait::test_method(&test_struct)
716 }
717 ",
718 );
719 }
720
721 #[test]
722 fn trait_method_multi_params() {
723 check_assist(
724 qualify_path,
725 r"
726 mod test_mod {
727 pub trait TestTrait {
728 fn test_method(&self, test: i32);
729 }
730 pub struct TestStruct {}
731 impl TestTrait for TestStruct {
732 fn test_method(&self, test: i32) {}
733 }
734 }
735
736 fn main() {
737 let test_struct = test_mod::TestStruct {};
738 test_struct.test_meth$0od(42)
739 }
740 ",
741 r"
742 mod test_mod {
743 pub trait TestTrait {
744 fn test_method(&self, test: i32);
745 }
746 pub struct TestStruct {}
747 impl TestTrait for TestStruct {
748 fn test_method(&self, test: i32) {}
749 }
750 }
751
752 fn main() {
753 let test_struct = test_mod::TestStruct {};
754 test_mod::TestTrait::test_method(&test_struct, 42)
755 }
756 ",
757 );
758 }
759
760 #[test]
761 fn trait_method_consume() {
762 check_assist(
763 qualify_path,
764 r"
765 mod test_mod {
766 pub trait TestTrait {
767 fn test_method(self);
768 }
769 pub struct TestStruct {}
770 impl TestTrait for TestStruct {
771 fn test_method(self) {}
772 }
773 }
774
775 fn main() {
776 let test_struct = test_mod::TestStruct {};
777 test_struct.test_meth$0od()
778 }
779 ",
780 r"
781 mod test_mod {
782 pub trait TestTrait {
783 fn test_method(self);
784 }
785 pub struct TestStruct {}
786 impl TestTrait for TestStruct {
787 fn test_method(self) {}
788 }
789 }
790
791 fn main() {
792 let test_struct = test_mod::TestStruct {};
793 test_mod::TestTrait::test_method(test_struct)
794 }
795 ",
796 );
797 }
798
799 #[test]
800 fn trait_method_cross_crate() {
801 check_assist(
802 qualify_path,
803 r"
804 //- /main.rs crate:main deps:dep
805 fn main() {
806 let test_struct = dep::test_mod::TestStruct {};
807 test_struct.test_meth$0od()
808 }
809 //- /dep.rs crate:dep
810 pub mod test_mod {
811 pub trait TestTrait {
812 fn test_method(&self);
813 }
814 pub struct TestStruct {}
815 impl TestTrait for TestStruct {
816 fn test_method(&self) {}
817 }
818 }
819 ",
820 r"
821 fn main() {
822 let test_struct = dep::test_mod::TestStruct {};
823 dep::test_mod::TestTrait::test_method(&test_struct)
824 }
825 ",
826 );
827 }
828
829 #[test]
830 fn assoc_fn_cross_crate() {
831 check_assist(
832 qualify_path,
833 r"
834 //- /main.rs crate:main deps:dep
835 fn main() {
836 dep::test_mod::TestStruct::test_func$0tion
837 }
838 //- /dep.rs crate:dep
839 pub mod test_mod {
840 pub trait TestTrait {
841 fn test_function();
842 }
843 pub struct TestStruct {}
844 impl TestTrait for TestStruct {
845 fn test_function() {}
846 }
847 }
848 ",
849 r"
850 fn main() {
851 <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::test_function
852 }
853 ",
854 );
855 }
856
857 #[test]
858 fn assoc_const_cross_crate() {
859 check_assist(
860 qualify_path,
861 r"
862 //- /main.rs crate:main deps:dep
863 fn main() {
864 dep::test_mod::TestStruct::CONST$0
865 }
866 //- /dep.rs crate:dep
867 pub mod test_mod {
868 pub trait TestTrait {
869 const CONST: bool;
870 }
871 pub struct TestStruct {}
872 impl TestTrait for TestStruct {
873 const CONST: bool = true;
874 }
875 }
876 ",
877 r"
878 fn main() {
879 <dep::test_mod::TestStruct as dep::test_mod::TestTrait>::CONST
880 }
881 ",
882 );
883 }
884
885 #[test]
886 fn assoc_fn_as_method_cross_crate() {
887 check_assist_not_applicable(
888 qualify_path,
889 r"
890 //- /main.rs crate:main deps:dep
891 fn main() {
892 let test_struct = dep::test_mod::TestStruct {};
893 test_struct.test_func$0tion()
894 }
895 //- /dep.rs crate:dep
896 pub mod test_mod {
897 pub trait TestTrait {
898 fn test_function();
899 }
900 pub struct TestStruct {}
901 impl TestTrait for TestStruct {
902 fn test_function() {}
903 }
904 }
905 ",
906 );
907 }
908
909 #[test]
910 fn private_trait_cross_crate() {
911 check_assist_not_applicable(
912 qualify_path,
913 r"
914 //- /main.rs crate:main deps:dep
915 fn main() {
916 let test_struct = dep::test_mod::TestStruct {};
917 test_struct.test_meth$0od()
918 }
919 //- /dep.rs crate:dep
920 pub mod test_mod {
921 trait TestTrait {
922 fn test_method(&self);
923 }
924 pub struct TestStruct {}
925 impl TestTrait for TestStruct {
926 fn test_method(&self) {}
927 }
928 }
929 ",
930 );
931 }
932
933 #[test]
934 fn not_applicable_for_imported_trait_for_method() {
935 check_assist_not_applicable(
936 qualify_path,
937 r"
938 mod test_mod {
939 pub trait TestTrait {
940 fn test_method(&self);
941 }
942 pub trait TestTrait2 {
943 fn test_method(&self);
944 }
945 pub enum TestEnum {
946 One,
947 Two,
948 }
949 impl TestTrait2 for TestEnum {
950 fn test_method(&self) {}
951 }
952 impl TestTrait for TestEnum {
953 fn test_method(&self) {}
954 }
955 }
956
957 use test_mod::TestTrait2;
958 fn main() {
959 let one = test_mod::TestEnum::One;
960 one.test$0_method();
961 }
962 ",
963 )
964 }
965
966 #[test]
967 fn dep_import() {
968 check_assist(
969 qualify_path,
970 r"
971//- /lib.rs crate:dep
972pub struct Struct;
973
974//- /main.rs crate:main deps:dep
975fn main() {
976 Struct$0
977}
978",
979 r"
980fn main() {
981 dep::Struct
982}
983",
984 );
985 }
986
987 #[test]
988 fn whole_segment() {
989 // Tests that only imports whose last segment matches the identifier get suggested.
990 check_assist(
991 qualify_path,
992 r"
993//- /lib.rs crate:dep
994pub mod fmt {
995 pub trait Display {}
996}
997
998pub fn panic_fmt() {}
999
1000//- /main.rs crate:main deps:dep
1001struct S;
1002
1003impl f$0mt::Display for S {}
1004",
1005 r"
1006struct S;
1007
1008impl dep::fmt::Display for S {}
1009",
1010 );
1011 }
1012
1013 #[test]
1014 fn macro_generated() {
1015 // Tests that macro-generated items are suggested from external crates.
1016 check_assist(
1017 qualify_path,
1018 r"
1019//- /lib.rs crate:dep
1020macro_rules! mac {
1021 () => {
1022 pub struct Cheese;
1023 };
1024}
1025
1026mac!();
1027
1028//- /main.rs crate:main deps:dep
1029fn main() {
1030 Cheese$0;
1031}
1032",
1033 r"
1034fn main() {
1035 dep::Cheese;
1036}
1037",
1038 );
1039 }
1040
1041 #[test]
1042 fn casing() {
1043 // Tests that differently cased names don't interfere and we only suggest the matching one.
1044 check_assist(
1045 qualify_path,
1046 r"
1047//- /lib.rs crate:dep
1048pub struct FMT;
1049pub struct fmt;
1050
1051//- /main.rs crate:main deps:dep
1052fn main() {
1053 FMT$0;
1054}
1055",
1056 r"
1057fn main() {
1058 dep::FMT;
1059}
1060",
1061 );
1062 }
1063
1064 #[test]
1065 fn keep_generic_annotations() {
1066 check_assist(
1067 qualify_path,
1068 r"
1069//- /lib.rs crate:dep
1070pub mod generic { pub struct Thing<'a, T>(&'a T); }
1071
1072//- /main.rs crate:main deps:dep
1073fn foo() -> Thin$0g<'static, ()> {}
1074
1075fn main() {}
1076",
1077 r"
1078fn foo() -> dep::generic::Thing<'static, ()> {}
1079
1080fn main() {}
1081",
1082 );
1083 }
1084
1085 #[test]
1086 fn keep_generic_annotations_leading_colon() {
1087 check_assist(
1088 qualify_path,
1089 r"
1090//- /lib.rs crate:dep
1091pub mod generic { pub struct Thing<'a, T>(&'a T); }
1092
1093//- /main.rs crate:main deps:dep
1094fn foo() -> Thin$0g::<'static, ()> {}
1095
1096fn main() {}
1097",
1098 r"
1099fn foo() -> dep::generic::Thing::<'static, ()> {}
1100
1101fn main() {}
1102",
1103 );
1104 }
1105
1106 #[test]
1107 fn associated_struct_const_generic() {
1108 check_assist(
1109 qualify_path,
1110 r"
1111 mod test_mod {
1112 pub struct TestStruct<T> {}
1113 impl<T> TestStruct<T> {
1114 const TEST_CONST: u8 = 42;
1115 }
1116 }
1117
1118 fn main() {
1119 TestStruct::<()>::TEST_CONST$0
1120 }
1121 ",
1122 r"
1123 mod test_mod {
1124 pub struct TestStruct<T> {}
1125 impl<T> TestStruct<T> {
1126 const TEST_CONST: u8 = 42;
1127 }
1128 }
1129
1130 fn main() {
1131 test_mod::TestStruct::<()>::TEST_CONST
1132 }
1133 ",
1134 );
1135 }
1136
1137 #[test]
1138 fn associated_trait_const_generic() {
1139 check_assist(
1140 qualify_path,
1141 r"
1142 mod test_mod {
1143 pub trait TestTrait {
1144 const TEST_CONST: u8;
1145 }
1146 pub struct TestStruct<T> {}
1147 impl<T> TestTrait for TestStruct<T> {
1148 const TEST_CONST: u8 = 42;
1149 }
1150 }
1151
1152 fn main() {
1153 test_mod::TestStruct::<()>::TEST_CONST$0
1154 }
1155 ",
1156 r"
1157 mod test_mod {
1158 pub trait TestTrait {
1159 const TEST_CONST: u8;
1160 }
1161 pub struct TestStruct<T> {}
1162 impl<T> TestTrait for TestStruct<T> {
1163 const TEST_CONST: u8 = 42;
1164 }
1165 }
1166
1167 fn main() {
1168 <test_mod::TestStruct::<()> as test_mod::TestTrait>::TEST_CONST
1169 }
1170 ",
1171 );
1172 }
1173
1174 #[test]
1175 fn trait_method_generic() {
1176 check_assist(
1177 qualify_path,
1178 r"
1179 mod test_mod {
1180 pub trait TestTrait {
1181 fn test_method<T>(&self);
1182 }
1183 pub struct TestStruct {}
1184 impl TestTrait for TestStruct {
1185 fn test_method<T>(&self) {}
1186 }
1187 }
1188
1189 fn main() {
1190 let test_struct = test_mod::TestStruct {};
1191 test_struct.test_meth$0od::<()>()
1192 }
1193 ",
1194 r"
1195 mod test_mod {
1196 pub trait TestTrait {
1197 fn test_method<T>(&self);
1198 }
1199 pub struct TestStruct {}
1200 impl TestTrait for TestStruct {
1201 fn test_method<T>(&self) {}
1202 }
1203 }
1204
1205 fn main() {
1206 let test_struct = test_mod::TestStruct {};
1207 test_mod::TestTrait::test_method::<()>(&test_struct)
1208 }
1209 ",
1210 );
1211 }
1212}
diff --git a/crates/assists/src/handlers/raw_string.rs b/crates/assists/src/handlers/raw_string.rs
deleted file mode 100644
index be963f162..000000000
--- a/crates/assists/src/handlers/raw_string.rs
+++ /dev/null
@@ -1,512 +0,0 @@
1use std::borrow::Cow;
2
3use syntax::{ast, AstToken, TextRange, TextSize};
4use test_utils::mark;
5
6use crate::{AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: make_raw_string
9//
10// Adds `r#` to a plain string literal.
11//
12// ```
13// fn main() {
14// "Hello,$0 World!";
15// }
16// ```
17// ->
18// ```
19// fn main() {
20// r#"Hello, World!"#;
21// }
22// ```
23pub(crate) fn make_raw_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
24 let token = ctx.find_token_at_offset::<ast::String>()?;
25 if token.is_raw() {
26 return None;
27 }
28 let value = token.value()?;
29 let target = token.syntax().text_range();
30 acc.add(
31 AssistId("make_raw_string", AssistKind::RefactorRewrite),
32 "Rewrite as raw string",
33 target,
34 |edit| {
35 let hashes = "#".repeat(required_hashes(&value).max(1));
36 if matches!(value, Cow::Borrowed(_)) {
37 // Avoid replacing the whole string to better position the cursor.
38 edit.insert(token.syntax().text_range().start(), format!("r{}", hashes));
39 edit.insert(token.syntax().text_range().end(), format!("{}", hashes));
40 } else {
41 edit.replace(
42 token.syntax().text_range(),
43 format!("r{}\"{}\"{}", hashes, value, hashes),
44 );
45 }
46 },
47 )
48}
49
50// Assist: make_usual_string
51//
52// Turns a raw string into a plain string.
53//
54// ```
55// fn main() {
56// r#"Hello,$0 "World!""#;
57// }
58// ```
59// ->
60// ```
61// fn main() {
62// "Hello, \"World!\"";
63// }
64// ```
65pub(crate) fn make_usual_string(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
66 let token = ctx.find_token_at_offset::<ast::String>()?;
67 if !token.is_raw() {
68 return None;
69 }
70 let value = token.value()?;
71 let target = token.syntax().text_range();
72 acc.add(
73 AssistId("make_usual_string", AssistKind::RefactorRewrite),
74 "Rewrite as regular string",
75 target,
76 |edit| {
77 // parse inside string to escape `"`
78 let escaped = value.escape_default().to_string();
79 if let Some(offsets) = token.quote_offsets() {
80 if token.text()[offsets.contents - token.syntax().text_range().start()] == escaped {
81 edit.replace(offsets.quotes.0, "\"");
82 edit.replace(offsets.quotes.1, "\"");
83 return;
84 }
85 }
86
87 edit.replace(token.syntax().text_range(), format!("\"{}\"", escaped));
88 },
89 )
90}
91
92// Assist: add_hash
93//
94// Adds a hash to a raw string literal.
95//
96// ```
97// fn main() {
98// r#"Hello,$0 World!"#;
99// }
100// ```
101// ->
102// ```
103// fn main() {
104// r##"Hello, World!"##;
105// }
106// ```
107pub(crate) fn add_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
108 let token = ctx.find_token_at_offset::<ast::String>()?;
109 if !token.is_raw() {
110 return None;
111 }
112 let text_range = token.syntax().text_range();
113 let target = text_range;
114 acc.add(AssistId("add_hash", AssistKind::Refactor), "Add #", target, |edit| {
115 edit.insert(text_range.start() + TextSize::of('r'), "#");
116 edit.insert(text_range.end(), "#");
117 })
118}
119
120// Assist: remove_hash
121//
122// Removes a hash from a raw string literal.
123//
124// ```
125// fn main() {
126// r#"Hello,$0 World!"#;
127// }
128// ```
129// ->
130// ```
131// fn main() {
132// r"Hello, World!";
133// }
134// ```
135pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
136 let token = ctx.find_token_at_offset::<ast::String>()?;
137 if !token.is_raw() {
138 return None;
139 }
140
141 let text = token.text().as_str();
142 if !text.starts_with("r#") && text.ends_with('#') {
143 return None;
144 }
145
146 let existing_hashes = text.chars().skip(1).take_while(|&it| it == '#').count();
147
148 let text_range = token.syntax().text_range();
149 let internal_text = &text[token.text_range_between_quotes()? - text_range.start()];
150
151 if existing_hashes == required_hashes(internal_text) {
152 mark::hit!(cant_remove_required_hash);
153 return None;
154 }
155
156 acc.add(AssistId("remove_hash", AssistKind::RefactorRewrite), "Remove #", text_range, |edit| {
157 edit.delete(TextRange::at(text_range.start() + TextSize::of('r'), TextSize::of('#')));
158 edit.delete(TextRange::new(text_range.end() - TextSize::of('#'), text_range.end()));
159 })
160}
161
162fn required_hashes(s: &str) -> usize {
163 let mut res = 0usize;
164 for idx in s.match_indices('"').map(|(i, _)| i) {
165 let (_, sub) = s.split_at(idx + 1);
166 let n_hashes = sub.chars().take_while(|c| *c == '#').count();
167 res = res.max(n_hashes + 1)
168 }
169 res
170}
171
172#[test]
173fn test_required_hashes() {
174 assert_eq!(0, required_hashes("abc"));
175 assert_eq!(0, required_hashes("###"));
176 assert_eq!(1, required_hashes("\""));
177 assert_eq!(2, required_hashes("\"#abc"));
178 assert_eq!(0, required_hashes("#abc"));
179 assert_eq!(3, required_hashes("#ab\"##c"));
180 assert_eq!(5, required_hashes("#ab\"##\"####c"));
181}
182
183#[cfg(test)]
184mod tests {
185 use test_utils::mark;
186
187 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
188
189 use super::*;
190
191 #[test]
192 fn make_raw_string_target() {
193 check_assist_target(
194 make_raw_string,
195 r#"
196 fn f() {
197 let s = $0"random\nstring";
198 }
199 "#,
200 r#""random\nstring""#,
201 );
202 }
203
204 #[test]
205 fn make_raw_string_works() {
206 check_assist(
207 make_raw_string,
208 r#"
209fn f() {
210 let s = $0"random\nstring";
211}
212"#,
213 r##"
214fn f() {
215 let s = r#"random
216string"#;
217}
218"##,
219 )
220 }
221
222 #[test]
223 fn make_raw_string_works_inside_macros() {
224 check_assist(
225 make_raw_string,
226 r#"
227 fn f() {
228 format!($0"x = {}", 92)
229 }
230 "#,
231 r##"
232 fn f() {
233 format!(r#"x = {}"#, 92)
234 }
235 "##,
236 )
237 }
238
239 #[test]
240 fn make_raw_string_hashes_inside_works() {
241 check_assist(
242 make_raw_string,
243 r###"
244fn f() {
245 let s = $0"#random##\nstring";
246}
247"###,
248 r####"
249fn f() {
250 let s = r#"#random##
251string"#;
252}
253"####,
254 )
255 }
256
257 #[test]
258 fn make_raw_string_closing_hashes_inside_works() {
259 check_assist(
260 make_raw_string,
261 r###"
262fn f() {
263 let s = $0"#random\"##\nstring";
264}
265"###,
266 r####"
267fn f() {
268 let s = r###"#random"##
269string"###;
270}
271"####,
272 )
273 }
274
275 #[test]
276 fn make_raw_string_nothing_to_unescape_works() {
277 check_assist(
278 make_raw_string,
279 r#"
280 fn f() {
281 let s = $0"random string";
282 }
283 "#,
284 r##"
285 fn f() {
286 let s = r#"random string"#;
287 }
288 "##,
289 )
290 }
291
292 #[test]
293 fn make_raw_string_not_works_on_partial_string() {
294 check_assist_not_applicable(
295 make_raw_string,
296 r#"
297 fn f() {
298 let s = "foo$0
299 }
300 "#,
301 )
302 }
303
304 #[test]
305 fn make_usual_string_not_works_on_partial_string() {
306 check_assist_not_applicable(
307 make_usual_string,
308 r#"
309 fn main() {
310 let s = r#"bar$0
311 }
312 "#,
313 )
314 }
315
316 #[test]
317 fn add_hash_target() {
318 check_assist_target(
319 add_hash,
320 r#"
321 fn f() {
322 let s = $0r"random string";
323 }
324 "#,
325 r#"r"random string""#,
326 );
327 }
328
329 #[test]
330 fn add_hash_works() {
331 check_assist(
332 add_hash,
333 r#"
334 fn f() {
335 let s = $0r"random string";
336 }
337 "#,
338 r##"
339 fn f() {
340 let s = r#"random string"#;
341 }
342 "##,
343 )
344 }
345
346 #[test]
347 fn add_more_hash_works() {
348 check_assist(
349 add_hash,
350 r##"
351 fn f() {
352 let s = $0r#"random"string"#;
353 }
354 "##,
355 r###"
356 fn f() {
357 let s = r##"random"string"##;
358 }
359 "###,
360 )
361 }
362
363 #[test]
364 fn add_hash_not_works() {
365 check_assist_not_applicable(
366 add_hash,
367 r#"
368 fn f() {
369 let s = $0"random string";
370 }
371 "#,
372 );
373 }
374
375 #[test]
376 fn remove_hash_target() {
377 check_assist_target(
378 remove_hash,
379 r##"
380 fn f() {
381 let s = $0r#"random string"#;
382 }
383 "##,
384 r##"r#"random string"#"##,
385 );
386 }
387
388 #[test]
389 fn remove_hash_works() {
390 check_assist(
391 remove_hash,
392 r##"fn f() { let s = $0r#"random string"#; }"##,
393 r#"fn f() { let s = r"random string"; }"#,
394 )
395 }
396
397 #[test]
398 fn cant_remove_required_hash() {
399 mark::check!(cant_remove_required_hash);
400 check_assist_not_applicable(
401 remove_hash,
402 r##"
403 fn f() {
404 let s = $0r#"random"str"ing"#;
405 }
406 "##,
407 )
408 }
409
410 #[test]
411 fn remove_more_hash_works() {
412 check_assist(
413 remove_hash,
414 r###"
415 fn f() {
416 let s = $0r##"random string"##;
417 }
418 "###,
419 r##"
420 fn f() {
421 let s = r#"random string"#;
422 }
423 "##,
424 )
425 }
426
427 #[test]
428 fn remove_hash_doesnt_work() {
429 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0"random string"; }"#);
430 }
431
432 #[test]
433 fn remove_hash_no_hash_doesnt_work() {
434 check_assist_not_applicable(remove_hash, r#"fn f() { let s = $0r"random string"; }"#);
435 }
436
437 #[test]
438 fn make_usual_string_target() {
439 check_assist_target(
440 make_usual_string,
441 r##"
442 fn f() {
443 let s = $0r#"random string"#;
444 }
445 "##,
446 r##"r#"random string"#"##,
447 );
448 }
449
450 #[test]
451 fn make_usual_string_works() {
452 check_assist(
453 make_usual_string,
454 r##"
455 fn f() {
456 let s = $0r#"random string"#;
457 }
458 "##,
459 r#"
460 fn f() {
461 let s = "random string";
462 }
463 "#,
464 )
465 }
466
467 #[test]
468 fn make_usual_string_with_quote_works() {
469 check_assist(
470 make_usual_string,
471 r##"
472 fn f() {
473 let s = $0r#"random"str"ing"#;
474 }
475 "##,
476 r#"
477 fn f() {
478 let s = "random\"str\"ing";
479 }
480 "#,
481 )
482 }
483
484 #[test]
485 fn make_usual_string_more_hash_works() {
486 check_assist(
487 make_usual_string,
488 r###"
489 fn f() {
490 let s = $0r##"random string"##;
491 }
492 "###,
493 r##"
494 fn f() {
495 let s = "random string";
496 }
497 "##,
498 )
499 }
500
501 #[test]
502 fn make_usual_string_not_works() {
503 check_assist_not_applicable(
504 make_usual_string,
505 r#"
506 fn f() {
507 let s = $0"random string";
508 }
509 "#,
510 );
511 }
512}
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
deleted file mode 100644
index 6114091f2..000000000
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ /dev/null
@@ -1,421 +0,0 @@
1use syntax::{
2 ast::{self, AstNode},
3 match_ast, SyntaxElement, 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// $0dbg!(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 let new_contents = adjusted_macro_contents(&macro_call)?;
26
27 let macro_text_range = macro_call.syntax().text_range();
28 let macro_end = if macro_call.semicolon_token().is_some() {
29 macro_text_range.end() - TextSize::of(';')
30 } else {
31 macro_text_range.end()
32 };
33
34 acc.add(
35 AssistId("remove_dbg", AssistKind::Refactor),
36 "Remove dbg!()",
37 macro_text_range,
38 |builder| {
39 builder.replace(TextRange::new(macro_text_range.start(), macro_end), new_contents);
40 },
41 )
42}
43
44fn adjusted_macro_contents(macro_call: &ast::MacroCall) -> Option<String> {
45 let contents = get_valid_macrocall_contents(&macro_call, "dbg")?;
46 let macro_text_with_brackets = macro_call.token_tree()?.syntax().text();
47 let macro_text_in_brackets = macro_text_with_brackets.slice(TextRange::new(
48 TextSize::of('('),
49 macro_text_with_brackets.len() - TextSize::of(')'),
50 ));
51
52 Some(
53 if !is_leaf_or_control_flow_expr(macro_call)
54 && needs_parentheses_around_macro_contents(contents)
55 {
56 format!("({})", macro_text_in_brackets)
57 } else {
58 macro_text_in_brackets.to_string()
59 },
60 )
61}
62
63fn is_leaf_or_control_flow_expr(macro_call: &ast::MacroCall) -> bool {
64 macro_call.syntax().next_sibling().is_none()
65 || match macro_call.syntax().parent() {
66 Some(parent) => match_ast! {
67 match parent {
68 ast::Condition(_it) => true,
69 ast::MatchExpr(_it) => true,
70 _ => false,
71 }
72 },
73 None => false,
74 }
75}
76
77/// Verifies that the given macro_call actually matches the given name
78/// and contains proper ending tokens, then returns the contents between the ending tokens
79fn get_valid_macrocall_contents(
80 macro_call: &ast::MacroCall,
81 macro_name: &str,
82) -> Option<Vec<SyntaxElement>> {
83 let path = macro_call.path()?;
84 let name_ref = path.segment()?.name_ref()?;
85
86 // Make sure it is actually a dbg-macro call, dbg followed by !
87 let excl = path.syntax().next_sibling_or_token()?;
88 if name_ref.text() != macro_name || excl.kind() != T![!] {
89 return None;
90 }
91
92 let mut children_with_tokens = macro_call.token_tree()?.syntax().children_with_tokens();
93 let first_child = children_with_tokens.next()?;
94 let mut contents_between_brackets = children_with_tokens.collect::<Vec<_>>();
95 let last_child = contents_between_brackets.pop()?;
96
97 if contents_between_brackets.is_empty() {
98 None
99 } else {
100 match (first_child.kind(), last_child.kind()) {
101 (T!['('], T![')']) | (T!['['], T![']']) | (T!['{'], T!['}']) => {
102 Some(contents_between_brackets)
103 }
104 _ => None,
105 }
106 }
107}
108
109fn needs_parentheses_around_macro_contents(macro_contents: Vec<SyntaxElement>) -> bool {
110 if macro_contents.len() < 2 {
111 return false;
112 }
113 let mut macro_contents = macro_contents.into_iter().peekable();
114 let mut unpaired_brackets_in_contents = Vec::new();
115 while let Some(element) = macro_contents.next() {
116 match element.kind() {
117 T!['('] | T!['['] | T!['{'] => unpaired_brackets_in_contents.push(element),
118 T![')'] => {
119 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['('])
120 {
121 return true;
122 }
123 }
124 T![']'] => {
125 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['['])
126 {
127 return true;
128 }
129 }
130 T!['}'] => {
131 if !matches!(unpaired_brackets_in_contents.pop(), Some(correct_bracket) if correct_bracket.kind() == T!['{'])
132 {
133 return true;
134 }
135 }
136 symbol_kind => {
137 let symbol_not_in_bracket = unpaired_brackets_in_contents.is_empty();
138 if symbol_not_in_bracket
139 && symbol_kind != T![:] // paths
140 && (symbol_kind != T![.] // field/method access
141 || macro_contents // range expressions consist of two SyntaxKind::Dot in macro invocations
142 .peek()
143 .map(|element| element.kind() == T![.])
144 .unwrap_or(false))
145 && symbol_kind != T![?] // try operator
146 && (symbol_kind.is_punct() || symbol_kind == T![as])
147 {
148 return true;
149 }
150 }
151 }
152 }
153 !unpaired_brackets_in_contents.is_empty()
154}
155
156#[cfg(test)]
157mod tests {
158 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
159
160 use super::*;
161
162 #[test]
163 fn test_remove_dbg() {
164 check_assist(remove_dbg, "$0dbg!(1 + 1)", "1 + 1");
165
166 check_assist(remove_dbg, "dbg!$0((1 + 1))", "(1 + 1)");
167
168 check_assist(remove_dbg, "dbg!(1 $0+ 1)", "1 + 1");
169
170 check_assist(remove_dbg, "let _ = $0dbg!(1 + 1)", "let _ = 1 + 1");
171
172 check_assist(
173 remove_dbg,
174 "
175fn foo(n: usize) {
176 if let Some(_) = dbg!(n.$0checked_sub(4)) {
177 // ...
178 }
179}
180",
181 "
182fn foo(n: usize) {
183 if let Some(_) = n.checked_sub(4) {
184 // ...
185 }
186}
187",
188 );
189
190 check_assist(remove_dbg, "$0dbg!(Foo::foo_test()).bar()", "Foo::foo_test().bar()");
191 }
192
193 #[test]
194 fn test_remove_dbg_with_brackets_and_braces() {
195 check_assist(remove_dbg, "dbg![$01 + 1]", "1 + 1");
196 check_assist(remove_dbg, "dbg!{$01 + 1}", "1 + 1");
197 }
198
199 #[test]
200 fn test_remove_dbg_not_applicable() {
201 check_assist_not_applicable(remove_dbg, "$0vec![1, 2, 3]");
202 check_assist_not_applicable(remove_dbg, "$0dbg(5, 6, 7)");
203 check_assist_not_applicable(remove_dbg, "$0dbg!(5, 6, 7");
204 }
205
206 #[test]
207 fn test_remove_dbg_target() {
208 check_assist_target(
209 remove_dbg,
210 "
211fn foo(n: usize) {
212 if let Some(_) = dbg!(n.$0checked_sub(4)) {
213 // ...
214 }
215}
216",
217 "dbg!(n.checked_sub(4))",
218 );
219 }
220
221 #[test]
222 fn test_remove_dbg_keep_semicolon() {
223 // https://github.com/rust-analyzer/rust-analyzer/issues/5129#issuecomment-651399779
224 // not quite though
225 // adding a comment at the end of the line makes
226 // the ast::MacroCall to include the semicolon at the end
227 check_assist(
228 remove_dbg,
229 r#"let res = $0dbg!(1 * 20); // needless comment"#,
230 r#"let res = 1 * 20; // needless comment"#,
231 );
232 }
233
234 #[test]
235 fn remove_dbg_from_non_leaf_simple_expression() {
236 check_assist(
237 remove_dbg,
238 "
239fn main() {
240 let mut a = 1;
241 while dbg!$0(a) < 10000 {
242 a += 1;
243 }
244}
245",
246 "
247fn main() {
248 let mut a = 1;
249 while a < 10000 {
250 a += 1;
251 }
252}
253",
254 );
255 }
256
257 #[test]
258 fn test_remove_dbg_keep_expression() {
259 check_assist(
260 remove_dbg,
261 r#"let res = $0dbg!(a + b).foo();"#,
262 r#"let res = (a + b).foo();"#,
263 );
264
265 check_assist(remove_dbg, r#"let res = $0dbg!(2 + 2) * 5"#, r#"let res = (2 + 2) * 5"#);
266 check_assist(remove_dbg, r#"let res = $0dbg![2 + 2] * 5"#, r#"let res = (2 + 2) * 5"#);
267 }
268
269 #[test]
270 fn test_remove_dbg_method_chaining() {
271 check_assist(
272 remove_dbg,
273 r#"let res = $0dbg!(foo().bar()).baz();"#,
274 r#"let res = foo().bar().baz();"#,
275 );
276 check_assist(
277 remove_dbg,
278 r#"let res = $0dbg!(foo.bar()).baz();"#,
279 r#"let res = foo.bar().baz();"#,
280 );
281 }
282
283 #[test]
284 fn test_remove_dbg_field_chaining() {
285 check_assist(remove_dbg, r#"let res = $0dbg!(foo.bar).baz;"#, r#"let res = foo.bar.baz;"#);
286 }
287
288 #[test]
289 fn test_remove_dbg_from_inside_fn() {
290 check_assist_target(
291 remove_dbg,
292 r#"
293fn square(x: u32) -> u32 {
294 x * x
295}
296
297fn main() {
298 let x = square(dbg$0!(5 + 10));
299 println!("{}", x);
300}"#,
301 "dbg!(5 + 10)",
302 );
303
304 check_assist(
305 remove_dbg,
306 r#"
307fn square(x: u32) -> u32 {
308 x * x
309}
310
311fn main() {
312 let x = square(dbg$0!(5 + 10));
313 println!("{}", x);
314}"#,
315 r#"
316fn square(x: u32) -> u32 {
317 x * x
318}
319
320fn main() {
321 let x = square(5 + 10);
322 println!("{}", x);
323}"#,
324 );
325 }
326
327 #[test]
328 fn test_remove_dbg_try_expr() {
329 check_assist(
330 remove_dbg,
331 r#"let res = $0dbg!(result?).foo();"#,
332 r#"let res = result?.foo();"#,
333 );
334 }
335
336 #[test]
337 fn test_remove_dbg_await_expr() {
338 check_assist(
339 remove_dbg,
340 r#"let res = $0dbg!(fut.await).foo();"#,
341 r#"let res = fut.await.foo();"#,
342 );
343 }
344
345 #[test]
346 fn test_remove_dbg_as_cast() {
347 check_assist(
348 remove_dbg,
349 r#"let res = $0dbg!(3 as usize).foo();"#,
350 r#"let res = (3 as usize).foo();"#,
351 );
352 }
353
354 #[test]
355 fn test_remove_dbg_index_expr() {
356 check_assist(
357 remove_dbg,
358 r#"let res = $0dbg!(array[3]).foo();"#,
359 r#"let res = array[3].foo();"#,
360 );
361 check_assist(
362 remove_dbg,
363 r#"let res = $0dbg!(tuple.3).foo();"#,
364 r#"let res = tuple.3.foo();"#,
365 );
366 }
367
368 #[test]
369 fn test_remove_dbg_range_expr() {
370 check_assist(
371 remove_dbg,
372 r#"let res = $0dbg!(foo..bar).foo();"#,
373 r#"let res = (foo..bar).foo();"#,
374 );
375 check_assist(
376 remove_dbg,
377 r#"let res = $0dbg!(foo..=bar).foo();"#,
378 r#"let res = (foo..=bar).foo();"#,
379 );
380 }
381
382 #[test]
383 fn test_remove_dbg_followed_by_block() {
384 check_assist(
385 remove_dbg,
386 r#"fn foo() {
387 if $0dbg!(x || y) {}
388}"#,
389 r#"fn foo() {
390 if x || y {}
391}"#,
392 );
393 check_assist(
394 remove_dbg,
395 r#"fn foo() {
396 while let foo = $0dbg!(&x) {}
397}"#,
398 r#"fn foo() {
399 while let foo = &x {}
400}"#,
401 );
402 check_assist(
403 remove_dbg,
404 r#"fn foo() {
405 if let foo = $0dbg!(&x) {}
406}"#,
407 r#"fn foo() {
408 if let foo = &x {}
409}"#,
410 );
411 check_assist(
412 remove_dbg,
413 r#"fn foo() {
414 match $0dbg!(&x) {}
415}"#,
416 r#"fn foo() {
417 match &x {}
418}"#,
419 );
420 }
421}
diff --git a/crates/assists/src/handlers/remove_mut.rs b/crates/assists/src/handlers/remove_mut.rs
deleted file mode 100644
index 30d36dacd..000000000
--- a/crates/assists/src/handlers/remove_mut.rs
+++ /dev/null
@@ -1,37 +0,0 @@
1use 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$0 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_syntax_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/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs
deleted file mode 100644
index c961680e2..000000000
--- a/crates/assists/src/handlers/remove_unused_param.rs
+++ /dev/null
@@ -1,288 +0,0 @@
1use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2use syntax::{
3 algo::find_node_at_range,
4 ast::{self, ArgListOwner},
5 AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
6};
7use test_utils::mark;
8use SyntaxKind::WHITESPACE;
9
10use crate::{
11 assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
12};
13
14// Assist: remove_unused_param
15//
16// Removes unused function parameter.
17//
18// ```
19// fn frobnicate(x: i32$0) {}
20//
21// fn main() {
22// frobnicate(92);
23// }
24// ```
25// ->
26// ```
27// fn frobnicate() {}
28//
29// fn main() {
30// frobnicate();
31// }
32// ```
33pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34 let param: ast::Param = ctx.find_node_at_offset()?;
35 let ident_pat = match param.pat()? {
36 ast::Pat::IdentPat(it) => it,
37 _ => return None,
38 };
39 let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
40 let param_position = func.param_list()?.params().position(|it| it == param)?;
41
42 let fn_def = {
43 let func = ctx.sema.to_def(&func)?;
44 Definition::ModuleDef(func.into())
45 };
46
47 let param_def = {
48 let local = ctx.sema.to_def(&ident_pat)?;
49 Definition::Local(local)
50 };
51 if param_def.usages(&ctx.sema).at_least_one() {
52 mark::hit!(keep_used);
53 return None;
54 }
55 acc.add(
56 AssistId("remove_unused_param", AssistKind::Refactor),
57 "Remove unused parameter",
58 param.syntax().text_range(),
59 |builder| {
60 builder.delete(range_to_remove(param.syntax()));
61 for (file_id, references) in fn_def.usages(&ctx.sema).all() {
62 process_usages(ctx, builder, file_id, references, param_position);
63 }
64 },
65 )
66}
67
68fn process_usages(
69 ctx: &AssistContext,
70 builder: &mut AssistBuilder,
71 file_id: FileId,
72 references: Vec<FileReference>,
73 arg_to_remove: usize,
74) {
75 let source_file = ctx.sema.parse(file_id);
76 builder.edit_file(file_id);
77 for usage in references {
78 if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) {
79 builder.delete(text_range);
80 }
81 }
82}
83
84fn process_usage(
85 source_file: &SourceFile,
86 FileReference { range, .. }: FileReference,
87 arg_to_remove: usize,
88) -> Option<TextRange> {
89 let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?;
90 let call_expr_range = call_expr.expr()?.syntax().text_range();
91 if !call_expr_range.contains_range(range) {
92 return None;
93 }
94 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
95 Some(range_to_remove(arg.syntax()))
96}
97
98fn range_to_remove(node: &SyntaxNode) -> TextRange {
99 let up_to_comma = next_prev().find_map(|dir| {
100 node.siblings_with_tokens(dir)
101 .filter_map(|it| it.into_token())
102 .find(|it| it.kind() == T![,])
103 .map(|it| (dir, it))
104 });
105 if let Some((dir, token)) = up_to_comma {
106 if node.next_sibling().is_some() {
107 let up_to_space = token
108 .siblings_with_tokens(dir)
109 .skip(1)
110 .take_while(|it| it.kind() == WHITESPACE)
111 .last()
112 .and_then(|it| it.into_token());
113 return node
114 .text_range()
115 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
116 }
117 node.text_range().cover(token.text_range())
118 } else {
119 node.text_range()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn remove_unused() {
131 check_assist(
132 remove_unused_param,
133 r#"
134fn a() { foo(9, 2) }
135fn foo(x: i32, $0y: i32) { x; }
136fn b() { foo(9, 2,) }
137"#,
138 r#"
139fn a() { foo(9) }
140fn foo(x: i32) { x; }
141fn b() { foo(9, ) }
142"#,
143 );
144 }
145
146 #[test]
147 fn remove_unused_first_param() {
148 check_assist(
149 remove_unused_param,
150 r#"
151fn foo($0x: i32, y: i32) { y; }
152fn a() { foo(1, 2) }
153fn b() { foo(1, 2,) }
154"#,
155 r#"
156fn foo(y: i32) { y; }
157fn a() { foo(2) }
158fn b() { foo(2,) }
159"#,
160 );
161 }
162
163 #[test]
164 fn remove_unused_single_param() {
165 check_assist(
166 remove_unused_param,
167 r#"
168fn foo($0x: i32) { 0; }
169fn a() { foo(1) }
170fn b() { foo(1, ) }
171"#,
172 r#"
173fn foo() { 0; }
174fn a() { foo() }
175fn b() { foo( ) }
176"#,
177 );
178 }
179
180 #[test]
181 fn remove_unused_surrounded_by_parms() {
182 check_assist(
183 remove_unused_param,
184 r#"
185fn foo(x: i32, $0y: i32, z: i32) { x; }
186fn a() { foo(1, 2, 3) }
187fn b() { foo(1, 2, 3,) }
188"#,
189 r#"
190fn foo(x: i32, z: i32) { x; }
191fn a() { foo(1, 3) }
192fn b() { foo(1, 3,) }
193"#,
194 );
195 }
196
197 #[test]
198 fn remove_unused_qualified_call() {
199 check_assist(
200 remove_unused_param,
201 r#"
202mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
203fn b() { bar::foo(9, 2) }
204"#,
205 r#"
206mod bar { pub fn foo(x: i32) { x; } }
207fn b() { bar::foo(9) }
208"#,
209 );
210 }
211
212 #[test]
213 fn remove_unused_turbofished_func() {
214 check_assist(
215 remove_unused_param,
216 r#"
217pub fn foo<T>(x: T, $0y: i32) { x; }
218fn b() { foo::<i32>(9, 2) }
219"#,
220 r#"
221pub fn foo<T>(x: T) { x; }
222fn b() { foo::<i32>(9) }
223"#,
224 );
225 }
226
227 #[test]
228 fn remove_unused_generic_unused_param_func() {
229 check_assist(
230 remove_unused_param,
231 r#"
232pub fn foo<T>(x: i32, $0y: T) { x; }
233fn b() { foo::<i32>(9, 2) }
234fn b2() { foo(9, 2) }
235"#,
236 r#"
237pub fn foo<T>(x: i32) { x; }
238fn b() { foo::<i32>(9) }
239fn b2() { foo(9) }
240"#,
241 );
242 }
243
244 #[test]
245 fn keep_used() {
246 mark::check!(keep_used);
247 check_assist_not_applicable(
248 remove_unused_param,
249 r#"
250fn foo(x: i32, $0y: i32) { y; }
251fn main() { foo(9, 2) }
252"#,
253 );
254 }
255
256 #[test]
257 fn remove_across_files() {
258 check_assist(
259 remove_unused_param,
260 r#"
261//- /main.rs
262fn foo(x: i32, $0y: i32) { x; }
263
264mod foo;
265
266//- /foo.rs
267use super::foo;
268
269fn bar() {
270 let _ = foo(1, 2);
271}
272"#,
273 r#"
274//- /main.rs
275fn foo(x: i32) { x; }
276
277mod foo;
278
279//- /foo.rs
280use super::foo;
281
282fn bar() {
283 let _ = foo(1);
284}
285"#,
286 )
287 }
288}
diff --git a/crates/assists/src/handlers/reorder_fields.rs b/crates/assists/src/handlers/reorder_fields.rs
deleted file mode 100644
index fba7d6ddb..000000000
--- a/crates/assists/src/handlers/reorder_fields.rs
+++ /dev/null
@@ -1,227 +0,0 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct};
5use ide_db::RootDatabase;
6use syntax::{algo, ast, match_ast, AstNode, SyntaxKind, SyntaxKind::*, SyntaxNode};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: reorder_fields
12//
13// Reorder the fields of record literals and record patterns in the same order as in
14// the definition.
15//
16// ```
17// struct Foo {foo: i32, bar: i32};
18// const test: Foo = $0Foo {bar: 0, foo: 1}
19// ```
20// ->
21// ```
22// struct Foo {foo: i32, bar: i32};
23// const test: Foo = Foo {foo: 1, bar: 0}
24// ```
25//
26pub(crate) fn reorder_fields(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 reorder::<ast::RecordExpr>(acc, ctx).or_else(|| reorder::<ast::RecordPat>(acc, ctx))
28}
29
30fn reorder<R: AstNode>(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
31 let record = ctx.find_node_at_offset::<R>()?;
32 let path = record.syntax().children().find_map(ast::Path::cast)?;
33
34 let ranks = compute_fields_ranks(&path, &ctx)?;
35
36 let fields = get_fields(&record.syntax());
37 let sorted_fields = sorted_by_rank(&fields, |node| {
38 *ranks.get(&get_field_name(node)).unwrap_or(&usize::max_value())
39 });
40
41 if sorted_fields == fields {
42 mark::hit!(reorder_sorted_fields);
43 return None;
44 }
45
46 let target = record.syntax().text_range();
47 acc.add(
48 AssistId("reorder_fields", AssistKind::RefactorRewrite),
49 "Reorder record fields",
50 target,
51 |edit| {
52 let mut rewriter = algo::SyntaxRewriter::default();
53 for (old, new) in fields.iter().zip(&sorted_fields) {
54 rewriter.replace(old, new);
55 }
56 edit.rewrite(rewriter);
57 },
58 )
59}
60
61fn get_fields_kind(node: &SyntaxNode) -> Vec<SyntaxKind> {
62 match node.kind() {
63 RECORD_EXPR => vec![RECORD_EXPR_FIELD],
64 RECORD_PAT => vec![RECORD_PAT_FIELD, IDENT_PAT],
65 _ => vec![],
66 }
67}
68
69fn get_field_name(node: &SyntaxNode) -> String {
70 let res = match_ast! {
71 match node {
72 ast::RecordExprField(field) => field.field_name().map(|it| it.to_string()),
73 ast::RecordPatField(field) => field.field_name().map(|it| it.to_string()),
74 _ => None,
75 }
76 };
77 res.unwrap_or_default()
78}
79
80fn get_fields(record: &SyntaxNode) -> Vec<SyntaxNode> {
81 let kinds = get_fields_kind(record);
82 record.children().flat_map(|n| n.children()).filter(|n| kinds.contains(&n.kind())).collect()
83}
84
85fn sorted_by_rank(
86 fields: &[SyntaxNode],
87 get_rank: impl Fn(&SyntaxNode) -> usize,
88) -> Vec<SyntaxNode> {
89 fields.iter().cloned().sorted_by_key(get_rank).collect()
90}
91
92fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> {
93 match sema.resolve_path(path) {
94 Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s),
95 _ => None,
96 }
97}
98
99fn compute_fields_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
100 Some(
101 struct_definition(path, &ctx.sema)?
102 .fields(ctx.db())
103 .iter()
104 .enumerate()
105 .map(|(idx, field)| (field.name(ctx.db()).to_string(), idx))
106 .collect(),
107 )
108}
109
110#[cfg(test)]
111mod tests {
112 use test_utils::mark;
113
114 use crate::tests::{check_assist, check_assist_not_applicable};
115
116 use super::*;
117
118 #[test]
119 fn reorder_sorted_fields() {
120 mark::check!(reorder_sorted_fields);
121 check_assist_not_applicable(
122 reorder_fields,
123 r#"
124struct Foo {
125 foo: i32,
126 bar: i32,
127}
128
129const test: Foo = $0Foo { foo: 0, bar: 0 };
130"#,
131 )
132 }
133
134 #[test]
135 fn trivial_empty_fields() {
136 check_assist_not_applicable(
137 reorder_fields,
138 r#"
139struct Foo {};
140const test: Foo = $0Foo {}
141"#,
142 )
143 }
144
145 #[test]
146 fn reorder_struct_fields() {
147 check_assist(
148 reorder_fields,
149 r#"
150struct Foo {foo: i32, bar: i32};
151const test: Foo = $0Foo {bar: 0, foo: 1}
152"#,
153 r#"
154struct Foo {foo: i32, bar: i32};
155const test: Foo = Foo {foo: 1, bar: 0}
156"#,
157 )
158 }
159
160 #[test]
161 fn reorder_struct_pattern() {
162 check_assist(
163 reorder_fields,
164 r#"
165struct Foo { foo: i64, bar: i64, baz: i64 }
166
167fn f(f: Foo) -> {
168 match f {
169 $0Foo { baz: 0, ref mut bar, .. } => (),
170 _ => ()
171 }
172}
173"#,
174 r#"
175struct Foo { foo: i64, bar: i64, baz: i64 }
176
177fn f(f: Foo) -> {
178 match f {
179 Foo { ref mut bar, baz: 0, .. } => (),
180 _ => ()
181 }
182}
183"#,
184 )
185 }
186
187 #[test]
188 fn reorder_with_extra_field() {
189 check_assist(
190 reorder_fields,
191 r#"
192struct Foo {
193 foo: String,
194 bar: String,
195}
196
197impl Foo {
198 fn new() -> Foo {
199 let foo = String::new();
200 $0Foo {
201 bar: foo.clone(),
202 extra: "Extra field",
203 foo,
204 }
205 }
206}
207"#,
208 r#"
209struct Foo {
210 foo: String,
211 bar: String,
212}
213
214impl Foo {
215 fn new() -> Foo {
216 let foo = String::new();
217 Foo {
218 foo,
219 bar: foo.clone(),
220 extra: "Extra field",
221 }
222 }
223}
224"#,
225 )
226 }
227}
diff --git a/crates/assists/src/handlers/reorder_impl.rs b/crates/assists/src/handlers/reorder_impl.rs
deleted file mode 100644
index 309f291c8..000000000
--- a/crates/assists/src/handlers/reorder_impl.rs
+++ /dev/null
@@ -1,201 +0,0 @@
1use itertools::Itertools;
2use rustc_hash::FxHashMap;
3
4use hir::{PathResolution, Semantics};
5use ide_db::RootDatabase;
6use syntax::{
7 algo,
8 ast::{self, NameOwner},
9 AstNode,
10};
11use test_utils::mark;
12
13use crate::{AssistContext, AssistId, AssistKind, Assists};
14
15// Assist: reorder_impl
16//
17// Reorder the methods of an `impl Trait`. The methods will be ordered
18// in the same order as in the trait definition.
19//
20// ```
21// trait Foo {
22// fn a() {}
23// fn b() {}
24// fn c() {}
25// }
26//
27// struct Bar;
28// $0impl Foo for Bar {
29// fn b() {}
30// fn c() {}
31// fn a() {}
32// }
33// ```
34// ->
35// ```
36// trait Foo {
37// fn a() {}
38// fn b() {}
39// fn c() {}
40// }
41//
42// struct Bar;
43// impl Foo for Bar {
44// fn a() {}
45// fn b() {}
46// fn c() {}
47// }
48// ```
49//
50pub(crate) fn reorder_impl(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
51 let impl_ast = ctx.find_node_at_offset::<ast::Impl>()?;
52 let items = impl_ast.assoc_item_list()?;
53 let methods = get_methods(&items);
54
55 let path = impl_ast
56 .trait_()
57 .and_then(|t| match t {
58 ast::Type::PathType(path) => Some(path),
59 _ => None,
60 })?
61 .path()?;
62
63 let ranks = compute_method_ranks(&path, ctx)?;
64 let sorted: Vec<_> = methods
65 .iter()
66 .cloned()
67 .sorted_by_key(|f| {
68 f.name().and_then(|n| ranks.get(&n.to_string()).copied()).unwrap_or(usize::max_value())
69 })
70 .collect();
71
72 // Don't edit already sorted methods:
73 if methods == sorted {
74 mark::hit!(not_applicable_if_sorted);
75 return None;
76 }
77
78 let target = items.syntax().text_range();
79 acc.add(AssistId("reorder_impl", AssistKind::RefactorRewrite), "Sort methods", target, |edit| {
80 let mut rewriter = algo::SyntaxRewriter::default();
81 for (old, new) in methods.iter().zip(&sorted) {
82 rewriter.replace(old.syntax(), new.syntax());
83 }
84 edit.rewrite(rewriter);
85 })
86}
87
88fn compute_method_ranks(path: &ast::Path, ctx: &AssistContext) -> Option<FxHashMap<String, usize>> {
89 let td = trait_definition(path, &ctx.sema)?;
90
91 Some(
92 td.items(ctx.db())
93 .iter()
94 .flat_map(|i| match i {
95 hir::AssocItem::Function(f) => Some(f),
96 _ => None,
97 })
98 .enumerate()
99 .map(|(idx, func)| ((func.name(ctx.db()).to_string(), idx)))
100 .collect(),
101 )
102}
103
104fn trait_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<hir::Trait> {
105 match sema.resolve_path(path)? {
106 PathResolution::Def(hir::ModuleDef::Trait(trait_)) => Some(trait_),
107 _ => None,
108 }
109}
110
111fn get_methods(items: &ast::AssocItemList) -> Vec<ast::Fn> {
112 items
113 .assoc_items()
114 .flat_map(|i| match i {
115 ast::AssocItem::Fn(f) => Some(f),
116 _ => None,
117 })
118 .filter(|f| f.name().is_some())
119 .collect()
120}
121
122#[cfg(test)]
123mod tests {
124 use test_utils::mark;
125
126 use crate::tests::{check_assist, check_assist_not_applicable};
127
128 use super::*;
129
130 #[test]
131 fn not_applicable_if_sorted() {
132 mark::check!(not_applicable_if_sorted);
133 check_assist_not_applicable(
134 reorder_impl,
135 r#"
136trait Bar {
137 fn a() {}
138 fn z() {}
139 fn b() {}
140}
141struct Foo;
142$0impl Bar for Foo {
143 fn a() {}
144 fn z() {}
145 fn b() {}
146}
147 "#,
148 )
149 }
150
151 #[test]
152 fn not_applicable_if_empty() {
153 check_assist_not_applicable(
154 reorder_impl,
155 r#"
156trait Bar {};
157struct Foo;
158$0impl Bar for Foo {}
159 "#,
160 )
161 }
162
163 #[test]
164 fn reorder_impl_trait_methods() {
165 check_assist(
166 reorder_impl,
167 r#"
168trait Bar {
169 fn a() {}
170 fn c() {}
171 fn b() {}
172 fn d() {}
173}
174
175struct Foo;
176$0impl Bar for Foo {
177 fn d() {}
178 fn b() {}
179 fn c() {}
180 fn a() {}
181}
182 "#,
183 r#"
184trait Bar {
185 fn a() {}
186 fn c() {}
187 fn b() {}
188 fn d() {}
189}
190
191struct Foo;
192impl Bar for Foo {
193 fn a() {}
194 fn c() {}
195 fn b() {}
196 fn d() {}
197}
198 "#,
199 )
200 }
201}
diff --git a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs b/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
deleted file mode 100644
index bd4c1c806..000000000
--- a/crates/assists/src/handlers/replace_derive_with_manual_impl.rs
+++ /dev/null
@@ -1,401 +0,0 @@
1use ide_db::helpers::mod_path_to_ast;
2use ide_db::imports_locator;
3use itertools::Itertools;
4use syntax::{
5 ast::{self, make, AstNode},
6 Direction, SmolStr,
7 SyntaxKind::{IDENT, WHITESPACE},
8 TextSize,
9};
10
11use crate::{
12 assist_context::{AssistBuilder, AssistContext, Assists},
13 utils::{
14 add_trait_assoc_items_to_impl, filter_assoc_items, render_snippet, Cursor, DefaultMethods,
15 },
16 AssistId, AssistKind,
17};
18
19// Assist: replace_derive_with_manual_impl
20//
21// Converts a `derive` impl into a manual one.
22//
23// ```
24// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
25// #[derive(Deb$0ug, Display)]
26// struct S;
27// ```
28// ->
29// ```
30// # trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
31// #[derive(Display)]
32// struct S;
33//
34// impl Debug for S {
35// fn fmt(&self, f: &mut Formatter) -> Result<()> {
36// ${0:todo!()}
37// }
38// }
39// ```
40pub(crate) fn replace_derive_with_manual_impl(
41 acc: &mut Assists,
42 ctx: &AssistContext,
43) -> Option<()> {
44 let attr = ctx.find_node_at_offset::<ast::Attr>()?;
45
46 let attr_name = attr
47 .syntax()
48 .descendants_with_tokens()
49 .filter(|t| t.kind() == IDENT)
50 .find_map(syntax::NodeOrToken::into_token)
51 .filter(|t| t.text() == "derive")?
52 .text()
53 .clone();
54
55 let trait_token =
56 ctx.token_at_offset().find(|t| t.kind() == IDENT && *t.text() != attr_name)?;
57 let trait_path = make::path_unqualified(make::path_segment(make::name_ref(trait_token.text())));
58
59 let annotated_name = attr.syntax().siblings(Direction::Next).find_map(ast::Name::cast)?;
60 let insert_pos = annotated_name.syntax().parent()?.text_range().end();
61
62 let current_module = ctx.sema.scope(annotated_name.syntax()).module()?;
63 let current_crate = current_module.krate();
64
65 let found_traits = imports_locator::find_exact_imports(
66 &ctx.sema,
67 current_crate,
68 trait_token.text().to_string(),
69 )
70 .filter_map(|candidate: either::Either<hir::ModuleDef, hir::MacroDef>| match candidate {
71 either::Either::Left(hir::ModuleDef::Trait(trait_)) => Some(trait_),
72 _ => None,
73 })
74 .flat_map(|trait_| {
75 current_module
76 .find_use_path(ctx.sema.db, hir::ModuleDef::Trait(trait_))
77 .as_ref()
78 .map(mod_path_to_ast)
79 .zip(Some(trait_))
80 });
81
82 let mut no_traits_found = true;
83 for (trait_path, trait_) in found_traits.inspect(|_| no_traits_found = false) {
84 add_assist(acc, ctx, &attr, &trait_path, Some(trait_), &annotated_name, insert_pos)?;
85 }
86 if no_traits_found {
87 add_assist(acc, ctx, &attr, &trait_path, None, &annotated_name, insert_pos)?;
88 }
89 Some(())
90}
91
92fn add_assist(
93 acc: &mut Assists,
94 ctx: &AssistContext,
95 attr: &ast::Attr,
96 trait_path: &ast::Path,
97 trait_: Option<hir::Trait>,
98 annotated_name: &ast::Name,
99 insert_pos: TextSize,
100) -> Option<()> {
101 let target = attr.syntax().text_range();
102 let input = attr.token_tree()?;
103 let label = format!("Convert to manual `impl {} for {}`", trait_path, annotated_name);
104 let trait_name = trait_path.segment().and_then(|seg| seg.name_ref())?;
105
106 acc.add(
107 AssistId("replace_derive_with_manual_impl", AssistKind::Refactor),
108 label,
109 target,
110 |builder| {
111 let impl_def_with_items =
112 impl_def_from_trait(&ctx.sema, annotated_name, trait_, trait_path);
113 update_attribute(builder, &input, &trait_name, &attr);
114 match (ctx.config.snippet_cap, impl_def_with_items) {
115 (None, _) => builder.insert(
116 insert_pos,
117 format!("\n\nimpl {} for {} {{\n\n}}", trait_path, annotated_name),
118 ),
119 (Some(cap), None) => builder.insert_snippet(
120 cap,
121 insert_pos,
122 format!("\n\nimpl {} for {} {{\n $0\n}}", trait_path, annotated_name),
123 ),
124 (Some(cap), Some((impl_def, first_assoc_item))) => {
125 let mut cursor = Cursor::Before(first_assoc_item.syntax());
126 let placeholder;
127 if let ast::AssocItem::Fn(ref func) = first_assoc_item {
128 if let Some(m) = func.syntax().descendants().find_map(ast::MacroCall::cast)
129 {
130 if m.syntax().text() == "todo!()" {
131 placeholder = m;
132 cursor = Cursor::Replace(placeholder.syntax());
133 }
134 }
135 }
136
137 builder.insert_snippet(
138 cap,
139 insert_pos,
140 format!("\n\n{}", render_snippet(cap, impl_def.syntax(), cursor)),
141 )
142 }
143 };
144 },
145 )
146}
147
148fn impl_def_from_trait(
149 sema: &hir::Semantics<ide_db::RootDatabase>,
150 annotated_name: &ast::Name,
151 trait_: Option<hir::Trait>,
152 trait_path: &ast::Path,
153) -> Option<(ast::Impl, ast::AssocItem)> {
154 let trait_ = trait_?;
155 let target_scope = sema.scope(annotated_name.syntax());
156 let trait_items = filter_assoc_items(sema.db, &trait_.items(sema.db), DefaultMethods::No);
157 if trait_items.is_empty() {
158 return None;
159 }
160 let impl_def = make::impl_trait(
161 trait_path.clone(),
162 make::path_unqualified(make::path_segment(make::name_ref(annotated_name.text()))),
163 );
164 let (impl_def, first_assoc_item) =
165 add_trait_assoc_items_to_impl(sema, trait_items, trait_, impl_def, target_scope);
166 Some((impl_def, first_assoc_item))
167}
168
169fn update_attribute(
170 builder: &mut AssistBuilder,
171 input: &ast::TokenTree,
172 trait_name: &ast::NameRef,
173 attr: &ast::Attr,
174) {
175 let new_attr_input = input
176 .syntax()
177 .descendants_with_tokens()
178 .filter(|t| t.kind() == IDENT)
179 .filter_map(|t| t.into_token().map(|t| t.text().clone()))
180 .filter(|t| t != trait_name.text())
181 .collect::<Vec<SmolStr>>();
182 let has_more_derives = !new_attr_input.is_empty();
183
184 if has_more_derives {
185 let new_attr_input = format!("({})", new_attr_input.iter().format(", "));
186 builder.replace(input.syntax().text_range(), new_attr_input);
187 } else {
188 let attr_range = attr.syntax().text_range();
189 builder.delete(attr_range);
190
191 if let Some(line_break_range) = attr
192 .syntax()
193 .next_sibling_or_token()
194 .filter(|t| t.kind() == WHITESPACE)
195 .map(|t| t.text_range())
196 {
197 builder.delete(line_break_range);
198 }
199 }
200}
201
202#[cfg(test)]
203mod tests {
204 use crate::tests::{check_assist, check_assist_not_applicable};
205
206 use super::*;
207
208 #[test]
209 fn add_custom_impl_debug() {
210 check_assist(
211 replace_derive_with_manual_impl,
212 "
213mod fmt {
214 pub struct Error;
215 pub type Result = Result<(), Error>;
216 pub struct Formatter<'a>;
217 pub trait Debug {
218 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
219 }
220}
221
222#[derive(Debu$0g)]
223struct Foo {
224 bar: String,
225}
226",
227 "
228mod fmt {
229 pub struct Error;
230 pub type Result = Result<(), Error>;
231 pub struct Formatter<'a>;
232 pub trait Debug {
233 fn fmt(&self, f: &mut Formatter<'_>) -> Result;
234 }
235}
236
237struct Foo {
238 bar: String,
239}
240
241impl fmt::Debug for Foo {
242 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
243 ${0:todo!()}
244 }
245}
246",
247 )
248 }
249 #[test]
250 fn add_custom_impl_all() {
251 check_assist(
252 replace_derive_with_manual_impl,
253 "
254mod foo {
255 pub trait Bar {
256 type Qux;
257 const Baz: usize = 42;
258 const Fez: usize;
259 fn foo();
260 fn bar() {}
261 }
262}
263
264#[derive($0Bar)]
265struct Foo {
266 bar: String,
267}
268",
269 "
270mod foo {
271 pub trait Bar {
272 type Qux;
273 const Baz: usize = 42;
274 const Fez: usize;
275 fn foo();
276 fn bar() {}
277 }
278}
279
280struct Foo {
281 bar: String,
282}
283
284impl foo::Bar for Foo {
285 $0type Qux;
286
287 const Baz: usize = 42;
288
289 const Fez: usize;
290
291 fn foo() {
292 todo!()
293 }
294}
295",
296 )
297 }
298 #[test]
299 fn add_custom_impl_for_unique_input() {
300 check_assist(
301 replace_derive_with_manual_impl,
302 "
303#[derive(Debu$0g)]
304struct Foo {
305 bar: String,
306}
307 ",
308 "
309struct Foo {
310 bar: String,
311}
312
313impl Debug for Foo {
314 $0
315}
316 ",
317 )
318 }
319
320 #[test]
321 fn add_custom_impl_for_with_visibility_modifier() {
322 check_assist(
323 replace_derive_with_manual_impl,
324 "
325#[derive(Debug$0)]
326pub struct Foo {
327 bar: String,
328}
329 ",
330 "
331pub struct Foo {
332 bar: String,
333}
334
335impl Debug for Foo {
336 $0
337}
338 ",
339 )
340 }
341
342 #[test]
343 fn add_custom_impl_when_multiple_inputs() {
344 check_assist(
345 replace_derive_with_manual_impl,
346 "
347#[derive(Display, Debug$0, Serialize)]
348struct Foo {}
349 ",
350 "
351#[derive(Display, Serialize)]
352struct Foo {}
353
354impl Debug for Foo {
355 $0
356}
357 ",
358 )
359 }
360
361 #[test]
362 fn test_ignore_derive_macro_without_input() {
363 check_assist_not_applicable(
364 replace_derive_with_manual_impl,
365 "
366#[derive($0)]
367struct Foo {}
368 ",
369 )
370 }
371
372 #[test]
373 fn test_ignore_if_cursor_on_param() {
374 check_assist_not_applicable(
375 replace_derive_with_manual_impl,
376 "
377#[derive$0(Debug)]
378struct Foo {}
379 ",
380 );
381
382 check_assist_not_applicable(
383 replace_derive_with_manual_impl,
384 "
385#[derive(Debug)$0]
386struct Foo {}
387 ",
388 )
389 }
390
391 #[test]
392 fn test_ignore_if_not_derive() {
393 check_assist_not_applicable(
394 replace_derive_with_manual_impl,
395 "
396#[allow(non_camel_$0case_types)]
397struct Foo {}
398 ",
399 )
400 }
401}
diff --git a/crates/assists/src/handlers/replace_if_let_with_match.rs b/crates/assists/src/handlers/replace_if_let_with_match.rs
deleted file mode 100644
index aee3397ab..000000000
--- a/crates/assists/src/handlers/replace_if_let_with_match.rs
+++ /dev/null
@@ -1,530 +0,0 @@
1use std::iter;
2
3use ide_db::{ty_filter::TryEnum, RootDatabase};
4use syntax::{
5 ast::{
6 self,
7 edit::{AstNodeEdit, IndentLevel},
8 make,
9 },
10 AstNode,
11};
12
13use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
14
15// Assist: replace_if_let_with_match
16//
17// Replaces `if let` with an else branch with a `match` expression.
18//
19// ```
20// enum Action { Move { distance: u32 }, Stop }
21//
22// fn handle(action: Action) {
23// $0if let Action::Move { distance } = action {
24// foo(distance)
25// } else {
26// bar()
27// }
28// }
29// ```
30// ->
31// ```
32// enum Action { Move { distance: u32 }, Stop }
33//
34// fn handle(action: Action) {
35// match action {
36// Action::Move { distance } => foo(distance),
37// _ => bar(),
38// }
39// }
40// ```
41pub(crate) fn replace_if_let_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
42 let if_expr: ast::IfExpr = ctx.find_node_at_offset()?;
43 let cond = if_expr.condition()?;
44 let pat = cond.pat()?;
45 let expr = cond.expr()?;
46 let then_block = if_expr.then_branch()?;
47 let else_block = match if_expr.else_branch()? {
48 ast::ElseBranch::Block(it) => it,
49 ast::ElseBranch::IfExpr(_) => return None,
50 };
51
52 let target = if_expr.syntax().text_range();
53 acc.add(
54 AssistId("replace_if_let_with_match", AssistKind::RefactorRewrite),
55 "Replace with match",
56 target,
57 move |edit| {
58 let match_expr = {
59 let then_arm = {
60 let then_block = then_block.reset_indent().indent(IndentLevel(1));
61 let then_expr = unwrap_trivial_block(then_block);
62 make::match_arm(vec![pat.clone()], then_expr)
63 };
64 let else_arm = {
65 let pattern = ctx
66 .sema
67 .type_of_pat(&pat)
68 .and_then(|ty| TryEnum::from_ty(&ctx.sema, &ty))
69 .map(|it| it.sad_pattern())
70 .unwrap_or_else(|| make::wildcard_pat().into());
71 let else_expr = unwrap_trivial_block(else_block);
72 make::match_arm(vec![pattern], else_expr)
73 };
74 let match_expr =
75 make::expr_match(expr, make::match_arm_list(vec![then_arm, else_arm]));
76 match_expr.indent(IndentLevel::from_node(if_expr.syntax()))
77 };
78
79 edit.replace_ast::<ast::Expr>(if_expr.into(), match_expr);
80 },
81 )
82}
83
84// Assist: replace_match_with_if_let
85//
86// Replaces a binary `match` with a wildcard pattern and no guards with an `if let` expression.
87//
88// ```
89// enum Action { Move { distance: u32 }, Stop }
90//
91// fn handle(action: Action) {
92// $0match action {
93// Action::Move { distance } => foo(distance),
94// _ => bar(),
95// }
96// }
97// ```
98// ->
99// ```
100// enum Action { Move { distance: u32 }, Stop }
101//
102// fn handle(action: Action) {
103// if let Action::Move { distance } = action {
104// foo(distance)
105// } else {
106// bar()
107// }
108// }
109// ```
110pub(crate) fn replace_match_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
111 let match_expr: ast::MatchExpr = ctx.find_node_at_offset()?;
112 let mut arms = match_expr.match_arm_list()?.arms();
113 let first_arm = arms.next()?;
114 let second_arm = arms.next()?;
115 if arms.next().is_some() || first_arm.guard().is_some() || second_arm.guard().is_some() {
116 return None;
117 }
118 let condition_expr = match_expr.expr()?;
119 let (if_let_pat, then_expr, else_expr) = if is_pat_wildcard_or_sad(&ctx.sema, &first_arm.pat()?)
120 {
121 (second_arm.pat()?, second_arm.expr()?, first_arm.expr()?)
122 } else if is_pat_wildcard_or_sad(&ctx.sema, &second_arm.pat()?) {
123 (first_arm.pat()?, first_arm.expr()?, second_arm.expr()?)
124 } else {
125 return None;
126 };
127
128 let target = match_expr.syntax().text_range();
129 acc.add(
130 AssistId("replace_match_with_if_let", AssistKind::RefactorRewrite),
131 "Replace with if let",
132 target,
133 move |edit| {
134 let condition = make::condition(condition_expr, Some(if_let_pat));
135 let then_block = match then_expr.reset_indent() {
136 ast::Expr::BlockExpr(block) => block,
137 expr => make::block_expr(iter::empty(), Some(expr)),
138 };
139 let else_expr = match else_expr {
140 ast::Expr::BlockExpr(block)
141 if block.statements().count() == 0 && block.tail_expr().is_none() =>
142 {
143 None
144 }
145 ast::Expr::TupleExpr(tuple) if tuple.fields().count() == 0 => None,
146 expr => Some(expr),
147 };
148 let if_let_expr = make::expr_if(
149 condition,
150 then_block,
151 else_expr.map(|else_expr| {
152 ast::ElseBranch::Block(make::block_expr(iter::empty(), Some(else_expr)))
153 }),
154 )
155 .indent(IndentLevel::from_node(match_expr.syntax()));
156
157 edit.replace_ast::<ast::Expr>(match_expr.into(), if_let_expr);
158 },
159 )
160}
161
162fn is_pat_wildcard_or_sad(sema: &hir::Semantics<RootDatabase>, pat: &ast::Pat) -> bool {
163 sema.type_of_pat(&pat)
164 .and_then(|ty| TryEnum::from_ty(sema, &ty))
165 .map(|it| it.sad_pattern().syntax().text() == pat.syntax().text())
166 .unwrap_or_else(|| matches!(pat, ast::Pat::WildcardPat(_)))
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 use crate::tests::{check_assist, check_assist_target};
174
175 #[test]
176 fn test_replace_if_let_with_match_unwraps_simple_expressions() {
177 check_assist(
178 replace_if_let_with_match,
179 r#"
180impl VariantData {
181 pub fn is_struct(&self) -> bool {
182 if $0let VariantData::Struct(..) = *self {
183 true
184 } else {
185 false
186 }
187 }
188} "#,
189 r#"
190impl VariantData {
191 pub fn is_struct(&self) -> bool {
192 match *self {
193 VariantData::Struct(..) => true,
194 _ => false,
195 }
196 }
197} "#,
198 )
199 }
200
201 #[test]
202 fn test_replace_if_let_with_match_doesnt_unwrap_multiline_expressions() {
203 check_assist(
204 replace_if_let_with_match,
205 r#"
206fn foo() {
207 if $0let VariantData::Struct(..) = a {
208 bar(
209 123
210 )
211 } else {
212 false
213 }
214} "#,
215 r#"
216fn foo() {
217 match a {
218 VariantData::Struct(..) => {
219 bar(
220 123
221 )
222 }
223 _ => false,
224 }
225} "#,
226 )
227 }
228
229 #[test]
230 fn replace_if_let_with_match_target() {
231 check_assist_target(
232 replace_if_let_with_match,
233 r#"
234impl VariantData {
235 pub fn is_struct(&self) -> bool {
236 if $0let VariantData::Struct(..) = *self {
237 true
238 } else {
239 false
240 }
241 }
242} "#,
243 "if let VariantData::Struct(..) = *self {
244 true
245 } else {
246 false
247 }",
248 );
249 }
250
251 #[test]
252 fn special_case_option() {
253 check_assist(
254 replace_if_let_with_match,
255 r#"
256enum Option<T> { Some(T), None }
257use Option::*;
258
259fn foo(x: Option<i32>) {
260 $0if let Some(x) = x {
261 println!("{}", x)
262 } else {
263 println!("none")
264 }
265}
266 "#,
267 r#"
268enum Option<T> { Some(T), None }
269use Option::*;
270
271fn foo(x: Option<i32>) {
272 match x {
273 Some(x) => println!("{}", x),
274 None => println!("none"),
275 }
276}
277 "#,
278 );
279 }
280
281 #[test]
282 fn special_case_result() {
283 check_assist(
284 replace_if_let_with_match,
285 r#"
286enum Result<T, E> { Ok(T), Err(E) }
287use Result::*;
288
289fn foo(x: Result<i32, ()>) {
290 $0if let Ok(x) = x {
291 println!("{}", x)
292 } else {
293 println!("none")
294 }
295}
296 "#,
297 r#"
298enum Result<T, E> { Ok(T), Err(E) }
299use Result::*;
300
301fn foo(x: Result<i32, ()>) {
302 match x {
303 Ok(x) => println!("{}", x),
304 Err(_) => println!("none"),
305 }
306}
307 "#,
308 );
309 }
310
311 #[test]
312 fn nested_indent() {
313 check_assist(
314 replace_if_let_with_match,
315 r#"
316fn main() {
317 if true {
318 $0if let Ok(rel_path) = path.strip_prefix(root_path) {
319 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
320 Some((*id, rel_path))
321 } else {
322 None
323 }
324 }
325}
326"#,
327 r#"
328fn main() {
329 if true {
330 match path.strip_prefix(root_path) {
331 Ok(rel_path) => {
332 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
333 Some((*id, rel_path))
334 }
335 _ => None,
336 }
337 }
338}
339"#,
340 )
341 }
342
343 #[test]
344 fn test_replace_match_with_if_let_unwraps_simple_expressions() {
345 check_assist(
346 replace_match_with_if_let,
347 r#"
348impl VariantData {
349 pub fn is_struct(&self) -> bool {
350 $0match *self {
351 VariantData::Struct(..) => true,
352 _ => false,
353 }
354 }
355} "#,
356 r#"
357impl VariantData {
358 pub fn is_struct(&self) -> bool {
359 if let VariantData::Struct(..) = *self {
360 true
361 } else {
362 false
363 }
364 }
365} "#,
366 )
367 }
368
369 #[test]
370 fn test_replace_match_with_if_let_doesnt_unwrap_multiline_expressions() {
371 check_assist(
372 replace_match_with_if_let,
373 r#"
374fn foo() {
375 $0match a {
376 VariantData::Struct(..) => {
377 bar(
378 123
379 )
380 }
381 _ => false,
382 }
383} "#,
384 r#"
385fn foo() {
386 if let VariantData::Struct(..) = a {
387 bar(
388 123
389 )
390 } else {
391 false
392 }
393} "#,
394 )
395 }
396
397 #[test]
398 fn replace_match_with_if_let_target() {
399 check_assist_target(
400 replace_match_with_if_let,
401 r#"
402impl VariantData {
403 pub fn is_struct(&self) -> bool {
404 $0match *self {
405 VariantData::Struct(..) => true,
406 _ => false,
407 }
408 }
409} "#,
410 r#"match *self {
411 VariantData::Struct(..) => true,
412 _ => false,
413 }"#,
414 );
415 }
416
417 #[test]
418 fn special_case_option_match_to_if_let() {
419 check_assist(
420 replace_match_with_if_let,
421 r#"
422enum Option<T> { Some(T), None }
423use Option::*;
424
425fn foo(x: Option<i32>) {
426 $0match x {
427 Some(x) => println!("{}", x),
428 None => println!("none"),
429 }
430}
431 "#,
432 r#"
433enum Option<T> { Some(T), None }
434use Option::*;
435
436fn foo(x: Option<i32>) {
437 if let Some(x) = x {
438 println!("{}", x)
439 } else {
440 println!("none")
441 }
442}
443 "#,
444 );
445 }
446
447 #[test]
448 fn special_case_result_match_to_if_let() {
449 check_assist(
450 replace_match_with_if_let,
451 r#"
452enum Result<T, E> { Ok(T), Err(E) }
453use Result::*;
454
455fn foo(x: Result<i32, ()>) {
456 $0match x {
457 Ok(x) => println!("{}", x),
458 Err(_) => println!("none"),
459 }
460}
461 "#,
462 r#"
463enum Result<T, E> { Ok(T), Err(E) }
464use Result::*;
465
466fn foo(x: Result<i32, ()>) {
467 if let Ok(x) = x {
468 println!("{}", x)
469 } else {
470 println!("none")
471 }
472}
473 "#,
474 );
475 }
476
477 #[test]
478 fn nested_indent_match_to_if_let() {
479 check_assist(
480 replace_match_with_if_let,
481 r#"
482fn main() {
483 if true {
484 $0match path.strip_prefix(root_path) {
485 Ok(rel_path) => {
486 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
487 Some((*id, rel_path))
488 }
489 _ => None,
490 }
491 }
492}
493"#,
494 r#"
495fn main() {
496 if true {
497 if let Ok(rel_path) = path.strip_prefix(root_path) {
498 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
499 Some((*id, rel_path))
500 } else {
501 None
502 }
503 }
504}
505"#,
506 )
507 }
508
509 #[test]
510 fn replace_match_with_if_let_empty_wildcard_expr() {
511 check_assist(
512 replace_match_with_if_let,
513 r#"
514fn main() {
515 $0match path.strip_prefix(root_path) {
516 Ok(rel_path) => println!("{}", rel_path),
517 _ => (),
518 }
519}
520"#,
521 r#"
522fn main() {
523 if let Ok(rel_path) = path.strip_prefix(root_path) {
524 println!("{}", rel_path)
525 }
526}
527"#,
528 )
529 }
530}
diff --git a/crates/assists/src/handlers/replace_impl_trait_with_generic.rs b/crates/assists/src/handlers/replace_impl_trait_with_generic.rs
deleted file mode 100644
index ff25b61ea..000000000
--- a/crates/assists/src/handlers/replace_impl_trait_with_generic.rs
+++ /dev/null
@@ -1,168 +0,0 @@
1use syntax::ast::{self, edit::AstNodeEdit, make, AstNode, GenericParamsOwner};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: replace_impl_trait_with_generic
6//
7// Replaces `impl Trait` function argument with the named generic.
8//
9// ```
10// fn foo(bar: $0impl Bar) {}
11// ```
12// ->
13// ```
14// fn foo<B: Bar>(bar: B) {}
15// ```
16pub(crate) fn replace_impl_trait_with_generic(
17 acc: &mut Assists,
18 ctx: &AssistContext,
19) -> Option<()> {
20 let type_impl_trait = ctx.find_node_at_offset::<ast::ImplTraitType>()?;
21 let type_param = type_impl_trait.syntax().parent().and_then(ast::Param::cast)?;
22 let type_fn = type_param.syntax().ancestors().find_map(ast::Fn::cast)?;
23
24 let impl_trait_ty = type_impl_trait.type_bound_list()?;
25
26 let target = type_fn.syntax().text_range();
27 acc.add(
28 AssistId("replace_impl_trait_with_generic", AssistKind::RefactorRewrite),
29 "Replace impl trait with generic",
30 target,
31 |edit| {
32 let generic_letter = impl_trait_ty.to_string().chars().next().unwrap().to_string();
33
34 let generic_param_list = type_fn
35 .generic_param_list()
36 .unwrap_or_else(|| make::generic_param_list(None))
37 .append_param(make::generic_param(generic_letter.clone(), Some(impl_trait_ty)));
38
39 let new_type_fn = type_fn
40 .replace_descendant::<ast::Type>(type_impl_trait.into(), make::ty(&generic_letter))
41 .with_generic_param_list(generic_param_list);
42
43 edit.replace_ast(type_fn.clone(), new_type_fn);
44 },
45 )
46}
47
48#[cfg(test)]
49mod tests {
50 use super::*;
51
52 use crate::tests::check_assist;
53
54 #[test]
55 fn replace_impl_trait_with_generic_params() {
56 check_assist(
57 replace_impl_trait_with_generic,
58 r#"
59 fn foo<G>(bar: $0impl Bar) {}
60 "#,
61 r#"
62 fn foo<G, B: Bar>(bar: B) {}
63 "#,
64 );
65 }
66
67 #[test]
68 fn replace_impl_trait_without_generic_params() {
69 check_assist(
70 replace_impl_trait_with_generic,
71 r#"
72 fn foo(bar: $0impl Bar) {}
73 "#,
74 r#"
75 fn foo<B: Bar>(bar: B) {}
76 "#,
77 );
78 }
79
80 #[test]
81 fn replace_two_impl_trait_with_generic_params() {
82 check_assist(
83 replace_impl_trait_with_generic,
84 r#"
85 fn foo<G>(foo: impl Foo, bar: $0impl Bar) {}
86 "#,
87 r#"
88 fn foo<G, B: Bar>(foo: impl Foo, bar: B) {}
89 "#,
90 );
91 }
92
93 #[test]
94 fn replace_impl_trait_with_empty_generic_params() {
95 check_assist(
96 replace_impl_trait_with_generic,
97 r#"
98 fn foo<>(bar: $0impl Bar) {}
99 "#,
100 r#"
101 fn foo<B: Bar>(bar: B) {}
102 "#,
103 );
104 }
105
106 #[test]
107 fn replace_impl_trait_with_empty_multiline_generic_params() {
108 check_assist(
109 replace_impl_trait_with_generic,
110 r#"
111 fn foo<
112 >(bar: $0impl Bar) {}
113 "#,
114 r#"
115 fn foo<B: Bar
116 >(bar: B) {}
117 "#,
118 );
119 }
120
121 #[test]
122 #[ignore = "This case is very rare but there is no simple solutions to fix it."]
123 fn replace_impl_trait_with_exist_generic_letter() {
124 check_assist(
125 replace_impl_trait_with_generic,
126 r#"
127 fn foo<B>(bar: $0impl Bar) {}
128 "#,
129 r#"
130 fn foo<B, C: Bar>(bar: C) {}
131 "#,
132 );
133 }
134
135 #[test]
136 fn replace_impl_trait_with_multiline_generic_params() {
137 check_assist(
138 replace_impl_trait_with_generic,
139 r#"
140 fn foo<
141 G: Foo,
142 F,
143 H,
144 >(bar: $0impl Bar) {}
145 "#,
146 r#"
147 fn foo<
148 G: Foo,
149 F,
150 H, B: Bar
151 >(bar: B) {}
152 "#,
153 );
154 }
155
156 #[test]
157 fn replace_impl_trait_multiple() {
158 check_assist(
159 replace_impl_trait_with_generic,
160 r#"
161 fn foo(bar: $0impl Foo + Bar) {}
162 "#,
163 r#"
164 fn foo<F: Foo + Bar>(bar: F) {}
165 "#,
166 );
167 }
168}
diff --git a/crates/assists/src/handlers/replace_let_with_if_let.rs b/crates/assists/src/handlers/replace_let_with_if_let.rs
deleted file mode 100644
index 5a27ada6b..000000000
--- a/crates/assists/src/handlers/replace_let_with_if_let.rs
+++ /dev/null
@@ -1,101 +0,0 @@
1use std::iter::once;
2
3use syntax::{
4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make,
8 },
9 AstNode, T,
10};
11
12use crate::{AssistContext, AssistId, AssistKind, Assists};
13use ide_db::ty_filter::TryEnum;
14
15// Assist: replace_let_with_if_let
16//
17// Replaces `let` with an `if-let`.
18//
19// ```
20// # enum Option<T> { Some(T), None }
21//
22// fn main(action: Action) {
23// $0let x = compute();
24// }
25//
26// fn compute() -> Option<i32> { None }
27// ```
28// ->
29// ```
30// # enum Option<T> { Some(T), None }
31//
32// fn main(action: Action) {
33// if let Some(x) = compute() {
34// }
35// }
36//
37// fn compute() -> Option<i32> { None }
38// ```
39pub(crate) fn replace_let_with_if_let(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let let_kw = ctx.find_token_syntax_at_offset(T![let])?;
41 let let_stmt = let_kw.ancestors().find_map(ast::LetStmt::cast)?;
42 let init = let_stmt.initializer()?;
43 let original_pat = let_stmt.pat()?;
44 let ty = ctx.sema.type_of_expr(&init)?;
45 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty).map(|it| it.happy_case());
46
47 let target = let_kw.text_range();
48 acc.add(
49 AssistId("replace_let_with_if_let", AssistKind::RefactorRewrite),
50 "Replace with if-let",
51 target,
52 |edit| {
53 let with_placeholder: ast::Pat = match happy_variant {
54 None => make::wildcard_pat().into(),
55 Some(var_name) => make::tuple_struct_pat(
56 make::path_unqualified(make::path_segment(make::name_ref(var_name))),
57 once(make::wildcard_pat().into()),
58 )
59 .into(),
60 };
61 let block =
62 make::block_expr(None, None).indent(IndentLevel::from_node(let_stmt.syntax()));
63 let if_ = make::expr_if(make::condition(init, Some(with_placeholder)), block, None);
64 let stmt = make::expr_stmt(if_);
65
66 let placeholder = stmt.syntax().descendants().find_map(ast::WildcardPat::cast).unwrap();
67 let stmt = stmt.replace_descendant(placeholder.into(), original_pat);
68
69 edit.replace_ast(ast::Stmt::from(let_stmt), ast::Stmt::from(stmt));
70 },
71 )
72}
73
74#[cfg(test)]
75mod tests {
76 use crate::tests::check_assist;
77
78 use super::*;
79
80 #[test]
81 fn replace_let_unknown_enum() {
82 check_assist(
83 replace_let_with_if_let,
84 r"
85enum E<T> { X(T), Y(T) }
86
87fn main() {
88 $0let x = E::X(92);
89}
90 ",
91 r"
92enum E<T> { X(T), Y(T) }
93
94fn main() {
95 if let x = E::X(92) {
96 }
97}
98 ",
99 )
100 }
101}
diff --git a/crates/assists/src/handlers/replace_qualified_name_with_use.rs b/crates/assists/src/handlers/replace_qualified_name_with_use.rs
deleted file mode 100644
index f3bc6cf39..000000000
--- a/crates/assists/src/handlers/replace_qualified_name_with_use.rs
+++ /dev/null
@@ -1,678 +0,0 @@
1use ide_db::helpers::insert_use::{insert_use, ImportScope};
2use syntax::{algo::SyntaxRewriter, ast, match_ast, AstNode, SyntaxNode};
3use test_utils::mark;
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: replace_qualified_name_with_use
8//
9// Adds a use statement for a given fully-qualified name.
10//
11// ```
12// fn process(map: std::collections::$0HashMap<String, String>) {}
13// ```
14// ->
15// ```
16// use std::collections::HashMap;
17//
18// fn process(map: HashMap<String, String>) {}
19// ```
20pub(crate) fn replace_qualified_name_with_use(
21 acc: &mut Assists,
22 ctx: &AssistContext,
23) -> Option<()> {
24 let path: ast::Path = ctx.find_node_at_offset()?;
25 // We don't want to mess with use statements
26 if path.syntax().ancestors().find_map(ast::Use::cast).is_some() {
27 return None;
28 }
29 if path.qualifier().is_none() {
30 mark::hit!(dont_import_trivial_paths);
31 return None;
32 }
33
34 let target = path.syntax().text_range();
35 let scope = ImportScope::find_insert_use_container(path.syntax(), &ctx.sema)?;
36 let syntax = scope.as_syntax_node();
37 acc.add(
38 AssistId("replace_qualified_name_with_use", AssistKind::RefactorRewrite),
39 "Replace qualified path with use",
40 target,
41 |builder| {
42 // Now that we've brought the name into scope, re-qualify all paths that could be
43 // affected (that is, all paths inside the node we added the `use` to).
44 let mut rewriter = SyntaxRewriter::default();
45 shorten_paths(&mut rewriter, syntax.clone(), &path);
46 if let Some(ref import_scope) = ImportScope::from(syntax.clone()) {
47 rewriter += insert_use(import_scope, path, ctx.config.insert_use.merge);
48 builder.rewrite(rewriter);
49 }
50 },
51 )
52}
53
54/// Adds replacements to `re` that shorten `path` in all descendants of `node`.
55fn shorten_paths(rewriter: &mut SyntaxRewriter<'static>, node: SyntaxNode, path: &ast::Path) {
56 for child in node.children() {
57 match_ast! {
58 match child {
59 // Don't modify `use` items, as this can break the `use` item when injecting a new
60 // import into the use tree.
61 ast::Use(_it) => continue,
62 // Don't descend into submodules, they don't have the same `use` items in scope.
63 ast::Module(_it) => continue,
64
65 ast::Path(p) => {
66 match maybe_replace_path(rewriter, p.clone(), path.clone()) {
67 Some(()) => {},
68 None => shorten_paths(rewriter, p.syntax().clone(), path),
69 }
70 },
71 _ => shorten_paths(rewriter, child, path),
72 }
73 }
74 }
75}
76
77fn maybe_replace_path(
78 rewriter: &mut SyntaxRewriter<'static>,
79 path: ast::Path,
80 target: ast::Path,
81) -> Option<()> {
82 if !path_eq(path.clone(), target) {
83 return None;
84 }
85
86 // Shorten `path`, leaving only its last segment.
87 if let Some(parent) = path.qualifier() {
88 rewriter.delete(parent.syntax());
89 }
90 if let Some(double_colon) = path.coloncolon_token() {
91 rewriter.delete(&double_colon);
92 }
93
94 Some(())
95}
96
97fn path_eq(lhs: ast::Path, rhs: ast::Path) -> bool {
98 let mut lhs_curr = lhs;
99 let mut rhs_curr = rhs;
100 loop {
101 match (lhs_curr.segment(), rhs_curr.segment()) {
102 (Some(lhs), Some(rhs)) if lhs.syntax().text() == rhs.syntax().text() => (),
103 _ => return false,
104 }
105
106 match (lhs_curr.qualifier(), rhs_curr.qualifier()) {
107 (Some(lhs), Some(rhs)) => {
108 lhs_curr = lhs;
109 rhs_curr = rhs;
110 }
111 (None, None) => return true,
112 _ => return false,
113 }
114 }
115}
116
117#[cfg(test)]
118mod tests {
119 use crate::tests::{check_assist, check_assist_not_applicable};
120
121 use super::*;
122
123 #[test]
124 fn test_replace_already_imported() {
125 check_assist(
126 replace_qualified_name_with_use,
127 r"use std::fs;
128
129fn main() {
130 std::f$0s::Path
131}",
132 r"use std::fs;
133
134fn main() {
135 fs::Path
136}",
137 )
138 }
139
140 #[test]
141 fn test_replace_add_use_no_anchor() {
142 check_assist(
143 replace_qualified_name_with_use,
144 r"
145std::fmt::Debug$0
146 ",
147 r"
148use std::fmt::Debug;
149
150Debug
151 ",
152 );
153 }
154 #[test]
155 fn test_replace_add_use_no_anchor_with_item_below() {
156 check_assist(
157 replace_qualified_name_with_use,
158 r"
159std::fmt::Debug$0
160
161fn main() {
162}
163 ",
164 r"
165use std::fmt::Debug;
166
167Debug
168
169fn main() {
170}
171 ",
172 );
173 }
174
175 #[test]
176 fn test_replace_add_use_no_anchor_with_item_above() {
177 check_assist(
178 replace_qualified_name_with_use,
179 r"
180fn main() {
181}
182
183std::fmt::Debug$0
184 ",
185 r"
186use std::fmt::Debug;
187
188fn main() {
189}
190
191Debug
192 ",
193 );
194 }
195
196 #[test]
197 fn test_replace_add_use_no_anchor_2seg() {
198 check_assist(
199 replace_qualified_name_with_use,
200 r"
201std::fmt$0::Debug
202 ",
203 r"
204use std::fmt;
205
206fmt::Debug
207 ",
208 );
209 }
210
211 #[test]
212 fn test_replace_add_use() {
213 check_assist(
214 replace_qualified_name_with_use,
215 r"
216use stdx;
217
218impl std::fmt::Debug$0 for Foo {
219}
220 ",
221 r"
222use std::fmt::Debug;
223
224use stdx;
225
226impl Debug for Foo {
227}
228 ",
229 );
230 }
231
232 #[test]
233 fn test_replace_file_use_other_anchor() {
234 check_assist(
235 replace_qualified_name_with_use,
236 r"
237impl std::fmt::Debug$0 for Foo {
238}
239 ",
240 r"
241use std::fmt::Debug;
242
243impl Debug for Foo {
244}
245 ",
246 );
247 }
248
249 #[test]
250 fn test_replace_add_use_other_anchor_indent() {
251 check_assist(
252 replace_qualified_name_with_use,
253 r"
254 impl std::fmt::Debug$0 for Foo {
255 }
256 ",
257 r"
258 use std::fmt::Debug;
259
260 impl Debug for Foo {
261 }
262 ",
263 );
264 }
265
266 #[test]
267 fn test_replace_split_different() {
268 check_assist(
269 replace_qualified_name_with_use,
270 r"
271use std::fmt;
272
273impl std::io$0 for Foo {
274}
275 ",
276 r"
277use std::{fmt, io};
278
279impl io for Foo {
280}
281 ",
282 );
283 }
284
285 #[test]
286 fn test_replace_split_self_for_use() {
287 check_assist(
288 replace_qualified_name_with_use,
289 r"
290use std::fmt;
291
292impl std::fmt::Debug$0 for Foo {
293}
294 ",
295 r"
296use std::fmt::{self, Debug};
297
298impl Debug for Foo {
299}
300 ",
301 );
302 }
303
304 #[test]
305 fn test_replace_split_self_for_target() {
306 check_assist(
307 replace_qualified_name_with_use,
308 r"
309use std::fmt::Debug;
310
311impl std::fmt$0 for Foo {
312}
313 ",
314 r"
315use std::fmt::{self, Debug};
316
317impl fmt for Foo {
318}
319 ",
320 );
321 }
322
323 #[test]
324 fn test_replace_add_to_nested_self_nested() {
325 check_assist(
326 replace_qualified_name_with_use,
327 r"
328use std::fmt::{Debug, nested::{Display}};
329
330impl std::fmt::nested$0 for Foo {
331}
332",
333 r"
334use std::fmt::{Debug, nested::{self, Display}};
335
336impl nested for Foo {
337}
338",
339 );
340 }
341
342 #[test]
343 fn test_replace_add_to_nested_self_already_included() {
344 check_assist(
345 replace_qualified_name_with_use,
346 r"
347use std::fmt::{Debug, nested::{self, Display}};
348
349impl std::fmt::nested$0 for Foo {
350}
351",
352 r"
353use std::fmt::{Debug, nested::{self, Display}};
354
355impl nested for Foo {
356}
357",
358 );
359 }
360
361 #[test]
362 fn test_replace_add_to_nested_nested() {
363 check_assist(
364 replace_qualified_name_with_use,
365 r"
366use std::fmt::{Debug, nested::{Display}};
367
368impl std::fmt::nested::Debug$0 for Foo {
369}
370",
371 r"
372use std::fmt::{Debug, nested::{Debug, Display}};
373
374impl Debug for Foo {
375}
376",
377 );
378 }
379
380 #[test]
381 fn test_replace_split_common_target_longer() {
382 check_assist(
383 replace_qualified_name_with_use,
384 r"
385use std::fmt::Debug;
386
387impl std::fmt::nested::Display$0 for Foo {
388}
389",
390 r"
391use std::fmt::{Debug, nested::Display};
392
393impl Display for Foo {
394}
395",
396 );
397 }
398
399 #[test]
400 fn test_replace_split_common_use_longer() {
401 check_assist(
402 replace_qualified_name_with_use,
403 r"
404use std::fmt::nested::Debug;
405
406impl std::fmt::Display$0 for Foo {
407}
408",
409 r"
410use std::fmt::{Display, nested::Debug};
411
412impl Display for Foo {
413}
414",
415 );
416 }
417
418 #[test]
419 fn test_replace_use_nested_import() {
420 check_assist(
421 replace_qualified_name_with_use,
422 r"
423use crate::{
424 ty::{Substs, Ty},
425 AssocItem,
426};
427
428fn foo() { crate::ty::lower$0::trait_env() }
429",
430 r"
431use crate::{AssocItem, ty::{Substs, Ty, lower}};
432
433fn foo() { lower::trait_env() }
434",
435 );
436 }
437
438 #[test]
439 fn test_replace_alias() {
440 check_assist(
441 replace_qualified_name_with_use,
442 r"
443use std::fmt as foo;
444
445impl foo::Debug$0 for Foo {
446}
447",
448 r"
449use std::fmt as foo;
450
451use foo::Debug;
452
453impl Debug for Foo {
454}
455",
456 );
457 }
458
459 #[test]
460 fn dont_import_trivial_paths() {
461 mark::check!(dont_import_trivial_paths);
462 check_assist_not_applicable(
463 replace_qualified_name_with_use,
464 r"
465impl foo$0 for Foo {
466}
467",
468 );
469 }
470
471 #[test]
472 fn test_replace_not_applicable_in_use() {
473 check_assist_not_applicable(
474 replace_qualified_name_with_use,
475 r"
476use std::fmt$0;
477",
478 );
479 }
480
481 #[test]
482 fn test_replace_add_use_no_anchor_in_mod_mod() {
483 check_assist(
484 replace_qualified_name_with_use,
485 r"
486mod foo {
487 mod bar {
488 std::fmt::Debug$0
489 }
490}
491 ",
492 r"
493mod foo {
494 mod bar {
495 use std::fmt::Debug;
496
497 Debug
498 }
499}
500 ",
501 );
502 }
503
504 #[test]
505 fn inserts_imports_after_inner_attributes() {
506 check_assist(
507 replace_qualified_name_with_use,
508 r"
509#![allow(dead_code)]
510
511fn main() {
512 std::fmt::Debug$0
513}
514 ",
515 r"
516#![allow(dead_code)]
517
518use std::fmt::Debug;
519
520fn main() {
521 Debug
522}
523 ",
524 );
525 }
526
527 #[test]
528 fn replaces_all_affected_paths() {
529 check_assist(
530 replace_qualified_name_with_use,
531 r"
532fn main() {
533 std::fmt::Debug$0;
534 let x: std::fmt::Debug = std::fmt::Debug;
535}
536 ",
537 r"
538use std::fmt::Debug;
539
540fn main() {
541 Debug;
542 let x: Debug = Debug;
543}
544 ",
545 );
546 }
547
548 #[test]
549 fn replaces_all_affected_paths_mod() {
550 check_assist(
551 replace_qualified_name_with_use,
552 r"
553mod m {
554 fn f() {
555 std::fmt::Debug$0;
556 let x: std::fmt::Debug = std::fmt::Debug;
557 }
558 fn g() {
559 std::fmt::Debug;
560 }
561}
562
563fn f() {
564 std::fmt::Debug;
565}
566 ",
567 r"
568mod m {
569 use std::fmt::Debug;
570
571 fn f() {
572 Debug;
573 let x: Debug = Debug;
574 }
575 fn g() {
576 Debug;
577 }
578}
579
580fn f() {
581 std::fmt::Debug;
582}
583 ",
584 );
585 }
586
587 #[test]
588 fn does_not_replace_in_submodules() {
589 check_assist(
590 replace_qualified_name_with_use,
591 r"
592fn main() {
593 std::fmt::Debug$0;
594}
595
596mod sub {
597 fn f() {
598 std::fmt::Debug;
599 }
600}
601 ",
602 r"
603use std::fmt::Debug;
604
605fn main() {
606 Debug;
607}
608
609mod sub {
610 fn f() {
611 std::fmt::Debug;
612 }
613}
614 ",
615 );
616 }
617
618 #[test]
619 fn does_not_replace_in_use() {
620 check_assist(
621 replace_qualified_name_with_use,
622 r"
623use std::fmt::Display;
624
625fn main() {
626 std::fmt$0;
627}
628 ",
629 r"
630use std::fmt::{self, Display};
631
632fn main() {
633 fmt;
634}
635 ",
636 );
637 }
638
639 #[test]
640 fn does_not_replace_pub_use() {
641 check_assist(
642 replace_qualified_name_with_use,
643 r"
644pub use std::fmt;
645
646impl std::io$0 for Foo {
647}
648 ",
649 r"
650pub use std::fmt;
651use std::io;
652
653impl io for Foo {
654}
655 ",
656 );
657 }
658
659 #[test]
660 fn does_not_replace_pub_crate_use() {
661 check_assist(
662 replace_qualified_name_with_use,
663 r"
664pub(crate) use std::fmt;
665
666impl std::io$0 for Foo {
667}
668 ",
669 r"
670pub(crate) use std::fmt;
671use std::io;
672
673impl io for Foo {
674}
675 ",
676 );
677 }
678}
diff --git a/crates/assists/src/handlers/replace_string_with_char.rs b/crates/assists/src/handlers/replace_string_with_char.rs
deleted file mode 100644
index 317318c24..000000000
--- a/crates/assists/src/handlers/replace_string_with_char.rs
+++ /dev/null
@@ -1,137 +0,0 @@
1use syntax::{ast, AstToken, SyntaxKind::STRING};
2
3use crate::{AssistContext, AssistId, AssistKind, Assists};
4
5// Assist: replace_string_with_char
6//
7// Replace string with char.
8//
9// ```
10// fn main() {
11// find("{$0");
12// }
13// ```
14// ->
15// ```
16// fn main() {
17// find('{');
18// }
19// ```
20pub(crate) fn replace_string_with_char(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
21 let token = ctx.find_token_syntax_at_offset(STRING).and_then(ast::String::cast)?;
22 let value = token.value()?;
23 let target = token.syntax().text_range();
24
25 if value.chars().take(2).count() != 1 {
26 return None;
27 }
28
29 acc.add(
30 AssistId("replace_string_with_char", AssistKind::RefactorRewrite),
31 "Replace string with char",
32 target,
33 |edit| {
34 edit.replace(token.syntax().text_range(), format!("'{}'", value));
35 },
36 )
37}
38
39#[cfg(test)]
40mod tests {
41 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
42
43 use super::*;
44
45 #[test]
46 fn replace_string_with_char_target() {
47 check_assist_target(
48 replace_string_with_char,
49 r#"
50 fn f() {
51 let s = "$0c";
52 }
53 "#,
54 r#""c""#,
55 );
56 }
57
58 #[test]
59 fn replace_string_with_char_assist() {
60 check_assist(
61 replace_string_with_char,
62 r#"
63 fn f() {
64 let s = "$0c";
65 }
66 "#,
67 r##"
68 fn f() {
69 let s = 'c';
70 }
71 "##,
72 )
73 }
74
75 #[test]
76 fn replace_string_with_char_assist_with_emoji() {
77 check_assist(
78 replace_string_with_char,
79 r#"
80 fn f() {
81 let s = "$0😀";
82 }
83 "#,
84 r##"
85 fn f() {
86 let s = '😀';
87 }
88 "##,
89 )
90 }
91
92 #[test]
93 fn replace_string_with_char_assist_not_applicable() {
94 check_assist_not_applicable(
95 replace_string_with_char,
96 r#"
97 fn f() {
98 let s = "$0test";
99 }
100 "#,
101 )
102 }
103
104 #[test]
105 fn replace_string_with_char_works_inside_macros() {
106 check_assist(
107 replace_string_with_char,
108 r#"
109 fn f() {
110 format!($0"x", 92)
111 }
112 "#,
113 r##"
114 fn f() {
115 format!('x', 92)
116 }
117 "##,
118 )
119 }
120
121 #[test]
122 fn replace_string_with_char_works_func_args() {
123 check_assist(
124 replace_string_with_char,
125 r#"
126 fn f() {
127 find($0"x");
128 }
129 "#,
130 r##"
131 fn f() {
132 find('x');
133 }
134 "##,
135 )
136 }
137}
diff --git a/crates/assists/src/handlers/replace_unwrap_with_match.rs b/crates/assists/src/handlers/replace_unwrap_with_match.rs
deleted file mode 100644
index a986a6ae8..000000000
--- a/crates/assists/src/handlers/replace_unwrap_with_match.rs
+++ /dev/null
@@ -1,188 +0,0 @@
1use std::iter;
2
3use syntax::{
4 ast::{
5 self,
6 edit::{AstNodeEdit, IndentLevel},
7 make,
8 },
9 AstNode,
10};
11
12use crate::{
13 utils::{render_snippet, Cursor},
14 AssistContext, AssistId, AssistKind, Assists,
15};
16use ide_db::ty_filter::TryEnum;
17
18// Assist: replace_unwrap_with_match
19//
20// Replaces `unwrap` a `match` expression. Works for Result and Option.
21//
22// ```
23// enum Result<T, E> { Ok(T), Err(E) }
24// fn main() {
25// let x: Result<i32, i32> = Result::Ok(92);
26// let y = x.$0unwrap();
27// }
28// ```
29// ->
30// ```
31// enum Result<T, E> { Ok(T), Err(E) }
32// fn main() {
33// let x: Result<i32, i32> = Result::Ok(92);
34// let y = match x {
35// Ok(a) => a,
36// $0_ => unreachable!(),
37// };
38// }
39// ```
40pub(crate) fn replace_unwrap_with_match(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
41 let method_call: ast::MethodCallExpr = ctx.find_node_at_offset()?;
42 let name = method_call.name_ref()?;
43 if name.text() != "unwrap" {
44 return None;
45 }
46 let caller = method_call.receiver()?;
47 let ty = ctx.sema.type_of_expr(&caller)?;
48 let happy_variant = TryEnum::from_ty(&ctx.sema, &ty)?.happy_case();
49 let target = method_call.syntax().text_range();
50 acc.add(
51 AssistId("replace_unwrap_with_match", AssistKind::RefactorRewrite),
52 "Replace unwrap with match",
53 target,
54 |builder| {
55 let ok_path = make::path_unqualified(make::path_segment(make::name_ref(happy_variant)));
56 let it = make::ident_pat(make::name("a")).into();
57 let ok_tuple = make::tuple_struct_pat(ok_path, iter::once(it)).into();
58
59 let bind_path = make::path_unqualified(make::path_segment(make::name_ref("a")));
60 let ok_arm = make::match_arm(iter::once(ok_tuple), make::expr_path(bind_path));
61
62 let unreachable_call = make::expr_unreachable();
63 let err_arm =
64 make::match_arm(iter::once(make::wildcard_pat().into()), unreachable_call);
65
66 let match_arm_list = make::match_arm_list(vec![ok_arm, err_arm]);
67 let match_expr = make::expr_match(caller.clone(), match_arm_list)
68 .indent(IndentLevel::from_node(method_call.syntax()));
69
70 let range = method_call.syntax().text_range();
71 match ctx.config.snippet_cap {
72 Some(cap) => {
73 let err_arm = match_expr
74 .syntax()
75 .descendants()
76 .filter_map(ast::MatchArm::cast)
77 .last()
78 .unwrap();
79 let snippet =
80 render_snippet(cap, match_expr.syntax(), Cursor::Before(err_arm.syntax()));
81 builder.replace_snippet(cap, range, snippet)
82 }
83 None => builder.replace(range, match_expr.to_string()),
84 }
85 },
86 )
87}
88
89#[cfg(test)]
90mod tests {
91 use crate::tests::{check_assist, check_assist_target};
92
93 use super::*;
94
95 #[test]
96 fn test_replace_result_unwrap_with_match() {
97 check_assist(
98 replace_unwrap_with_match,
99 r"
100enum Result<T, E> { Ok(T), Err(E) }
101fn i<T>(a: T) -> T { a }
102fn main() {
103 let x: Result<i32, i32> = Result::Ok(92);
104 let y = i(x).$0unwrap();
105}
106 ",
107 r"
108enum Result<T, E> { Ok(T), Err(E) }
109fn i<T>(a: T) -> T { a }
110fn main() {
111 let x: Result<i32, i32> = Result::Ok(92);
112 let y = match i(x) {
113 Ok(a) => a,
114 $0_ => unreachable!(),
115 };
116}
117 ",
118 )
119 }
120
121 #[test]
122 fn test_replace_option_unwrap_with_match() {
123 check_assist(
124 replace_unwrap_with_match,
125 r"
126enum Option<T> { Some(T), None }
127fn i<T>(a: T) -> T { a }
128fn main() {
129 let x = Option::Some(92);
130 let y = i(x).$0unwrap();
131}
132 ",
133 r"
134enum Option<T> { Some(T), None }
135fn i<T>(a: T) -> T { a }
136fn main() {
137 let x = Option::Some(92);
138 let y = match i(x) {
139 Some(a) => a,
140 $0_ => unreachable!(),
141 };
142}
143 ",
144 );
145 }
146
147 #[test]
148 fn test_replace_result_unwrap_with_match_chaining() {
149 check_assist(
150 replace_unwrap_with_match,
151 r"
152enum Result<T, E> { Ok(T), Err(E) }
153fn i<T>(a: T) -> T { a }
154fn main() {
155 let x: Result<i32, i32> = Result::Ok(92);
156 let y = i(x).$0unwrap().count_zeroes();
157}
158 ",
159 r"
160enum Result<T, E> { Ok(T), Err(E) }
161fn i<T>(a: T) -> T { a }
162fn main() {
163 let x: Result<i32, i32> = Result::Ok(92);
164 let y = match i(x) {
165 Ok(a) => a,
166 $0_ => unreachable!(),
167 }.count_zeroes();
168}
169 ",
170 )
171 }
172
173 #[test]
174 fn replace_unwrap_with_match_target() {
175 check_assist_target(
176 replace_unwrap_with_match,
177 r"
178enum Option<T> { Some(T), None }
179fn i<T>(a: T) -> T { a }
180fn main() {
181 let x = Option::Some(92);
182 let y = i(x).$0unwrap();
183}
184 ",
185 r"i(x).unwrap()",
186 );
187 }
188}
diff --git a/crates/assists/src/handlers/split_import.rs b/crates/assists/src/handlers/split_import.rs
deleted file mode 100644
index 9319a4267..000000000
--- a/crates/assists/src/handlers/split_import.rs
+++ /dev/null
@@ -1,79 +0,0 @@
1use std::iter::successors;
2
3use syntax::{ast, AstNode, T};
4
5use crate::{AssistContext, AssistId, AssistKind, Assists};
6
7// Assist: split_import
8//
9// Wraps the tail of import into braces.
10//
11// ```
12// use std::$0collections::HashMap;
13// ```
14// ->
15// ```
16// use std::{collections::HashMap};
17// ```
18pub(crate) fn split_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
19 let colon_colon = ctx.find_token_syntax_at_offset(T![::])?;
20 let path = ast::Path::cast(colon_colon.parent())?.qualifier()?;
21 let top_path = successors(Some(path.clone()), |it| it.parent_path()).last()?;
22
23 let use_tree = top_path.syntax().ancestors().find_map(ast::UseTree::cast)?;
24
25 let new_tree = use_tree.split_prefix(&path);
26 if new_tree == use_tree {
27 return None;
28 }
29
30 let target = colon_colon.text_range();
31 acc.add(AssistId("split_import", AssistKind::RefactorRewrite), "Split import", target, |edit| {
32 edit.replace_ast(use_tree, new_tree);
33 })
34}
35
36#[cfg(test)]
37mod tests {
38 use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
39
40 use super::*;
41
42 #[test]
43 fn test_split_import() {
44 check_assist(
45 split_import,
46 "use crate::$0db::RootDatabase;",
47 "use crate::{db::RootDatabase};",
48 )
49 }
50
51 #[test]
52 fn split_import_works_with_trees() {
53 check_assist(
54 split_import,
55 "use crate:$0:db::{RootDatabase, FileSymbol}",
56 "use crate::{db::{RootDatabase, FileSymbol}}",
57 )
58 }
59
60 #[test]
61 fn split_import_target() {
62 check_assist_target(split_import, "use crate::$0db::{RootDatabase, FileSymbol}", "::");
63 }
64
65 #[test]
66 fn issue4044() {
67 check_assist_not_applicable(split_import, "use crate::$0:::self;")
68 }
69
70 #[test]
71 fn test_empty_use() {
72 check_assist_not_applicable(
73 split_import,
74 r"
75use std::$0
76fn main() {}",
77 );
78 }
79}
diff --git a/crates/assists/src/handlers/toggle_ignore.rs b/crates/assists/src/handlers/toggle_ignore.rs
deleted file mode 100644
index 33e12a7d0..000000000
--- a/crates/assists/src/handlers/toggle_ignore.rs
+++ /dev/null
@@ -1,98 +0,0 @@
1use syntax::{
2 ast::{self, AttrsOwner},
3 AstNode, AstToken,
4};
5
6use crate::{utils::test_related_attribute, AssistContext, AssistId, AssistKind, Assists};
7
8// Assist: toggle_ignore
9//
10// Adds `#[ignore]` attribute to the test.
11//
12// ```
13// $0#[test]
14// fn arithmetics {
15// assert_eq!(2 + 2, 5);
16// }
17// ```
18// ->
19// ```
20// #[test]
21// #[ignore]
22// fn arithmetics {
23// assert_eq!(2 + 2, 5);
24// }
25// ```
26pub(crate) fn toggle_ignore(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
27 let attr: ast::Attr = ctx.find_node_at_offset()?;
28 let func = attr.syntax().parent().and_then(ast::Fn::cast)?;
29 let attr = test_related_attribute(&func)?;
30
31 match has_ignore_attribute(&func) {
32 None => acc.add(
33 AssistId("toggle_ignore", AssistKind::None),
34 "Ignore this test",
35 attr.syntax().text_range(),
36 |builder| builder.insert(attr.syntax().text_range().end(), &format!("\n#[ignore]")),
37 ),
38 Some(ignore_attr) => acc.add(
39 AssistId("toggle_ignore", AssistKind::None),
40 "Re-enable this test",
41 ignore_attr.syntax().text_range(),
42 |builder| {
43 builder.delete(ignore_attr.syntax().text_range());
44 let whitespace = ignore_attr
45 .syntax()
46 .next_sibling_or_token()
47 .and_then(|x| x.into_token())
48 .and_then(ast::Whitespace::cast);
49 if let Some(whitespace) = whitespace {
50 builder.delete(whitespace.syntax().text_range());
51 }
52 },
53 ),
54 }
55}
56
57fn has_ignore_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
58 fn_def.attrs().find(|attr| attr.path().map(|it| it.syntax().text() == "ignore") == Some(true))
59}
60
61#[cfg(test)]
62mod tests {
63 use crate::tests::check_assist;
64
65 use super::*;
66
67 #[test]
68 fn test_base_case() {
69 check_assist(
70 toggle_ignore,
71 r#"
72 #[test$0]
73 fn test() {}
74 "#,
75 r#"
76 #[test]
77 #[ignore]
78 fn test() {}
79 "#,
80 )
81 }
82
83 #[test]
84 fn test_unignore() {
85 check_assist(
86 toggle_ignore,
87 r#"
88 #[test$0]
89 #[ignore]
90 fn test() {}
91 "#,
92 r#"
93 #[test]
94 fn test() {}
95 "#,
96 )
97 }
98}
diff --git a/crates/assists/src/handlers/unmerge_use.rs b/crates/assists/src/handlers/unmerge_use.rs
deleted file mode 100644
index 3dbef8e51..000000000
--- a/crates/assists/src/handlers/unmerge_use.rs
+++ /dev/null
@@ -1,231 +0,0 @@
1use syntax::{
2 algo::SyntaxRewriter,
3 ast::{self, edit::AstNodeEdit, VisibilityOwner},
4 AstNode, SyntaxKind,
5};
6use test_utils::mark;
7
8use crate::{
9 assist_context::{AssistContext, Assists},
10 AssistId, AssistKind,
11};
12
13// Assist: unmerge_use
14//
15// Extracts single use item from use list.
16//
17// ```
18// use std::fmt::{Debug, Display$0};
19// ```
20// ->
21// ```
22// use std::fmt::{Debug};
23// use std::fmt::Display;
24// ```
25pub(crate) fn unmerge_use(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
26 let tree: ast::UseTree = ctx.find_node_at_offset()?;
27
28 let tree_list = tree.syntax().parent().and_then(ast::UseTreeList::cast)?;
29 if tree_list.use_trees().count() < 2 {
30 mark::hit!(skip_single_use_item);
31 return None;
32 }
33
34 let use_: ast::Use = tree_list.syntax().ancestors().find_map(ast::Use::cast)?;
35 let path = resolve_full_path(&tree)?;
36
37 let target = tree.syntax().text_range();
38 acc.add(
39 AssistId("unmerge_use", AssistKind::RefactorRewrite),
40 "Unmerge use",
41 target,
42 |builder| {
43 let new_use = ast::make::use_(
44 use_.visibility(),
45 ast::make::use_tree(
46 path,
47 tree.use_tree_list(),
48 tree.rename(),
49 tree.star_token().is_some(),
50 ),
51 );
52
53 let mut rewriter = SyntaxRewriter::default();
54 rewriter += tree.remove();
55 rewriter.insert_after(use_.syntax(), &ast::make::tokens::single_newline());
56 if let ident_level @ 1..=usize::MAX = use_.indent_level().0 as usize {
57 rewriter.insert_after(
58 use_.syntax(),
59 &ast::make::tokens::whitespace(&" ".repeat(4 * ident_level)),
60 );
61 }
62 rewriter.insert_after(use_.syntax(), new_use.syntax());
63
64 builder.rewrite(rewriter);
65 },
66 )
67}
68
69fn resolve_full_path(tree: &ast::UseTree) -> Option<ast::Path> {
70 let mut paths = tree
71 .syntax()
72 .ancestors()
73 .take_while(|n| n.kind() != SyntaxKind::USE_KW)
74 .filter_map(ast::UseTree::cast)
75 .filter_map(|t| t.path());
76
77 let mut final_path = paths.next()?;
78 for path in paths {
79 final_path = ast::make::path_concat(path, final_path)
80 }
81 Some(final_path)
82}
83
84#[cfg(test)]
85mod tests {
86 use crate::tests::{check_assist, check_assist_not_applicable};
87
88 use super::*;
89
90 #[test]
91 fn skip_single_use_item() {
92 mark::check!(skip_single_use_item);
93 check_assist_not_applicable(
94 unmerge_use,
95 r"
96use std::fmt::Debug$0;
97",
98 );
99 check_assist_not_applicable(
100 unmerge_use,
101 r"
102use std::fmt::{Debug$0};
103",
104 );
105 check_assist_not_applicable(
106 unmerge_use,
107 r"
108use std::fmt::Debug as Dbg$0;
109",
110 );
111 }
112
113 #[test]
114 fn skip_single_glob_import() {
115 check_assist_not_applicable(
116 unmerge_use,
117 r"
118use std::fmt::*$0;
119",
120 );
121 }
122
123 #[test]
124 fn unmerge_use_item() {
125 check_assist(
126 unmerge_use,
127 r"
128use std::fmt::{Debug, Display$0};
129",
130 r"
131use std::fmt::{Debug};
132use std::fmt::Display;
133",
134 );
135
136 check_assist(
137 unmerge_use,
138 r"
139use std::fmt::{Debug, format$0, Display};
140",
141 r"
142use std::fmt::{Debug, Display};
143use std::fmt::format;
144",
145 );
146 }
147
148 #[test]
149 fn unmerge_glob_import() {
150 check_assist(
151 unmerge_use,
152 r"
153use std::fmt::{*$0, Display};
154",
155 r"
156use std::fmt::{Display};
157use std::fmt::*;
158",
159 );
160 }
161
162 #[test]
163 fn unmerge_renamed_use_item() {
164 check_assist(
165 unmerge_use,
166 r"
167use std::fmt::{Debug, Display as Disp$0};
168",
169 r"
170use std::fmt::{Debug};
171use std::fmt::Display as Disp;
172",
173 );
174 }
175
176 #[test]
177 fn unmerge_indented_use_item() {
178 check_assist(
179 unmerge_use,
180 r"
181mod format {
182 use std::fmt::{Debug, Display$0 as Disp, format};
183}
184",
185 r"
186mod format {
187 use std::fmt::{Debug, format};
188 use std::fmt::Display as Disp;
189}
190",
191 );
192 }
193
194 #[test]
195 fn unmerge_nested_use_item() {
196 check_assist(
197 unmerge_use,
198 r"
199use foo::bar::{baz::{qux$0, foobar}, barbaz};
200",
201 r"
202use foo::bar::{baz::{foobar}, barbaz};
203use foo::bar::baz::qux;
204",
205 );
206 check_assist(
207 unmerge_use,
208 r"
209use foo::bar::{baz$0::{qux, foobar}, barbaz};
210",
211 r"
212use foo::bar::{barbaz};
213use foo::bar::baz::{qux, foobar};
214",
215 );
216 }
217
218 #[test]
219 fn unmerge_use_item_with_visibility() {
220 check_assist(
221 unmerge_use,
222 r"
223pub use std::fmt::{Debug, Display$0};
224",
225 r"
226pub use std::fmt::{Debug};
227pub use std::fmt::Display;
228",
229 );
230 }
231}
diff --git a/crates/assists/src/handlers/unwrap_block.rs b/crates/assists/src/handlers/unwrap_block.rs
deleted file mode 100644
index ed6f6177d..000000000
--- a/crates/assists/src/handlers/unwrap_block.rs
+++ /dev/null
@@ -1,582 +0,0 @@
1use syntax::{
2 ast::{
3 self,
4 edit::{AstNodeEdit, IndentLevel},
5 },
6 AstNode, SyntaxKind, TextRange, T,
7};
8
9use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: unwrap_block
12//
13// This assist removes if...else, for, while and loop control statements to just keep the body.
14//
15// ```
16// fn foo() {
17// if true {$0
18// println!("foo");
19// }
20// }
21// ```
22// ->
23// ```
24// fn foo() {
25// println!("foo");
26// }
27// ```
28pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
30 let assist_label = "Unwrap block";
31
32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33 let mut block = ast::BlockExpr::cast(l_curly_token.parent())?;
34 let target = block.syntax().text_range();
35 let mut parent = block.syntax().parent()?;
36 if ast::MatchArm::can_cast(parent.kind()) {
37 parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
38 }
39
40 if matches!(parent.kind(), SyntaxKind::BLOCK_EXPR | SyntaxKind::EXPR_STMT) {
41 return acc.add(assist_id, assist_label, target, |builder| {
42 builder.replace(
43 block.syntax().text_range(),
44 update_expr_string(block.to_string(), &[' ', '{', '\n']),
45 );
46 });
47 }
48
49 let parent = ast::Expr::cast(parent)?;
50
51 match parent.clone() {
52 ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
53 ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
54 ast::Expr::IfExpr(if_expr) => {
55 let then_branch = if_expr.then_branch()?;
56 if then_branch == block {
57 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
58 // For `else if` blocks
59 let ancestor_then_branch = ancestor.then_branch()?;
60
61 return acc.add(assist_id, assist_label, target, |edit| {
62 let range_to_del_else_if = TextRange::new(
63 ancestor_then_branch.syntax().text_range().end(),
64 l_curly_token.text_range().start(),
65 );
66 let range_to_del_rest = TextRange::new(
67 then_branch.syntax().text_range().end(),
68 if_expr.syntax().text_range().end(),
69 );
70
71 edit.delete(range_to_del_rest);
72 edit.delete(range_to_del_else_if);
73 edit.replace(
74 target,
75 update_expr_string(then_branch.to_string(), &[' ', '{']),
76 );
77 });
78 }
79 } else {
80 return acc.add(assist_id, assist_label, target, |edit| {
81 let range_to_del = TextRange::new(
82 then_branch.syntax().text_range().end(),
83 l_curly_token.text_range().start(),
84 );
85
86 edit.delete(range_to_del);
87 edit.replace(target, update_expr_string(block.to_string(), &[' ', '{']));
88 });
89 }
90 }
91 _ => return None,
92 };
93
94 let unwrapped = unwrap_trivial_block(block);
95 acc.add(assist_id, assist_label, target, |builder| {
96 builder.replace(
97 parent.syntax().text_range(),
98 update_expr_string(unwrapped.to_string(), &[' ', '{', '\n']),
99 );
100 })
101}
102
103fn update_expr_string(expr_str: String, trim_start_pat: &[char]) -> String {
104 let expr_string = expr_str.trim_start_matches(trim_start_pat);
105 let mut expr_string_lines: Vec<&str> = expr_string.lines().collect();
106 expr_string_lines.pop(); // Delete last line
107
108 expr_string_lines
109 .into_iter()
110 .map(|line| line.replacen(" ", "", 1)) // Delete indentation
111 .collect::<Vec<String>>()
112 .join("\n")
113}
114
115#[cfg(test)]
116mod tests {
117 use crate::tests::{check_assist, check_assist_not_applicable};
118
119 use super::*;
120
121 #[test]
122 fn unwrap_tail_expr_block() {
123 check_assist(
124 unwrap_block,
125 r#"
126fn main() {
127 $0{
128 92
129 }
130}
131"#,
132 r#"
133fn main() {
134 92
135}
136"#,
137 )
138 }
139
140 #[test]
141 fn unwrap_stmt_expr_block() {
142 check_assist(
143 unwrap_block,
144 r#"
145fn main() {
146 $0{
147 92;
148 }
149 ()
150}
151"#,
152 r#"
153fn main() {
154 92;
155 ()
156}
157"#,
158 );
159 // Pedantically, we should add an `;` here...
160 check_assist(
161 unwrap_block,
162 r#"
163fn main() {
164 $0{
165 92
166 }
167 ()
168}
169"#,
170 r#"
171fn main() {
172 92
173 ()
174}
175"#,
176 );
177 }
178
179 #[test]
180 fn simple_if() {
181 check_assist(
182 unwrap_block,
183 r#"
184fn main() {
185 bar();
186 if true {$0
187 foo();
188
189 //comment
190 bar();
191 } else {
192 println!("bar");
193 }
194}
195"#,
196 r#"
197fn main() {
198 bar();
199 foo();
200
201 //comment
202 bar();
203}
204"#,
205 );
206 }
207
208 #[test]
209 fn simple_if_else() {
210 check_assist(
211 unwrap_block,
212 r#"
213fn main() {
214 bar();
215 if true {
216 foo();
217
218 //comment
219 bar();
220 } else {$0
221 println!("bar");
222 }
223}
224"#,
225 r#"
226fn main() {
227 bar();
228 if true {
229 foo();
230
231 //comment
232 bar();
233 }
234 println!("bar");
235}
236"#,
237 );
238 }
239
240 #[test]
241 fn simple_if_else_if() {
242 check_assist(
243 unwrap_block,
244 r#"
245fn main() {
246 //bar();
247 if true {
248 println!("true");
249
250 //comment
251 //bar();
252 } else if false {$0
253 println!("bar");
254 } else {
255 println!("foo");
256 }
257}
258"#,
259 r#"
260fn main() {
261 //bar();
262 if true {
263 println!("true");
264
265 //comment
266 //bar();
267 }
268 println!("bar");
269}
270"#,
271 );
272 }
273
274 #[test]
275 fn simple_if_else_if_nested() {
276 check_assist(
277 unwrap_block,
278 r#"
279fn main() {
280 //bar();
281 if true {
282 println!("true");
283
284 //comment
285 //bar();
286 } else if false {
287 println!("bar");
288 } else if true {$0
289 println!("foo");
290 }
291}
292"#,
293 r#"
294fn main() {
295 //bar();
296 if true {
297 println!("true");
298
299 //comment
300 //bar();
301 } else if false {
302 println!("bar");
303 }
304 println!("foo");
305}
306"#,
307 );
308 }
309
310 #[test]
311 fn simple_if_else_if_nested_else() {
312 check_assist(
313 unwrap_block,
314 r#"
315fn main() {
316 //bar();
317 if true {
318 println!("true");
319
320 //comment
321 //bar();
322 } else if false {
323 println!("bar");
324 } else if true {
325 println!("foo");
326 } else {$0
327 println!("else");
328 }
329}
330"#,
331 r#"
332fn main() {
333 //bar();
334 if true {
335 println!("true");
336
337 //comment
338 //bar();
339 } else if false {
340 println!("bar");
341 } else if true {
342 println!("foo");
343 }
344 println!("else");
345}
346"#,
347 );
348 }
349
350 #[test]
351 fn simple_if_else_if_nested_middle() {
352 check_assist(
353 unwrap_block,
354 r#"
355fn main() {
356 //bar();
357 if true {
358 println!("true");
359
360 //comment
361 //bar();
362 } else if false {
363 println!("bar");
364 } else if true {$0
365 println!("foo");
366 } else {
367 println!("else");
368 }
369}
370"#,
371 r#"
372fn main() {
373 //bar();
374 if true {
375 println!("true");
376
377 //comment
378 //bar();
379 } else if false {
380 println!("bar");
381 }
382 println!("foo");
383}
384"#,
385 );
386 }
387
388 #[test]
389 fn simple_if_bad_cursor_position() {
390 check_assist_not_applicable(
391 unwrap_block,
392 r#"
393fn main() {
394 bar();$0
395 if true {
396 foo();
397
398 //comment
399 bar();
400 } else {
401 println!("bar");
402 }
403}
404"#,
405 );
406 }
407
408 #[test]
409 fn simple_for() {
410 check_assist(
411 unwrap_block,
412 r#"
413fn main() {
414 for i in 0..5 {$0
415 if true {
416 foo();
417
418 //comment
419 bar();
420 } else {
421 println!("bar");
422 }
423 }
424}
425"#,
426 r#"
427fn main() {
428 if true {
429 foo();
430
431 //comment
432 bar();
433 } else {
434 println!("bar");
435 }
436}
437"#,
438 );
439 }
440
441 #[test]
442 fn simple_if_in_for() {
443 check_assist(
444 unwrap_block,
445 r#"
446fn main() {
447 for i in 0..5 {
448 if true {$0
449 foo();
450
451 //comment
452 bar();
453 } else {
454 println!("bar");
455 }
456 }
457}
458"#,
459 r#"
460fn main() {
461 for i in 0..5 {
462 foo();
463
464 //comment
465 bar();
466 }
467}
468"#,
469 );
470 }
471
472 #[test]
473 fn simple_loop() {
474 check_assist(
475 unwrap_block,
476 r#"
477fn main() {
478 loop {$0
479 if true {
480 foo();
481
482 //comment
483 bar();
484 } else {
485 println!("bar");
486 }
487 }
488}
489"#,
490 r#"
491fn main() {
492 if true {
493 foo();
494
495 //comment
496 bar();
497 } else {
498 println!("bar");
499 }
500}
501"#,
502 );
503 }
504
505 #[test]
506 fn simple_while() {
507 check_assist(
508 unwrap_block,
509 r#"
510fn main() {
511 while true {$0
512 if true {
513 foo();
514
515 //comment
516 bar();
517 } else {
518 println!("bar");
519 }
520 }
521}
522"#,
523 r#"
524fn main() {
525 if true {
526 foo();
527
528 //comment
529 bar();
530 } else {
531 println!("bar");
532 }
533}
534"#,
535 );
536 }
537
538 #[test]
539 fn unwrap_match_arm() {
540 check_assist(
541 unwrap_block,
542 r#"
543fn main() {
544 match rel_path {
545 Ok(rel_path) => {$0
546 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
547 Some((*id, rel_path))
548 }
549 Err(_) => None,
550 }
551}
552"#,
553 r#"
554fn main() {
555 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
556 Some((*id, rel_path))
557}
558"#,
559 );
560 }
561
562 #[test]
563 fn simple_if_in_while_bad_cursor_position() {
564 check_assist_not_applicable(
565 unwrap_block,
566 r#"
567fn main() {
568 while true {
569 if true {
570 foo();$0
571
572 //comment
573 bar();
574 } else {
575 println!("bar");
576 }
577 }
578}
579"#,
580 );
581 }
582}
diff --git a/crates/assists/src/handlers/wrap_return_type_in_result.rs b/crates/assists/src/handlers/wrap_return_type_in_result.rs
deleted file mode 100644
index fec16fc49..000000000
--- a/crates/assists/src/handlers/wrap_return_type_in_result.rs
+++ /dev/null
@@ -1,1158 +0,0 @@
1use std::iter;
2
3use syntax::{
4 ast::{self, make, BlockExpr, Expr, LoopBodyOwner},
5 match_ast, AstNode, SyntaxNode,
6};
7use test_utils::mark;
8
9use crate::{AssistContext, AssistId, AssistKind, Assists};
10
11// Assist: wrap_return_type_in_result
12//
13// Wrap the function's return type into Result.
14//
15// ```
16// fn foo() -> i32$0 { 42i32 }
17// ```
18// ->
19// ```
20// fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
21// ```
22pub(crate) fn wrap_return_type_in_result(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
23 let ret_type = ctx.find_node_at_offset::<ast::RetType>()?;
24 let parent = ret_type.syntax().parent()?;
25 let block_expr = match_ast! {
26 match parent {
27 ast::Fn(func) => func.body()?,
28 ast::ClosureExpr(closure) => match closure.body()? {
29 Expr::BlockExpr(block) => block,
30 // closures require a block when a return type is specified
31 _ => return None,
32 },
33 _ => return None,
34 }
35 };
36
37 let type_ref = &ret_type.ty()?;
38 let ret_type_str = type_ref.syntax().text().to_string();
39 let first_part_ret_type = ret_type_str.splitn(2, '<').next();
40 if let Some(ret_type_first_part) = first_part_ret_type {
41 if ret_type_first_part.ends_with("Result") {
42 mark::hit!(wrap_return_type_in_result_simple_return_type_already_result);
43 return None;
44 }
45 }
46
47 acc.add(
48 AssistId("wrap_return_type_in_result", AssistKind::RefactorRewrite),
49 "Wrap return type in Result",
50 type_ref.syntax().text_range(),
51 |builder| {
52 let mut tail_return_expr_collector = TailReturnCollector::new();
53 tail_return_expr_collector.collect_jump_exprs(&block_expr, false);
54 tail_return_expr_collector.collect_tail_exprs(&block_expr);
55
56 for ret_expr_arg in tail_return_expr_collector.exprs_to_wrap {
57 let ok_wrapped = make::expr_call(
58 make::expr_path(make::path_unqualified(make::path_segment(make::name_ref(
59 "Ok",
60 )))),
61 make::arg_list(iter::once(ret_expr_arg.clone())),
62 );
63 builder.replace_ast(ret_expr_arg, ok_wrapped);
64 }
65
66 match ctx.config.snippet_cap {
67 Some(cap) => {
68 let snippet = format!("Result<{}, ${{0:_}}>", type_ref);
69 builder.replace_snippet(cap, type_ref.syntax().text_range(), snippet)
70 }
71 None => builder
72 .replace(type_ref.syntax().text_range(), format!("Result<{}, _>", type_ref)),
73 }
74 },
75 )
76}
77
78struct TailReturnCollector {
79 exprs_to_wrap: Vec<ast::Expr>,
80}
81
82impl TailReturnCollector {
83 fn new() -> Self {
84 Self { exprs_to_wrap: vec![] }
85 }
86 /// Collect all`return` expression
87 fn collect_jump_exprs(&mut self, block_expr: &BlockExpr, collect_break: bool) {
88 let statements = block_expr.statements();
89 for stmt in statements {
90 let expr = match &stmt {
91 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
92 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
93 ast::Stmt::Item(_) => continue,
94 };
95 if let Some(expr) = &expr {
96 self.handle_exprs(expr, collect_break);
97 }
98 }
99
100 // Browse tail expressions for each block
101 if let Some(expr) = block_expr.tail_expr() {
102 if let Some(last_exprs) = get_tail_expr_from_block(&expr) {
103 for last_expr in last_exprs {
104 let last_expr = match last_expr {
105 NodeType::Node(expr) => expr,
106 NodeType::Leaf(expr) => expr.syntax().clone(),
107 };
108
109 if let Some(last_expr) = Expr::cast(last_expr.clone()) {
110 self.handle_exprs(&last_expr, collect_break);
111 } else if let Some(expr_stmt) = ast::Stmt::cast(last_expr) {
112 let expr_stmt = match &expr_stmt {
113 ast::Stmt::ExprStmt(stmt) => stmt.expr(),
114 ast::Stmt::LetStmt(stmt) => stmt.initializer(),
115 ast::Stmt::Item(_) => None,
116 };
117 if let Some(expr) = &expr_stmt {
118 self.handle_exprs(expr, collect_break);
119 }
120 }
121 }
122 }
123 }
124 }
125
126 fn handle_exprs(&mut self, expr: &Expr, collect_break: bool) {
127 match expr {
128 Expr::BlockExpr(block_expr) => {
129 self.collect_jump_exprs(&block_expr, collect_break);
130 }
131 Expr::ReturnExpr(ret_expr) => {
132 if let Some(ret_expr_arg) = &ret_expr.expr() {
133 self.exprs_to_wrap.push(ret_expr_arg.clone());
134 }
135 }
136 Expr::BreakExpr(break_expr) if collect_break => {
137 if let Some(break_expr_arg) = &break_expr.expr() {
138 self.exprs_to_wrap.push(break_expr_arg.clone());
139 }
140 }
141 Expr::IfExpr(if_expr) => {
142 for block in if_expr.blocks() {
143 self.collect_jump_exprs(&block, collect_break);
144 }
145 }
146 Expr::LoopExpr(loop_expr) => {
147 if let Some(block_expr) = loop_expr.loop_body() {
148 self.collect_jump_exprs(&block_expr, collect_break);
149 }
150 }
151 Expr::ForExpr(for_expr) => {
152 if let Some(block_expr) = for_expr.loop_body() {
153 self.collect_jump_exprs(&block_expr, collect_break);
154 }
155 }
156 Expr::WhileExpr(while_expr) => {
157 if let Some(block_expr) = while_expr.loop_body() {
158 self.collect_jump_exprs(&block_expr, collect_break);
159 }
160 }
161 Expr::MatchExpr(match_expr) => {
162 if let Some(arm_list) = match_expr.match_arm_list() {
163 arm_list.arms().filter_map(|match_arm| match_arm.expr()).for_each(|expr| {
164 self.handle_exprs(&expr, collect_break);
165 });
166 }
167 }
168 _ => {}
169 }
170 }
171
172 fn collect_tail_exprs(&mut self, block: &BlockExpr) {
173 if let Some(expr) = block.tail_expr() {
174 self.handle_exprs(&expr, true);
175 self.fetch_tail_exprs(&expr);
176 }
177 }
178
179 fn fetch_tail_exprs(&mut self, expr: &Expr) {
180 if let Some(exprs) = get_tail_expr_from_block(expr) {
181 for node_type in &exprs {
182 match node_type {
183 NodeType::Leaf(expr) => {
184 self.exprs_to_wrap.push(expr.clone());
185 }
186 NodeType::Node(expr) => {
187 if let Some(last_expr) = Expr::cast(expr.clone()) {
188 self.fetch_tail_exprs(&last_expr);
189 }
190 }
191 }
192 }
193 }
194 }
195}
196
197#[derive(Debug)]
198enum NodeType {
199 Leaf(ast::Expr),
200 Node(SyntaxNode),
201}
202
203/// Get a tail expression inside a block
204fn get_tail_expr_from_block(expr: &Expr) -> Option<Vec<NodeType>> {
205 match expr {
206 Expr::IfExpr(if_expr) => {
207 let mut nodes = vec![];
208 for block in if_expr.blocks() {
209 if let Some(block_expr) = block.tail_expr() {
210 if let Some(tail_exprs) = get_tail_expr_from_block(&block_expr) {
211 nodes.extend(tail_exprs);
212 }
213 } else if let Some(last_expr) = block.syntax().last_child() {
214 nodes.push(NodeType::Node(last_expr));
215 } else {
216 nodes.push(NodeType::Node(block.syntax().clone()));
217 }
218 }
219 Some(nodes)
220 }
221 Expr::LoopExpr(loop_expr) => {
222 loop_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
223 }
224 Expr::ForExpr(for_expr) => {
225 for_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
226 }
227 Expr::WhileExpr(while_expr) => {
228 while_expr.syntax().last_child().map(|lc| vec![NodeType::Node(lc)])
229 }
230 Expr::BlockExpr(block_expr) => {
231 block_expr.tail_expr().map(|lc| vec![NodeType::Node(lc.syntax().clone())])
232 }
233 Expr::MatchExpr(match_expr) => {
234 let arm_list = match_expr.match_arm_list()?;
235 let arms: Vec<NodeType> = arm_list
236 .arms()
237 .filter_map(|match_arm| match_arm.expr())
238 .map(|expr| match expr {
239 Expr::ReturnExpr(ret_expr) => NodeType::Node(ret_expr.syntax().clone()),
240 Expr::BreakExpr(break_expr) => NodeType::Node(break_expr.syntax().clone()),
241 _ => match expr.syntax().last_child() {
242 Some(last_expr) => NodeType::Node(last_expr),
243 None => NodeType::Node(expr.syntax().clone()),
244 },
245 })
246 .collect();
247
248 Some(arms)
249 }
250 Expr::BreakExpr(expr) => expr.expr().map(|e| vec![NodeType::Leaf(e)]),
251 Expr::ReturnExpr(ret_expr) => Some(vec![NodeType::Node(ret_expr.syntax().clone())]),
252
253 Expr::CallExpr(_)
254 | Expr::Literal(_)
255 | Expr::TupleExpr(_)
256 | Expr::ArrayExpr(_)
257 | Expr::ParenExpr(_)
258 | Expr::PathExpr(_)
259 | Expr::RecordExpr(_)
260 | Expr::IndexExpr(_)
261 | Expr::MethodCallExpr(_)
262 | Expr::AwaitExpr(_)
263 | Expr::CastExpr(_)
264 | Expr::RefExpr(_)
265 | Expr::PrefixExpr(_)
266 | Expr::RangeExpr(_)
267 | Expr::BinExpr(_)
268 | Expr::MacroCall(_)
269 | Expr::BoxExpr(_) => Some(vec![NodeType::Leaf(expr.clone())]),
270 _ => None,
271 }
272}
273
274#[cfg(test)]
275mod tests {
276 use crate::tests::{check_assist, check_assist_not_applicable};
277
278 use super::*;
279
280 #[test]
281 fn wrap_return_type_in_result_simple() {
282 check_assist(
283 wrap_return_type_in_result,
284 r#"
285fn foo() -> i3$02 {
286 let test = "test";
287 return 42i32;
288}
289"#,
290 r#"
291fn foo() -> Result<i32, ${0:_}> {
292 let test = "test";
293 return Ok(42i32);
294}
295"#,
296 );
297 }
298
299 #[test]
300 fn wrap_return_type_in_result_simple_closure() {
301 check_assist(
302 wrap_return_type_in_result,
303 r#"
304fn foo() {
305 || -> i32$0 {
306 let test = "test";
307 return 42i32;
308 };
309}
310"#,
311 r#"
312fn foo() {
313 || -> Result<i32, ${0:_}> {
314 let test = "test";
315 return Ok(42i32);
316 };
317}
318"#,
319 );
320 }
321
322 #[test]
323 fn wrap_return_type_in_result_simple_return_type_bad_cursor() {
324 check_assist_not_applicable(
325 wrap_return_type_in_result,
326 r#"
327fn foo() -> i32 {
328 let test = "test";$0
329 return 42i32;
330}
331"#,
332 );
333 }
334
335 #[test]
336 fn wrap_return_type_in_result_simple_return_type_bad_cursor_closure() {
337 check_assist_not_applicable(
338 wrap_return_type_in_result,
339 r#"
340fn foo() {
341 || -> i32 {
342 let test = "test";$0
343 return 42i32;
344 };
345}
346"#,
347 );
348 }
349
350 #[test]
351 fn wrap_return_type_in_result_closure_non_block() {
352 check_assist_not_applicable(wrap_return_type_in_result, r#"fn foo() { || -> i$032 3; }"#);
353 }
354
355 #[test]
356 fn wrap_return_type_in_result_simple_return_type_already_result_std() {
357 check_assist_not_applicable(
358 wrap_return_type_in_result,
359 r#"
360fn foo() -> std::result::Result<i32$0, String> {
361 let test = "test";
362 return 42i32;
363}
364"#,
365 );
366 }
367
368 #[test]
369 fn wrap_return_type_in_result_simple_return_type_already_result() {
370 mark::check!(wrap_return_type_in_result_simple_return_type_already_result);
371 check_assist_not_applicable(
372 wrap_return_type_in_result,
373 r#"
374fn foo() -> Result<i32$0, String> {
375 let test = "test";
376 return 42i32;
377}
378"#,
379 );
380 }
381
382 #[test]
383 fn wrap_return_type_in_result_simple_return_type_already_result_closure() {
384 check_assist_not_applicable(
385 wrap_return_type_in_result,
386 r#"
387fn foo() {
388 || -> Result<i32$0, String> {
389 let test = "test";
390 return 42i32;
391 };
392}
393"#,
394 );
395 }
396
397 #[test]
398 fn wrap_return_type_in_result_simple_with_cursor() {
399 check_assist(
400 wrap_return_type_in_result,
401 r#"
402fn foo() -> $0i32 {
403 let test = "test";
404 return 42i32;
405}
406"#,
407 r#"
408fn foo() -> Result<i32, ${0:_}> {
409 let test = "test";
410 return Ok(42i32);
411}
412"#,
413 );
414 }
415
416 #[test]
417 fn wrap_return_type_in_result_simple_with_tail() {
418 check_assist(
419 wrap_return_type_in_result,
420 r#"
421fn foo() ->$0 i32 {
422 let test = "test";
423 42i32
424}
425"#,
426 r#"
427fn foo() -> Result<i32, ${0:_}> {
428 let test = "test";
429 Ok(42i32)
430}
431"#,
432 );
433 }
434
435 #[test]
436 fn wrap_return_type_in_result_simple_with_tail_closure() {
437 check_assist(
438 wrap_return_type_in_result,
439 r#"
440fn foo() {
441 || ->$0 i32 {
442 let test = "test";
443 42i32
444 };
445}
446"#,
447 r#"
448fn foo() {
449 || -> Result<i32, ${0:_}> {
450 let test = "test";
451 Ok(42i32)
452 };
453}
454"#,
455 );
456 }
457
458 #[test]
459 fn wrap_return_type_in_result_simple_with_tail_only() {
460 check_assist(
461 wrap_return_type_in_result,
462 r#"fn foo() -> i32$0 { 42i32 }"#,
463 r#"fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }"#,
464 );
465 }
466
467 #[test]
468 fn wrap_return_type_in_result_simple_with_tail_block_like() {
469 check_assist(
470 wrap_return_type_in_result,
471 r#"
472fn foo() -> i32$0 {
473 if true {
474 42i32
475 } else {
476 24i32
477 }
478}
479"#,
480 r#"
481fn foo() -> Result<i32, ${0:_}> {
482 if true {
483 Ok(42i32)
484 } else {
485 Ok(24i32)
486 }
487}
488"#,
489 );
490 }
491
492 #[test]
493 fn wrap_return_type_in_result_simple_without_block_closure() {
494 check_assist(
495 wrap_return_type_in_result,
496 r#"
497fn foo() {
498 || -> i32$0 {
499 if true {
500 42i32
501 } else {
502 24i32
503 }
504 };
505}
506"#,
507 r#"
508fn foo() {
509 || -> Result<i32, ${0:_}> {
510 if true {
511 Ok(42i32)
512 } else {
513 Ok(24i32)
514 }
515 };
516}
517"#,
518 );
519 }
520
521 #[test]
522 fn wrap_return_type_in_result_simple_with_nested_if() {
523 check_assist(
524 wrap_return_type_in_result,
525 r#"
526fn foo() -> i32$0 {
527 if true {
528 if false {
529 1
530 } else {
531 2
532 }
533 } else {
534 24i32
535 }
536}
537"#,
538 r#"
539fn foo() -> Result<i32, ${0:_}> {
540 if true {
541 if false {
542 Ok(1)
543 } else {
544 Ok(2)
545 }
546 } else {
547 Ok(24i32)
548 }
549}
550"#,
551 );
552 }
553
554 #[test]
555 fn wrap_return_type_in_result_simple_with_await() {
556 check_assist(
557 wrap_return_type_in_result,
558 r#"
559async fn foo() -> i$032 {
560 if true {
561 if false {
562 1.await
563 } else {
564 2.await
565 }
566 } else {
567 24i32.await
568 }
569}
570"#,
571 r#"
572async fn foo() -> Result<i32, ${0:_}> {
573 if true {
574 if false {
575 Ok(1.await)
576 } else {
577 Ok(2.await)
578 }
579 } else {
580 Ok(24i32.await)
581 }
582}
583"#,
584 );
585 }
586
587 #[test]
588 fn wrap_return_type_in_result_simple_with_array() {
589 check_assist(
590 wrap_return_type_in_result,
591 r#"fn foo() -> [i32;$0 3] { [1, 2, 3] }"#,
592 r#"fn foo() -> Result<[i32; 3], ${0:_}> { Ok([1, 2, 3]) }"#,
593 );
594 }
595
596 #[test]
597 fn wrap_return_type_in_result_simple_with_cast() {
598 check_assist(
599 wrap_return_type_in_result,
600 r#"
601fn foo() -$0> i32 {
602 if true {
603 if false {
604 1 as i32
605 } else {
606 2 as i32
607 }
608 } else {
609 24 as i32
610 }
611}
612"#,
613 r#"
614fn foo() -> Result<i32, ${0:_}> {
615 if true {
616 if false {
617 Ok(1 as i32)
618 } else {
619 Ok(2 as i32)
620 }
621 } else {
622 Ok(24 as i32)
623 }
624}
625"#,
626 );
627 }
628
629 #[test]
630 fn wrap_return_type_in_result_simple_with_tail_block_like_match() {
631 check_assist(
632 wrap_return_type_in_result,
633 r#"
634fn foo() -> i32$0 {
635 let my_var = 5;
636 match my_var {
637 5 => 42i32,
638 _ => 24i32,
639 }
640}
641"#,
642 r#"
643fn foo() -> Result<i32, ${0:_}> {
644 let my_var = 5;
645 match my_var {
646 5 => Ok(42i32),
647 _ => Ok(24i32),
648 }
649}
650"#,
651 );
652 }
653
654 #[test]
655 fn wrap_return_type_in_result_simple_with_loop_with_tail() {
656 check_assist(
657 wrap_return_type_in_result,
658 r#"
659fn foo() -> i32$0 {
660 let my_var = 5;
661 loop {
662 println!("test");
663 5
664 }
665 my_var
666}
667"#,
668 r#"
669fn foo() -> Result<i32, ${0:_}> {
670 let my_var = 5;
671 loop {
672 println!("test");
673 5
674 }
675 Ok(my_var)
676}
677"#,
678 );
679 }
680
681 #[test]
682 fn wrap_return_type_in_result_simple_with_loop_in_let_stmt() {
683 check_assist(
684 wrap_return_type_in_result,
685 r#"
686fn foo() -> i32$0 {
687 let my_var = let x = loop {
688 break 1;
689 };
690 my_var
691}
692"#,
693 r#"
694fn foo() -> Result<i32, ${0:_}> {
695 let my_var = let x = loop {
696 break 1;
697 };
698 Ok(my_var)
699}
700"#,
701 );
702 }
703
704 #[test]
705 fn wrap_return_type_in_result_simple_with_tail_block_like_match_return_expr() {
706 check_assist(
707 wrap_return_type_in_result,
708 r#"
709fn foo() -> i32$0 {
710 let my_var = 5;
711 let res = match my_var {
712 5 => 42i32,
713 _ => return 24i32,
714 };
715 res
716}
717"#,
718 r#"
719fn foo() -> Result<i32, ${0:_}> {
720 let my_var = 5;
721 let res = match my_var {
722 5 => 42i32,
723 _ => return Ok(24i32),
724 };
725 Ok(res)
726}
727"#,
728 );
729
730 check_assist(
731 wrap_return_type_in_result,
732 r#"
733fn foo() -> i32$0 {
734 let my_var = 5;
735 let res = if my_var == 5 {
736 42i32
737 } else {
738 return 24i32;
739 };
740 res
741}
742"#,
743 r#"
744fn foo() -> Result<i32, ${0:_}> {
745 let my_var = 5;
746 let res = if my_var == 5 {
747 42i32
748 } else {
749 return Ok(24i32);
750 };
751 Ok(res)
752}
753"#,
754 );
755 }
756
757 #[test]
758 fn wrap_return_type_in_result_simple_with_tail_block_like_match_deeper() {
759 check_assist(
760 wrap_return_type_in_result,
761 r#"
762fn foo() -> i32$0 {
763 let my_var = 5;
764 match my_var {
765 5 => {
766 if true {
767 42i32
768 } else {
769 25i32
770 }
771 },
772 _ => {
773 let test = "test";
774 if test == "test" {
775 return bar();
776 }
777 53i32
778 },
779 }
780}
781"#,
782 r#"
783fn foo() -> Result<i32, ${0:_}> {
784 let my_var = 5;
785 match my_var {
786 5 => {
787 if true {
788 Ok(42i32)
789 } else {
790 Ok(25i32)
791 }
792 },
793 _ => {
794 let test = "test";
795 if test == "test" {
796 return Ok(bar());
797 }
798 Ok(53i32)
799 },
800 }
801}
802"#,
803 );
804 }
805
806 #[test]
807 fn wrap_return_type_in_result_simple_with_tail_block_like_early_return() {
808 check_assist(
809 wrap_return_type_in_result,
810 r#"
811fn foo() -> i$032 {
812 let test = "test";
813 if test == "test" {
814 return 24i32;
815 }
816 53i32
817}
818"#,
819 r#"
820fn foo() -> Result<i32, ${0:_}> {
821 let test = "test";
822 if test == "test" {
823 return Ok(24i32);
824 }
825 Ok(53i32)
826}
827"#,
828 );
829 }
830
831 #[test]
832 fn wrap_return_type_in_result_simple_with_closure() {
833 check_assist(
834 wrap_return_type_in_result,
835 r#"
836fn foo(the_field: u32) ->$0 u32 {
837 let true_closure = || { return true; };
838 if the_field < 5 {
839 let mut i = 0;
840 if true_closure() {
841 return 99;
842 } else {
843 return 0;
844 }
845 }
846 the_field
847}
848"#,
849 r#"
850fn foo(the_field: u32) -> Result<u32, ${0:_}> {
851 let true_closure = || { return true; };
852 if the_field < 5 {
853 let mut i = 0;
854 if true_closure() {
855 return Ok(99);
856 } else {
857 return Ok(0);
858 }
859 }
860 Ok(the_field)
861}
862"#,
863 );
864
865 check_assist(
866 wrap_return_type_in_result,
867 r#"
868 fn foo(the_field: u32) -> u32$0 {
869 let true_closure = || {
870 return true;
871 };
872 if the_field < 5 {
873 let mut i = 0;
874
875
876 if true_closure() {
877 return 99;
878 } else {
879 return 0;
880 }
881 }
882 let t = None;
883
884 t.unwrap_or_else(|| the_field)
885 }
886 "#,
887 r#"
888 fn foo(the_field: u32) -> Result<u32, ${0:_}> {
889 let true_closure = || {
890 return true;
891 };
892 if the_field < 5 {
893 let mut i = 0;
894
895
896 if true_closure() {
897 return Ok(99);
898 } else {
899 return Ok(0);
900 }
901 }
902 let t = None;
903
904 Ok(t.unwrap_or_else(|| the_field))
905 }
906 "#,
907 );
908 }
909
910 #[test]
911 fn wrap_return_type_in_result_simple_with_weird_forms() {
912 check_assist(
913 wrap_return_type_in_result,
914 r#"
915fn foo() -> i32$0 {
916 let test = "test";
917 if test == "test" {
918 return 24i32;
919 }
920 let mut i = 0;
921 loop {
922 if i == 1 {
923 break 55;
924 }
925 i += 1;
926 }
927}
928"#,
929 r#"
930fn foo() -> Result<i32, ${0:_}> {
931 let test = "test";
932 if test == "test" {
933 return Ok(24i32);
934 }
935 let mut i = 0;
936 loop {
937 if i == 1 {
938 break Ok(55);
939 }
940 i += 1;
941 }
942}
943"#,
944 );
945
946 check_assist(
947 wrap_return_type_in_result,
948 r#"
949fn foo() -> i32$0 {
950 let test = "test";
951 if test == "test" {
952 return 24i32;
953 }
954 let mut i = 0;
955 loop {
956 loop {
957 if i == 1 {
958 break 55;
959 }
960 i += 1;
961 }
962 }
963}
964"#,
965 r#"
966fn foo() -> Result<i32, ${0:_}> {
967 let test = "test";
968 if test == "test" {
969 return Ok(24i32);
970 }
971 let mut i = 0;
972 loop {
973 loop {
974 if i == 1 {
975 break Ok(55);
976 }
977 i += 1;
978 }
979 }
980}
981"#,
982 );
983
984 check_assist(
985 wrap_return_type_in_result,
986 r#"
987fn foo() -> i3$02 {
988 let test = "test";
989 let other = 5;
990 if test == "test" {
991 let res = match other {
992 5 => 43,
993 _ => return 56,
994 };
995 }
996 let mut i = 0;
997 loop {
998 loop {
999 if i == 1 {
1000 break 55;
1001 }
1002 i += 1;
1003 }
1004 }
1005}
1006"#,
1007 r#"
1008fn foo() -> Result<i32, ${0:_}> {
1009 let test = "test";
1010 let other = 5;
1011 if test == "test" {
1012 let res = match other {
1013 5 => 43,
1014 _ => return Ok(56),
1015 };
1016 }
1017 let mut i = 0;
1018 loop {
1019 loop {
1020 if i == 1 {
1021 break Ok(55);
1022 }
1023 i += 1;
1024 }
1025 }
1026}
1027"#,
1028 );
1029
1030 check_assist(
1031 wrap_return_type_in_result,
1032 r#"
1033fn foo(the_field: u32) -> u32$0 {
1034 if the_field < 5 {
1035 let mut i = 0;
1036 loop {
1037 if i > 5 {
1038 return 55u32;
1039 }
1040 i += 3;
1041 }
1042 match i {
1043 5 => return 99,
1044 _ => return 0,
1045 };
1046 }
1047 the_field
1048}
1049"#,
1050 r#"
1051fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1052 if the_field < 5 {
1053 let mut i = 0;
1054 loop {
1055 if i > 5 {
1056 return Ok(55u32);
1057 }
1058 i += 3;
1059 }
1060 match i {
1061 5 => return Ok(99),
1062 _ => return Ok(0),
1063 };
1064 }
1065 Ok(the_field)
1066}
1067"#,
1068 );
1069
1070 check_assist(
1071 wrap_return_type_in_result,
1072 r#"
1073fn foo(the_field: u32) -> u3$02 {
1074 if the_field < 5 {
1075 let mut i = 0;
1076 match i {
1077 5 => return 99,
1078 _ => return 0,
1079 }
1080 }
1081 the_field
1082}
1083"#,
1084 r#"
1085fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1086 if the_field < 5 {
1087 let mut i = 0;
1088 match i {
1089 5 => return Ok(99),
1090 _ => return Ok(0),
1091 }
1092 }
1093 Ok(the_field)
1094}
1095"#,
1096 );
1097
1098 check_assist(
1099 wrap_return_type_in_result,
1100 r#"
1101fn foo(the_field: u32) -> u32$0 {
1102 if the_field < 5 {
1103 let mut i = 0;
1104 if i == 5 {
1105 return 99
1106 } else {
1107 return 0
1108 }
1109 }
1110 the_field
1111}
1112"#,
1113 r#"
1114fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1115 if the_field < 5 {
1116 let mut i = 0;
1117 if i == 5 {
1118 return Ok(99)
1119 } else {
1120 return Ok(0)
1121 }
1122 }
1123 Ok(the_field)
1124}
1125"#,
1126 );
1127
1128 check_assist(
1129 wrap_return_type_in_result,
1130 r#"
1131fn foo(the_field: u32) -> $0u32 {
1132 if the_field < 5 {
1133 let mut i = 0;
1134 if i == 5 {
1135 return 99;
1136 } else {
1137 return 0;
1138 }
1139 }
1140 the_field
1141}
1142"#,
1143 r#"
1144fn foo(the_field: u32) -> Result<u32, ${0:_}> {
1145 if the_field < 5 {
1146 let mut i = 0;
1147 if i == 5 {
1148 return Ok(99);
1149 } else {
1150 return Ok(0);
1151 }
1152 }
1153 Ok(the_field)
1154}
1155"#,
1156 );
1157 }
1158}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
deleted file mode 100644
index 14178a651..000000000
--- a/crates/assists/src/lib.rs
+++ /dev/null
@@ -1,230 +0,0 @@
1//! `assists` crate provides a bunch of code assists, also known as code
2//! actions (in LSP) or intentions (in IntelliJ).
3//!
4//! An assist is a micro-refactoring, which is automatically activated in
5//! certain context. For example, if the cursor is over `,`, a "swap `,`" assist
6//! becomes available.
7
8#[allow(unused)]
9macro_rules! eprintln {
10 ($($tt:tt)*) => { stdx::eprintln!($($tt)*) };
11}
12
13mod assist_config;
14mod assist_context;
15#[cfg(test)]
16mod tests;
17pub mod utils;
18pub mod ast_transform;
19
20use hir::Semantics;
21use ide_db::base_db::FileRange;
22use ide_db::{label::Label, source_change::SourceChange, RootDatabase};
23use syntax::TextRange;
24
25pub(crate) use crate::assist_context::{AssistContext, Assists};
26
27pub use assist_config::AssistConfig;
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq)]
30pub enum AssistKind {
31 None,
32 QuickFix,
33 Generate,
34 Refactor,
35 RefactorExtract,
36 RefactorInline,
37 RefactorRewrite,
38}
39
40impl AssistKind {
41 pub fn contains(self, other: AssistKind) -> bool {
42 if self == other {
43 return true;
44 }
45
46 match self {
47 AssistKind::None | AssistKind::Generate => return true,
48 AssistKind::Refactor => match other {
49 AssistKind::RefactorExtract
50 | AssistKind::RefactorInline
51 | AssistKind::RefactorRewrite => return true,
52 _ => return false,
53 },
54 _ => return false,
55 }
56 }
57}
58
59/// Unique identifier of the assist, should not be shown to the user
60/// directly.
61#[derive(Debug, Clone, Copy, PartialEq, Eq)]
62pub struct AssistId(pub &'static str, pub AssistKind);
63
64#[derive(Clone, Debug)]
65pub struct GroupLabel(pub String);
66
67#[derive(Debug, Clone)]
68pub struct Assist {
69 pub id: AssistId,
70 /// Short description of the assist, as shown in the UI.
71 pub label: Label,
72 pub group: Option<GroupLabel>,
73 /// Target ranges are used to sort assists: the smaller the target range,
74 /// the more specific assist is, and so it should be sorted first.
75 pub target: TextRange,
76 /// Computing source change sometimes is much more costly then computing the
77 /// other fields. Additionally, the actual change is not required to show
78 /// the lightbulb UI, it only is needed when the user tries to apply an
79 /// assist. So, we compute it lazily: the API allow requesting assists with
80 /// or without source change. We could (and in fact, used to) distinguish
81 /// between resolved and unresolved assists at the type level, but this is
82 /// cumbersome, especially if you want to embed an assist into another data
83 /// structure, such as a diagnostic.
84 pub source_change: Option<SourceChange>,
85}
86
87impl Assist {
88 /// Return all the assists applicable at the given position.
89 pub fn get(
90 db: &RootDatabase,
91 config: &AssistConfig,
92 resolve: bool,
93 range: FileRange,
94 ) -> Vec<Assist> {
95 let sema = Semantics::new(db);
96 let ctx = AssistContext::new(sema, config, range);
97 let mut acc = Assists::new(&ctx, resolve);
98 handlers::all().iter().for_each(|handler| {
99 handler(&mut acc, &ctx);
100 });
101 acc.finish()
102 }
103}
104
105mod handlers {
106 use crate::{AssistContext, Assists};
107
108 pub(crate) type Handler = fn(&mut Assists, &AssistContext) -> Option<()>;
109
110 mod add_explicit_type;
111 mod add_missing_impl_members;
112 mod add_turbo_fish;
113 mod apply_demorgan;
114 mod auto_import;
115 mod change_visibility;
116 mod convert_integer_literal;
117 mod early_return;
118 mod expand_glob_import;
119 mod extract_struct_from_enum_variant;
120 mod extract_variable;
121 mod fill_match_arms;
122 mod fix_visibility;
123 mod flip_binexpr;
124 mod flip_comma;
125 mod flip_trait_bound;
126 mod generate_default_from_enum_variant;
127 mod generate_derive;
128 mod generate_from_impl_for_enum;
129 mod generate_function;
130 mod generate_impl;
131 mod generate_new;
132 mod infer_function_return_type;
133 mod inline_function;
134 mod inline_local_variable;
135 mod introduce_named_lifetime;
136 mod invert_if;
137 mod merge_imports;
138 mod merge_match_arms;
139 mod move_bounds;
140 mod move_guard;
141 mod move_module_to_file;
142 mod pull_assignment_up;
143 mod qualify_path;
144 mod raw_string;
145 mod remove_dbg;
146 mod remove_mut;
147 mod remove_unused_param;
148 mod reorder_fields;
149 mod reorder_impl;
150 mod replace_derive_with_manual_impl;
151 mod replace_if_let_with_match;
152 mod replace_impl_trait_with_generic;
153 mod replace_let_with_if_let;
154 mod replace_qualified_name_with_use;
155 mod replace_string_with_char;
156 mod replace_unwrap_with_match;
157 mod split_import;
158 mod toggle_ignore;
159 mod unmerge_use;
160 mod unwrap_block;
161 mod wrap_return_type_in_result;
162
163 pub(crate) fn all() -> &'static [Handler] {
164 &[
165 // These are alphabetic for the foolish consistency
166 add_explicit_type::add_explicit_type,
167 add_turbo_fish::add_turbo_fish,
168 apply_demorgan::apply_demorgan,
169 auto_import::auto_import,
170 change_visibility::change_visibility,
171 convert_integer_literal::convert_integer_literal,
172 early_return::convert_to_guarded_return,
173 expand_glob_import::expand_glob_import,
174 move_module_to_file::move_module_to_file,
175 extract_struct_from_enum_variant::extract_struct_from_enum_variant,
176 extract_variable::extract_variable,
177 fill_match_arms::fill_match_arms,
178 fix_visibility::fix_visibility,
179 flip_binexpr::flip_binexpr,
180 flip_comma::flip_comma,
181 flip_trait_bound::flip_trait_bound,
182 generate_default_from_enum_variant::generate_default_from_enum_variant,
183 generate_derive::generate_derive,
184 generate_from_impl_for_enum::generate_from_impl_for_enum,
185 generate_function::generate_function,
186 generate_impl::generate_impl,
187 generate_new::generate_new,
188 infer_function_return_type::infer_function_return_type,
189 inline_function::inline_function,
190 inline_local_variable::inline_local_variable,
191 introduce_named_lifetime::introduce_named_lifetime,
192 invert_if::invert_if,
193 merge_imports::merge_imports,
194 merge_match_arms::merge_match_arms,
195 move_bounds::move_bounds_to_where_clause,
196 move_guard::move_arm_cond_to_match_guard,
197 move_guard::move_guard_to_arm_body,
198 pull_assignment_up::pull_assignment_up,
199 qualify_path::qualify_path,
200 raw_string::add_hash,
201 raw_string::make_usual_string,
202 raw_string::remove_hash,
203 remove_dbg::remove_dbg,
204 remove_mut::remove_mut,
205 remove_unused_param::remove_unused_param,
206 reorder_fields::reorder_fields,
207 reorder_impl::reorder_impl,
208 replace_derive_with_manual_impl::replace_derive_with_manual_impl,
209 replace_if_let_with_match::replace_if_let_with_match,
210 replace_if_let_with_match::replace_match_with_if_let,
211 replace_impl_trait_with_generic::replace_impl_trait_with_generic,
212 replace_let_with_if_let::replace_let_with_if_let,
213 replace_qualified_name_with_use::replace_qualified_name_with_use,
214 replace_unwrap_with_match::replace_unwrap_with_match,
215 split_import::split_import,
216 toggle_ignore::toggle_ignore,
217 unmerge_use::unmerge_use,
218 unwrap_block::unwrap_block,
219 wrap_return_type_in_result::wrap_return_type_in_result,
220 // These are manually sorted for better priorities
221 add_missing_impl_members::add_missing_impl_members,
222 add_missing_impl_members::add_missing_default_members,
223 //
224 replace_string_with_char::replace_string_with_char,
225 raw_string::make_raw_string,
226 // Are you sure you want to add new assist here, and not to the
227 // sorted list above?
228 ]
229 }
230}
diff --git a/crates/assists/src/tests.rs b/crates/assists/src/tests.rs
deleted file mode 100644
index 32bd8698b..000000000
--- a/crates/assists/src/tests.rs
+++ /dev/null
@@ -1,236 +0,0 @@
1mod generated;
2
3use hir::Semantics;
4use ide_db::{
5 base_db::{fixture::WithFixture, FileId, FileRange, SourceDatabaseExt},
6 helpers::{
7 insert_use::{InsertUseConfig, MergeBehavior},
8 SnippetCap,
9 },
10 source_change::FileSystemEdit,
11 RootDatabase,
12};
13use syntax::TextRange;
14use test_utils::{assert_eq_text, extract_offset, extract_range};
15
16use crate::{handlers::Handler, Assist, AssistConfig, AssistContext, AssistKind, Assists};
17use stdx::{format_to, trim_indent};
18
19pub(crate) const TEST_CONFIG: AssistConfig = AssistConfig {
20 snippet_cap: SnippetCap::new(true),
21 allowed: None,
22 insert_use: InsertUseConfig {
23 merge: Some(MergeBehavior::Full),
24 prefix_kind: hir::PrefixKind::Plain,
25 },
26};
27
28pub(crate) fn with_single_file(text: &str) -> (RootDatabase, FileId) {
29 RootDatabase::with_single_file(text)
30}
31
32pub(crate) fn check_assist(assist: Handler, ra_fixture_before: &str, ra_fixture_after: &str) {
33 let ra_fixture_after = trim_indent(ra_fixture_after);
34 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), None);
35}
36
37// There is no way to choose what assist within a group you want to test against,
38// so this is here to allow you choose.
39pub(crate) fn check_assist_by_label(
40 assist: Handler,
41 ra_fixture_before: &str,
42 ra_fixture_after: &str,
43 label: &str,
44) {
45 let ra_fixture_after = trim_indent(ra_fixture_after);
46 check(assist, ra_fixture_before, ExpectedResult::After(&ra_fixture_after), Some(label));
47}
48
49// FIXME: instead of having a separate function here, maybe use
50// `extract_ranges` and mark the target as `<target> </target>` in the
51// fixture?
52pub(crate) fn check_assist_target(assist: Handler, ra_fixture: &str, target: &str) {
53 check(assist, ra_fixture, ExpectedResult::Target(target), None);
54}
55
56pub(crate) fn check_assist_not_applicable(assist: Handler, ra_fixture: &str) {
57 check(assist, ra_fixture, ExpectedResult::NotApplicable, None);
58}
59
60fn check_doc_test(assist_id: &str, before: &str, after: &str) {
61 let after = trim_indent(after);
62 let (db, file_id, selection) = RootDatabase::with_range_or_offset(&before);
63 let before = db.file_text(file_id).to_string();
64 let frange = FileRange { file_id, range: selection.into() };
65
66 let assist = Assist::get(&db, &TEST_CONFIG, true, frange)
67 .into_iter()
68 .find(|assist| assist.id.0 == assist_id)
69 .unwrap_or_else(|| {
70 panic!(
71 "\n\nAssist is not applicable: {}\nAvailable assists: {}",
72 assist_id,
73 Assist::get(&db, &TEST_CONFIG, false, frange)
74 .into_iter()
75 .map(|assist| assist.id.0)
76 .collect::<Vec<_>>()
77 .join(", ")
78 )
79 });
80
81 let actual = {
82 let source_change = assist.source_change.unwrap();
83 let mut actual = before;
84 if let Some(source_file_edit) = source_change.get_source_edit(file_id) {
85 source_file_edit.apply(&mut actual);
86 }
87 actual
88 };
89 assert_eq_text!(&after, &actual);
90}
91
92enum ExpectedResult<'a> {
93 NotApplicable,
94 After(&'a str),
95 Target(&'a str),
96}
97
98fn check(handler: Handler, before: &str, expected: ExpectedResult, assist_label: Option<&str>) {
99 let (db, file_with_caret_id, range_or_offset) = RootDatabase::with_range_or_offset(before);
100 let text_without_caret = db.file_text(file_with_caret_id).to_string();
101
102 let frange = FileRange { file_id: file_with_caret_id, range: range_or_offset.into() };
103
104 let sema = Semantics::new(&db);
105 let config = TEST_CONFIG;
106 let ctx = AssistContext::new(sema, &config, frange);
107 let mut acc = Assists::new(&ctx, true);
108 handler(&mut acc, &ctx);
109 let mut res = acc.finish();
110
111 let assist = match assist_label {
112 Some(label) => res.into_iter().find(|resolved| resolved.label == label),
113 None => res.pop(),
114 };
115
116 match (assist, expected) {
117 (Some(assist), ExpectedResult::After(after)) => {
118 let source_change = assist.source_change.unwrap();
119 assert!(!source_change.source_file_edits.is_empty());
120 let skip_header = source_change.source_file_edits.len() == 1
121 && source_change.file_system_edits.len() == 0;
122
123 let mut buf = String::new();
124 for (file_id, edit) in source_change.source_file_edits {
125 let mut text = db.file_text(file_id).as_ref().to_owned();
126 edit.apply(&mut text);
127 if !skip_header {
128 let sr = db.file_source_root(file_id);
129 let sr = db.source_root(sr);
130 let path = sr.path_for_file(&file_id).unwrap();
131 format_to!(buf, "//- {}\n", path)
132 }
133 buf.push_str(&text);
134 }
135
136 for file_system_edit in source_change.file_system_edits {
137 if let FileSystemEdit::CreateFile { dst, initial_contents } = file_system_edit {
138 let sr = db.file_source_root(dst.anchor);
139 let sr = db.source_root(sr);
140 let mut base = sr.path_for_file(&dst.anchor).unwrap().clone();
141 base.pop();
142 let created_file_path = format!("{}{}", base.to_string(), &dst.path[1..]);
143 format_to!(buf, "//- {}\n", created_file_path);
144 buf.push_str(&initial_contents);
145 }
146 }
147
148 assert_eq_text!(after, &buf);
149 }
150 (Some(assist), ExpectedResult::Target(target)) => {
151 let range = assist.target;
152 assert_eq_text!(&text_without_caret[range], target);
153 }
154 (Some(_), ExpectedResult::NotApplicable) => panic!("assist should not be applicable!"),
155 (None, ExpectedResult::After(_)) | (None, ExpectedResult::Target(_)) => {
156 panic!("code action is not applicable")
157 }
158 (None, ExpectedResult::NotApplicable) => (),
159 };
160}
161
162#[test]
163fn assist_order_field_struct() {
164 let before = "struct Foo { $0bar: u32 }";
165 let (before_cursor_pos, before) = extract_offset(before);
166 let (db, file_id) = with_single_file(&before);
167 let frange = FileRange { file_id, range: TextRange::empty(before_cursor_pos) };
168 let assists = Assist::get(&db, &TEST_CONFIG, false, frange);
169 let mut assists = assists.iter();
170
171 assert_eq!(assists.next().expect("expected assist").label, "Change visibility to pub(crate)");
172 assert_eq!(assists.next().expect("expected assist").label, "Add `#[derive]`");
173}
174
175#[test]
176fn assist_order_if_expr() {
177 let before = "
178 pub fn test_some_range(a: int) -> bool {
179 if let 2..6 = $05$0 {
180 true
181 } else {
182 false
183 }
184 }";
185 let (range, before) = extract_range(before);
186 let (db, file_id) = with_single_file(&before);
187 let frange = FileRange { file_id, range };
188 let assists = Assist::get(&db, &TEST_CONFIG, false, frange);
189 let mut assists = assists.iter();
190
191 assert_eq!(assists.next().expect("expected assist").label, "Extract into variable");
192 assert_eq!(assists.next().expect("expected assist").label, "Replace with match");
193}
194
195#[test]
196fn assist_filter_works() {
197 let before = "
198 pub fn test_some_range(a: int) -> bool {
199 if let 2..6 = $05$0 {
200 true
201 } else {
202 false
203 }
204 }";
205 let (range, before) = extract_range(before);
206 let (db, file_id) = with_single_file(&before);
207 let frange = FileRange { file_id, range };
208
209 {
210 let mut cfg = TEST_CONFIG;
211 cfg.allowed = Some(vec![AssistKind::Refactor]);
212
213 let assists = Assist::get(&db, &cfg, false, frange);
214 let mut assists = assists.iter();
215
216 assert_eq!(assists.next().expect("expected assist").label, "Extract into variable");
217 assert_eq!(assists.next().expect("expected assist").label, "Replace with match");
218 }
219
220 {
221 let mut cfg = TEST_CONFIG;
222 cfg.allowed = Some(vec![AssistKind::RefactorExtract]);
223 let assists = Assist::get(&db, &cfg, false, frange);
224 assert_eq!(assists.len(), 1);
225
226 let mut assists = assists.iter();
227 assert_eq!(assists.next().expect("expected assist").label, "Extract into variable");
228 }
229
230 {
231 let mut cfg = TEST_CONFIG;
232 cfg.allowed = Some(vec![AssistKind::QuickFix]);
233 let assists = Assist::get(&db, &cfg, false, frange);
234 assert!(assists.is_empty(), "All asserts but quickfixes should be filtered out");
235 }
236}
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
deleted file mode 100644
index d48d063b4..000000000
--- a/crates/assists/src/tests/generated.rs
+++ /dev/null
@@ -1,1184 +0,0 @@
1//! Generated file, do not edit by hand, see `xtask/src/codegen`
2
3use super::check_doc_test;
4
5#[test]
6fn doctest_add_explicit_type() {
7 check_doc_test(
8 "add_explicit_type",
9 r#####"
10fn main() {
11 let x$0 = 92;
12}
13"#####,
14 r#####"
15fn main() {
16 let x: i32 = 92;
17}
18"#####,
19 )
20}
21
22#[test]
23fn doctest_add_hash() {
24 check_doc_test(
25 "add_hash",
26 r#####"
27fn main() {
28 r#"Hello,$0 World!"#;
29}
30"#####,
31 r#####"
32fn main() {
33 r##"Hello, World!"##;
34}
35"#####,
36 )
37}
38
39#[test]
40fn doctest_add_impl_default_members() {
41 check_doc_test(
42 "add_impl_default_members",
43 r#####"
44trait Trait {
45 type X;
46 fn foo(&self);
47 fn bar(&self) {}
48}
49
50impl Trait for () {
51 type X = ();
52 fn foo(&self) {}$0
53
54}
55"#####,
56 r#####"
57trait Trait {
58 type X;
59 fn foo(&self);
60 fn bar(&self) {}
61}
62
63impl Trait for () {
64 type X = ();
65 fn foo(&self) {}
66
67 $0fn bar(&self) {}
68}
69"#####,
70 )
71}
72
73#[test]
74fn doctest_add_impl_missing_members() {
75 check_doc_test(
76 "add_impl_missing_members",
77 r#####"
78trait Trait<T> {
79 type X;
80 fn foo(&self) -> T;
81 fn bar(&self) {}
82}
83
84impl Trait<u32> for () {$0
85
86}
87"#####,
88 r#####"
89trait Trait<T> {
90 type X;
91 fn foo(&self) -> T;
92 fn bar(&self) {}
93}
94
95impl Trait<u32> for () {
96 $0type X;
97
98 fn foo(&self) -> u32 {
99 todo!()
100 }
101}
102"#####,
103 )
104}
105
106#[test]
107fn doctest_add_turbo_fish() {
108 check_doc_test(
109 "add_turbo_fish",
110 r#####"
111fn make<T>() -> T { todo!() }
112fn main() {
113 let x = make$0();
114}
115"#####,
116 r#####"
117fn make<T>() -> T { todo!() }
118fn main() {
119 let x = make::<${0:_}>();
120}
121"#####,
122 )
123}
124
125#[test]
126fn doctest_apply_demorgan() {
127 check_doc_test(
128 "apply_demorgan",
129 r#####"
130fn main() {
131 if x != 4 ||$0 !y {}
132}
133"#####,
134 r#####"
135fn main() {
136 if !(x == 4 && y) {}
137}
138"#####,
139 )
140}
141
142#[test]
143fn doctest_auto_import() {
144 check_doc_test(
145 "auto_import",
146 r#####"
147fn main() {
148 let map = HashMap$0::new();
149}
150pub mod std { pub mod collections { pub struct HashMap { } } }
151"#####,
152 r#####"
153use std::collections::HashMap;
154
155fn main() {
156 let map = HashMap::new();
157}
158pub mod std { pub mod collections { pub struct HashMap { } } }
159"#####,
160 )
161}
162
163#[test]
164fn doctest_change_visibility() {
165 check_doc_test(
166 "change_visibility",
167 r#####"
168$0fn frobnicate() {}
169"#####,
170 r#####"
171pub(crate) fn frobnicate() {}
172"#####,
173 )
174}
175
176#[test]
177fn doctest_convert_integer_literal() {
178 check_doc_test(
179 "convert_integer_literal",
180 r#####"
181const _: i32 = 10$0;
182"#####,
183 r#####"
184const _: i32 = 0b1010;
185"#####,
186 )
187}
188
189#[test]
190fn doctest_convert_to_guarded_return() {
191 check_doc_test(
192 "convert_to_guarded_return",
193 r#####"
194fn main() {
195 $0if cond {
196 foo();
197 bar();
198 }
199}
200"#####,
201 r#####"
202fn main() {
203 if !cond {
204 return;
205 }
206 foo();
207 bar();
208}
209"#####,
210 )
211}
212
213#[test]
214fn doctest_expand_glob_import() {
215 check_doc_test(
216 "expand_glob_import",
217 r#####"
218mod foo {
219 pub struct Bar;
220 pub struct Baz;
221}
222
223use foo::*$0;
224
225fn qux(bar: Bar, baz: Baz) {}
226"#####,
227 r#####"
228mod foo {
229 pub struct Bar;
230 pub struct Baz;
231}
232
233use foo::{Baz, Bar};
234
235fn qux(bar: Bar, baz: Baz) {}
236"#####,
237 )
238}
239
240#[test]
241fn doctest_extract_struct_from_enum_variant() {
242 check_doc_test(
243 "extract_struct_from_enum_variant",
244 r#####"
245enum A { $0One(u32, u32) }
246"#####,
247 r#####"
248struct One(pub u32, pub u32);
249
250enum A { One(One) }
251"#####,
252 )
253}
254
255#[test]
256fn doctest_extract_variable() {
257 check_doc_test(
258 "extract_variable",
259 r#####"
260fn main() {
261 $0(1 + 2)$0 * 4;
262}
263"#####,
264 r#####"
265fn main() {
266 let $0var_name = (1 + 2);
267 var_name * 4;
268}
269"#####,
270 )
271}
272
273#[test]
274fn doctest_fill_match_arms() {
275 check_doc_test(
276 "fill_match_arms",
277 r#####"
278enum Action { Move { distance: u32 }, Stop }
279
280fn handle(action: Action) {
281 match action {
282 $0
283 }
284}
285"#####,
286 r#####"
287enum Action { Move { distance: u32 }, Stop }
288
289fn handle(action: Action) {
290 match action {
291 $0Action::Move { distance } => {}
292 Action::Stop => {}
293 }
294}
295"#####,
296 )
297}
298
299#[test]
300fn doctest_fix_visibility() {
301 check_doc_test(
302 "fix_visibility",
303 r#####"
304mod m {
305 fn frobnicate() {}
306}
307fn main() {
308 m::frobnicate$0() {}
309}
310"#####,
311 r#####"
312mod m {
313 $0pub(crate) fn frobnicate() {}
314}
315fn main() {
316 m::frobnicate() {}
317}
318"#####,
319 )
320}
321
322#[test]
323fn doctest_flip_binexpr() {
324 check_doc_test(
325 "flip_binexpr",
326 r#####"
327fn main() {
328 let _ = 90 +$0 2;
329}
330"#####,
331 r#####"
332fn main() {
333 let _ = 2 + 90;
334}
335"#####,
336 )
337}
338
339#[test]
340fn doctest_flip_comma() {
341 check_doc_test(
342 "flip_comma",
343 r#####"
344fn main() {
345 ((1, 2),$0 (3, 4));
346}
347"#####,
348 r#####"
349fn main() {
350 ((3, 4), (1, 2));
351}
352"#####,
353 )
354}
355
356#[test]
357fn doctest_flip_trait_bound() {
358 check_doc_test(
359 "flip_trait_bound",
360 r#####"
361fn foo<T: Clone +$0 Copy>() { }
362"#####,
363 r#####"
364fn foo<T: Copy + Clone>() { }
365"#####,
366 )
367}
368
369#[test]
370fn doctest_generate_default_from_enum_variant() {
371 check_doc_test(
372 "generate_default_from_enum_variant",
373 r#####"
374enum Version {
375 Undefined,
376 Minor$0,
377 Major,
378}
379"#####,
380 r#####"
381enum Version {
382 Undefined,
383 Minor,
384 Major,
385}
386
387impl Default for Version {
388 fn default() -> Self {
389 Self::Minor
390 }
391}
392"#####,
393 )
394}
395
396#[test]
397fn doctest_generate_derive() {
398 check_doc_test(
399 "generate_derive",
400 r#####"
401struct Point {
402 x: u32,
403 y: u32,$0
404}
405"#####,
406 r#####"
407#[derive($0)]
408struct Point {
409 x: u32,
410 y: u32,
411}
412"#####,
413 )
414}
415
416#[test]
417fn doctest_generate_from_impl_for_enum() {
418 check_doc_test(
419 "generate_from_impl_for_enum",
420 r#####"
421enum A { $0One(u32) }
422"#####,
423 r#####"
424enum A { One(u32) }
425
426impl From<u32> for A {
427 fn from(v: u32) -> Self {
428 A::One(v)
429 }
430}
431"#####,
432 )
433}
434
435#[test]
436fn doctest_generate_function() {
437 check_doc_test(
438 "generate_function",
439 r#####"
440struct Baz;
441fn baz() -> Baz { Baz }
442fn foo() {
443 bar$0("", baz());
444}
445
446"#####,
447 r#####"
448struct Baz;
449fn baz() -> Baz { Baz }
450fn foo() {
451 bar("", baz());
452}
453
454fn bar(arg: &str, baz: Baz) ${0:-> ()} {
455 todo!()
456}
457
458"#####,
459 )
460}
461
462#[test]
463fn doctest_generate_impl() {
464 check_doc_test(
465 "generate_impl",
466 r#####"
467struct Ctx<T: Clone> {
468 data: T,$0
469}
470"#####,
471 r#####"
472struct Ctx<T: Clone> {
473 data: T,
474}
475
476impl<T: Clone> Ctx<T> {
477 $0
478}
479"#####,
480 )
481}
482
483#[test]
484fn doctest_generate_new() {
485 check_doc_test(
486 "generate_new",
487 r#####"
488struct Ctx<T: Clone> {
489 data: T,$0
490}
491"#####,
492 r#####"
493struct Ctx<T: Clone> {
494 data: T,
495}
496
497impl<T: Clone> Ctx<T> {
498 fn $0new(data: T) -> Self { Self { data } }
499}
500
501"#####,
502 )
503}
504
505#[test]
506fn doctest_infer_function_return_type() {
507 check_doc_test(
508 "infer_function_return_type",
509 r#####"
510fn foo() { 4$02i32 }
511"#####,
512 r#####"
513fn foo() -> i32 { 42i32 }
514"#####,
515 )
516}
517
518#[test]
519fn doctest_inline_function() {
520 check_doc_test(
521 "inline_function",
522 r#####"
523fn add(a: u32, b: u32) -> u32 { a + b }
524fn main() {
525 let x = add$0(1, 2);
526}
527"#####,
528 r#####"
529fn add(a: u32, b: u32) -> u32 { a + b }
530fn main() {
531 let x = {
532 let a = 1;
533 let b = 2;
534 a + b
535 };
536}
537"#####,
538 )
539}
540
541#[test]
542fn doctest_inline_local_variable() {
543 check_doc_test(
544 "inline_local_variable",
545 r#####"
546fn main() {
547 let x$0 = 1 + 2;
548 x * 4;
549}
550"#####,
551 r#####"
552fn main() {
553 (1 + 2) * 4;
554}
555"#####,
556 )
557}
558
559#[test]
560fn doctest_introduce_named_lifetime() {
561 check_doc_test(
562 "introduce_named_lifetime",
563 r#####"
564impl Cursor<'_$0> {
565 fn node(self) -> &SyntaxNode {
566 match self {
567 Cursor::Replace(node) | Cursor::Before(node) => node,
568 }
569 }
570}
571"#####,
572 r#####"
573impl<'a> Cursor<'a> {
574 fn node(self) -> &SyntaxNode {
575 match self {
576 Cursor::Replace(node) | Cursor::Before(node) => node,
577 }
578 }
579}
580"#####,
581 )
582}
583
584#[test]
585fn doctest_invert_if() {
586 check_doc_test(
587 "invert_if",
588 r#####"
589fn main() {
590 if$0 !y { A } else { B }
591}
592"#####,
593 r#####"
594fn main() {
595 if y { B } else { A }
596}
597"#####,
598 )
599}
600
601#[test]
602fn doctest_make_raw_string() {
603 check_doc_test(
604 "make_raw_string",
605 r#####"
606fn main() {
607 "Hello,$0 World!";
608}
609"#####,
610 r#####"
611fn main() {
612 r#"Hello, World!"#;
613}
614"#####,
615 )
616}
617
618#[test]
619fn doctest_make_usual_string() {
620 check_doc_test(
621 "make_usual_string",
622 r#####"
623fn main() {
624 r#"Hello,$0 "World!""#;
625}
626"#####,
627 r#####"
628fn main() {
629 "Hello, \"World!\"";
630}
631"#####,
632 )
633}
634
635#[test]
636fn doctest_merge_imports() {
637 check_doc_test(
638 "merge_imports",
639 r#####"
640use std::$0fmt::Formatter;
641use std::io;
642"#####,
643 r#####"
644use std::{fmt::Formatter, io};
645"#####,
646 )
647}
648
649#[test]
650fn doctest_merge_match_arms() {
651 check_doc_test(
652 "merge_match_arms",
653 r#####"
654enum Action { Move { distance: u32 }, Stop }
655
656fn handle(action: Action) {
657 match action {
658 $0Action::Move(..) => foo(),
659 Action::Stop => foo(),
660 }
661}
662"#####,
663 r#####"
664enum Action { Move { distance: u32 }, Stop }
665
666fn handle(action: Action) {
667 match action {
668 Action::Move(..) | Action::Stop => foo(),
669 }
670}
671"#####,
672 )
673}
674
675#[test]
676fn doctest_move_arm_cond_to_match_guard() {
677 check_doc_test(
678 "move_arm_cond_to_match_guard",
679 r#####"
680enum Action { Move { distance: u32 }, Stop }
681
682fn handle(action: Action) {
683 match action {
684 Action::Move { distance } => $0if distance > 10 { foo() },
685 _ => (),
686 }
687}
688"#####,
689 r#####"
690enum Action { Move { distance: u32 }, Stop }
691
692fn handle(action: Action) {
693 match action {
694 Action::Move { distance } if distance > 10 => foo(),
695 _ => (),
696 }
697}
698"#####,
699 )
700}
701
702#[test]
703fn doctest_move_bounds_to_where_clause() {
704 check_doc_test(
705 "move_bounds_to_where_clause",
706 r#####"
707fn apply<T, U, $0F: FnOnce(T) -> U>(f: F, x: T) -> U {
708 f(x)
709}
710"#####,
711 r#####"
712fn apply<T, U, F>(f: F, x: T) -> U where F: FnOnce(T) -> U {
713 f(x)
714}
715"#####,
716 )
717}
718
719#[test]
720fn doctest_move_guard_to_arm_body() {
721 check_doc_test(
722 "move_guard_to_arm_body",
723 r#####"
724enum Action { Move { distance: u32 }, Stop }
725
726fn handle(action: Action) {
727 match action {
728 Action::Move { distance } $0if distance > 10 => foo(),
729 _ => (),
730 }
731}
732"#####,
733 r#####"
734enum Action { Move { distance: u32 }, Stop }
735
736fn handle(action: Action) {
737 match action {
738 Action::Move { distance } => if distance > 10 {
739 foo()
740 },
741 _ => (),
742 }
743}
744"#####,
745 )
746}
747
748#[test]
749fn doctest_move_module_to_file() {
750 check_doc_test(
751 "move_module_to_file",
752 r#####"
753mod $0foo {
754 fn t() {}
755}
756"#####,
757 r#####"
758mod foo;
759"#####,
760 )
761}
762
763#[test]
764fn doctest_pull_assignment_up() {
765 check_doc_test(
766 "pull_assignment_up",
767 r#####"
768fn main() {
769 let mut foo = 6;
770
771 if true {
772 $0foo = 5;
773 } else {
774 foo = 4;
775 }
776}
777"#####,
778 r#####"
779fn main() {
780 let mut foo = 6;
781
782 foo = if true {
783 5
784 } else {
785 4
786 };
787}
788"#####,
789 )
790}
791
792#[test]
793fn doctest_qualify_path() {
794 check_doc_test(
795 "qualify_path",
796 r#####"
797fn main() {
798 let map = HashMap$0::new();
799}
800pub mod std { pub mod collections { pub struct HashMap { } } }
801"#####,
802 r#####"
803fn main() {
804 let map = std::collections::HashMap::new();
805}
806pub mod std { pub mod collections { pub struct HashMap { } } }
807"#####,
808 )
809}
810
811#[test]
812fn doctest_remove_dbg() {
813 check_doc_test(
814 "remove_dbg",
815 r#####"
816fn main() {
817 $0dbg!(92);
818}
819"#####,
820 r#####"
821fn main() {
822 92;
823}
824"#####,
825 )
826}
827
828#[test]
829fn doctest_remove_hash() {
830 check_doc_test(
831 "remove_hash",
832 r#####"
833fn main() {
834 r#"Hello,$0 World!"#;
835}
836"#####,
837 r#####"
838fn main() {
839 r"Hello, World!";
840}
841"#####,
842 )
843}
844
845#[test]
846fn doctest_remove_mut() {
847 check_doc_test(
848 "remove_mut",
849 r#####"
850impl Walrus {
851 fn feed(&mut$0 self, amount: u32) {}
852}
853"#####,
854 r#####"
855impl Walrus {
856 fn feed(&self, amount: u32) {}
857}
858"#####,
859 )
860}
861
862#[test]
863fn doctest_remove_unused_param() {
864 check_doc_test(
865 "remove_unused_param",
866 r#####"
867fn frobnicate(x: i32$0) {}
868
869fn main() {
870 frobnicate(92);
871}
872"#####,
873 r#####"
874fn frobnicate() {}
875
876fn main() {
877 frobnicate();
878}
879"#####,
880 )
881}
882
883#[test]
884fn doctest_reorder_fields() {
885 check_doc_test(
886 "reorder_fields",
887 r#####"
888struct Foo {foo: i32, bar: i32};
889const test: Foo = $0Foo {bar: 0, foo: 1}
890"#####,
891 r#####"
892struct Foo {foo: i32, bar: i32};
893const test: Foo = Foo {foo: 1, bar: 0}
894"#####,
895 )
896}
897
898#[test]
899fn doctest_reorder_impl() {
900 check_doc_test(
901 "reorder_impl",
902 r#####"
903trait Foo {
904 fn a() {}
905 fn b() {}
906 fn c() {}
907}
908
909struct Bar;
910$0impl Foo for Bar {
911 fn b() {}
912 fn c() {}
913 fn a() {}
914}
915"#####,
916 r#####"
917trait Foo {
918 fn a() {}
919 fn b() {}
920 fn c() {}
921}
922
923struct Bar;
924impl Foo for Bar {
925 fn a() {}
926 fn b() {}
927 fn c() {}
928}
929"#####,
930 )
931}
932
933#[test]
934fn doctest_replace_derive_with_manual_impl() {
935 check_doc_test(
936 "replace_derive_with_manual_impl",
937 r#####"
938trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
939#[derive(Deb$0ug, Display)]
940struct S;
941"#####,
942 r#####"
943trait Debug { fn fmt(&self, f: &mut Formatter) -> Result<()>; }
944#[derive(Display)]
945struct S;
946
947impl Debug for S {
948 fn fmt(&self, f: &mut Formatter) -> Result<()> {
949 ${0:todo!()}
950 }
951}
952"#####,
953 )
954}
955
956#[test]
957fn doctest_replace_if_let_with_match() {
958 check_doc_test(
959 "replace_if_let_with_match",
960 r#####"
961enum Action { Move { distance: u32 }, Stop }
962
963fn handle(action: Action) {
964 $0if let Action::Move { distance } = action {
965 foo(distance)
966 } else {
967 bar()
968 }
969}
970"#####,
971 r#####"
972enum Action { Move { distance: u32 }, Stop }
973
974fn handle(action: Action) {
975 match action {
976 Action::Move { distance } => foo(distance),
977 _ => bar(),
978 }
979}
980"#####,
981 )
982}
983
984#[test]
985fn doctest_replace_impl_trait_with_generic() {
986 check_doc_test(
987 "replace_impl_trait_with_generic",
988 r#####"
989fn foo(bar: $0impl Bar) {}
990"#####,
991 r#####"
992fn foo<B: Bar>(bar: B) {}
993"#####,
994 )
995}
996
997#[test]
998fn doctest_replace_let_with_if_let() {
999 check_doc_test(
1000 "replace_let_with_if_let",
1001 r#####"
1002enum Option<T> { Some(T), None }
1003
1004fn main(action: Action) {
1005 $0let x = compute();
1006}
1007
1008fn compute() -> Option<i32> { None }
1009"#####,
1010 r#####"
1011enum Option<T> { Some(T), None }
1012
1013fn main(action: Action) {
1014 if let Some(x) = compute() {
1015 }
1016}
1017
1018fn compute() -> Option<i32> { None }
1019"#####,
1020 )
1021}
1022
1023#[test]
1024fn doctest_replace_match_with_if_let() {
1025 check_doc_test(
1026 "replace_match_with_if_let",
1027 r#####"
1028enum Action { Move { distance: u32 }, Stop }
1029
1030fn handle(action: Action) {
1031 $0match action {
1032 Action::Move { distance } => foo(distance),
1033 _ => bar(),
1034 }
1035}
1036"#####,
1037 r#####"
1038enum Action { Move { distance: u32 }, Stop }
1039
1040fn handle(action: Action) {
1041 if let Action::Move { distance } = action {
1042 foo(distance)
1043 } else {
1044 bar()
1045 }
1046}
1047"#####,
1048 )
1049}
1050
1051#[test]
1052fn doctest_replace_qualified_name_with_use() {
1053 check_doc_test(
1054 "replace_qualified_name_with_use",
1055 r#####"
1056fn process(map: std::collections::$0HashMap<String, String>) {}
1057"#####,
1058 r#####"
1059use std::collections::HashMap;
1060
1061fn process(map: HashMap<String, String>) {}
1062"#####,
1063 )
1064}
1065
1066#[test]
1067fn doctest_replace_string_with_char() {
1068 check_doc_test(
1069 "replace_string_with_char",
1070 r#####"
1071fn main() {
1072 find("{$0");
1073}
1074"#####,
1075 r#####"
1076fn main() {
1077 find('{');
1078}
1079"#####,
1080 )
1081}
1082
1083#[test]
1084fn doctest_replace_unwrap_with_match() {
1085 check_doc_test(
1086 "replace_unwrap_with_match",
1087 r#####"
1088enum Result<T, E> { Ok(T), Err(E) }
1089fn main() {
1090 let x: Result<i32, i32> = Result::Ok(92);
1091 let y = x.$0unwrap();
1092}
1093"#####,
1094 r#####"
1095enum Result<T, E> { Ok(T), Err(E) }
1096fn main() {
1097 let x: Result<i32, i32> = Result::Ok(92);
1098 let y = match x {
1099 Ok(a) => a,
1100 $0_ => unreachable!(),
1101 };
1102}
1103"#####,
1104 )
1105}
1106
1107#[test]
1108fn doctest_split_import() {
1109 check_doc_test(
1110 "split_import",
1111 r#####"
1112use std::$0collections::HashMap;
1113"#####,
1114 r#####"
1115use std::{collections::HashMap};
1116"#####,
1117 )
1118}
1119
1120#[test]
1121fn doctest_toggle_ignore() {
1122 check_doc_test(
1123 "toggle_ignore",
1124 r#####"
1125$0#[test]
1126fn arithmetics {
1127 assert_eq!(2 + 2, 5);
1128}
1129"#####,
1130 r#####"
1131#[test]
1132#[ignore]
1133fn arithmetics {
1134 assert_eq!(2 + 2, 5);
1135}
1136"#####,
1137 )
1138}
1139
1140#[test]
1141fn doctest_unmerge_use() {
1142 check_doc_test(
1143 "unmerge_use",
1144 r#####"
1145use std::fmt::{Debug, Display$0};
1146"#####,
1147 r#####"
1148use std::fmt::{Debug};
1149use std::fmt::Display;
1150"#####,
1151 )
1152}
1153
1154#[test]
1155fn doctest_unwrap_block() {
1156 check_doc_test(
1157 "unwrap_block",
1158 r#####"
1159fn foo() {
1160 if true {$0
1161 println!("foo");
1162 }
1163}
1164"#####,
1165 r#####"
1166fn foo() {
1167 println!("foo");
1168}
1169"#####,
1170 )
1171}
1172
1173#[test]
1174fn doctest_wrap_return_type_in_result() {
1175 check_doc_test(
1176 "wrap_return_type_in_result",
1177 r#####"
1178fn foo() -> i32$0 { 42i32 }
1179"#####,
1180 r#####"
1181fn foo() -> Result<i32, ${0:_}> { Ok(42i32) }
1182"#####,
1183 )
1184}
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
deleted file mode 100644
index fc9f83bab..000000000
--- a/crates/assists/src/utils.rs
+++ /dev/null
@@ -1,250 +0,0 @@
1//! Assorted functions shared by several assists.
2
3use std::ops;
4
5use hir::HasSource;
6use ide_db::{helpers::SnippetCap, RootDatabase};
7use itertools::Itertools;
8use syntax::{
9 ast::edit::AstNodeEdit,
10 ast::AttrsOwner,
11 ast::NameOwner,
12 ast::{self, edit, make, ArgListOwner},
13 AstNode, Direction,
14 SyntaxKind::*,
15 SyntaxNode, TextSize, T,
16};
17
18use crate::ast_transform::{self, AstTransform, QualifyPaths, SubstituteTypeParams};
19
20pub(crate) fn unwrap_trivial_block(block: ast::BlockExpr) -> ast::Expr {
21 extract_trivial_expression(&block)
22 .filter(|expr| !expr.syntax().text().contains_char('\n'))
23 .unwrap_or_else(|| block.into())
24}
25
26pub fn extract_trivial_expression(block: &ast::BlockExpr) -> Option<ast::Expr> {
27 let has_anything_else = |thing: &SyntaxNode| -> bool {
28 let mut non_trivial_children =
29 block.syntax().children_with_tokens().filter(|it| match it.kind() {
30 WHITESPACE | T!['{'] | T!['}'] => false,
31 _ => it.as_node() != Some(thing),
32 });
33 non_trivial_children.next().is_some()
34 };
35
36 if let Some(expr) = block.tail_expr() {
37 if has_anything_else(expr.syntax()) {
38 return None;
39 }
40 return Some(expr);
41 }
42 // Unwrap `{ continue; }`
43 let (stmt,) = block.statements().next_tuple()?;
44 if let ast::Stmt::ExprStmt(expr_stmt) = stmt {
45 if has_anything_else(expr_stmt.syntax()) {
46 return None;
47 }
48 let expr = expr_stmt.expr()?;
49 match expr.syntax().kind() {
50 CONTINUE_EXPR | BREAK_EXPR | RETURN_EXPR => return Some(expr),
51 _ => (),
52 }
53 }
54 None
55}
56
57/// This is a method with a heuristics to support test methods annotated with custom test annotations, such as
58/// `#[test_case(...)]`, `#[tokio::test]` and similar.
59/// Also a regular `#[test]` annotation is supported.
60///
61/// It may produce false positives, for example, `#[wasm_bindgen_test]` requires a different command to run the test,
62/// but it's better than not to have the runnables for the tests at all.
63pub fn test_related_attribute(fn_def: &ast::Fn) -> Option<ast::Attr> {
64 fn_def.attrs().find_map(|attr| {
65 let path = attr.path()?;
66 if path.syntax().text().to_string().contains("test") {
67 Some(attr)
68 } else {
69 None
70 }
71 })
72}
73
74#[derive(Copy, Clone, PartialEq)]
75pub enum DefaultMethods {
76 Only,
77 No,
78}
79
80pub fn filter_assoc_items(
81 db: &RootDatabase,
82 items: &[hir::AssocItem],
83 default_methods: DefaultMethods,
84) -> Vec<ast::AssocItem> {
85 fn has_def_name(item: &ast::AssocItem) -> bool {
86 match item {
87 ast::AssocItem::Fn(def) => def.name(),
88 ast::AssocItem::TypeAlias(def) => def.name(),
89 ast::AssocItem::Const(def) => def.name(),
90 ast::AssocItem::MacroCall(_) => None,
91 }
92 .is_some()
93 }
94
95 items
96 .iter()
97 // Note: This throws away items with no source.
98 .filter_map(|i| {
99 let item = match i {
100 hir::AssocItem::Function(i) => ast::AssocItem::Fn(i.source(db)?.value),
101 hir::AssocItem::TypeAlias(i) => ast::AssocItem::TypeAlias(i.source(db)?.value),
102 hir::AssocItem::Const(i) => ast::AssocItem::Const(i.source(db)?.value),
103 };
104 Some(item)
105 })
106 .filter(has_def_name)
107 .filter(|it| match it {
108 ast::AssocItem::Fn(def) => matches!(
109 (default_methods, def.body()),
110 (DefaultMethods::Only, Some(_)) | (DefaultMethods::No, None)
111 ),
112 _ => default_methods == DefaultMethods::No,
113 })
114 .collect::<Vec<_>>()
115}
116
117pub fn add_trait_assoc_items_to_impl(
118 sema: &hir::Semantics<ide_db::RootDatabase>,
119 items: Vec<ast::AssocItem>,
120 trait_: hir::Trait,
121 impl_def: ast::Impl,
122 target_scope: hir::SemanticsScope,
123) -> (ast::Impl, ast::AssocItem) {
124 let impl_item_list = impl_def.assoc_item_list().unwrap_or_else(make::assoc_item_list);
125
126 let n_existing_items = impl_item_list.assoc_items().count();
127 let source_scope = sema.scope_for_def(trait_);
128 let ast_transform = QualifyPaths::new(&target_scope, &source_scope)
129 .or(SubstituteTypeParams::for_trait_impl(&source_scope, trait_, impl_def.clone()));
130
131 let items = items
132 .into_iter()
133 .map(|it| ast_transform::apply(&*ast_transform, it))
134 .map(|it| match it {
135 ast::AssocItem::Fn(def) => ast::AssocItem::Fn(add_body(def)),
136 ast::AssocItem::TypeAlias(def) => ast::AssocItem::TypeAlias(def.remove_bounds()),
137 _ => it,
138 })
139 .map(|it| edit::remove_attrs_and_docs(&it));
140
141 let new_impl_item_list = impl_item_list.append_items(items);
142 let new_impl_def = impl_def.with_assoc_item_list(new_impl_item_list);
143 let first_new_item =
144 new_impl_def.assoc_item_list().unwrap().assoc_items().nth(n_existing_items).unwrap();
145 return (new_impl_def, first_new_item);
146
147 fn add_body(fn_def: ast::Fn) -> ast::Fn {
148 match fn_def.body() {
149 Some(_) => fn_def,
150 None => {
151 let body =
152 make::block_expr(None, Some(make::expr_todo())).indent(edit::IndentLevel(1));
153 fn_def.with_body(body)
154 }
155 }
156 }
157}
158
159#[derive(Clone, Copy, Debug)]
160pub(crate) enum Cursor<'a> {
161 Replace(&'a SyntaxNode),
162 Before(&'a SyntaxNode),
163}
164
165impl<'a> Cursor<'a> {
166 fn node(self) -> &'a SyntaxNode {
167 match self {
168 Cursor::Replace(node) | Cursor::Before(node) => node,
169 }
170 }
171}
172
173pub(crate) fn render_snippet(_cap: SnippetCap, node: &SyntaxNode, cursor: Cursor) -> String {
174 assert!(cursor.node().ancestors().any(|it| it == *node));
175 let range = cursor.node().text_range() - node.text_range().start();
176 let range: ops::Range<usize> = range.into();
177
178 let mut placeholder = cursor.node().to_string();
179 escape(&mut placeholder);
180 let tab_stop = match cursor {
181 Cursor::Replace(placeholder) => format!("${{0:{}}}", placeholder),
182 Cursor::Before(placeholder) => format!("$0{}", placeholder),
183 };
184
185 let mut buf = node.to_string();
186 buf.replace_range(range, &tab_stop);
187 return buf;
188
189 fn escape(buf: &mut String) {
190 stdx::replace(buf, '{', r"\{");
191 stdx::replace(buf, '}', r"\}");
192 stdx::replace(buf, '$', r"\$");
193 }
194}
195
196pub(crate) fn vis_offset(node: &SyntaxNode) -> TextSize {
197 node.children_with_tokens()
198 .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
199 .map(|it| it.text_range().start())
200 .unwrap_or_else(|| node.text_range().start())
201}
202
203pub(crate) fn invert_boolean_expression(expr: ast::Expr) -> ast::Expr {
204 if let Some(expr) = invert_special_case(&expr) {
205 return expr;
206 }
207 make::expr_prefix(T![!], expr)
208}
209
210fn invert_special_case(expr: &ast::Expr) -> Option<ast::Expr> {
211 match expr {
212 ast::Expr::BinExpr(bin) => match bin.op_kind()? {
213 ast::BinOp::NegatedEqualityTest => bin.replace_op(T![==]).map(|it| it.into()),
214 ast::BinOp::EqualityTest => bin.replace_op(T![!=]).map(|it| it.into()),
215 // Parenthesize composite boolean expressions before prefixing `!`
216 ast::BinOp::BooleanAnd | ast::BinOp::BooleanOr => {
217 Some(make::expr_prefix(T![!], make::expr_paren(expr.clone())))
218 }
219 _ => None,
220 },
221 ast::Expr::MethodCallExpr(mce) => {
222 let receiver = mce.receiver()?;
223 let method = mce.name_ref()?;
224 let arg_list = mce.arg_list()?;
225
226 let method = match method.text().as_str() {
227 "is_some" => "is_none",
228 "is_none" => "is_some",
229 "is_ok" => "is_err",
230 "is_err" => "is_ok",
231 _ => return None,
232 };
233 Some(make::expr_method_call(receiver, method, arg_list))
234 }
235 ast::Expr::PrefixExpr(pe) if pe.op_kind()? == ast::PrefixOp::Not => {
236 if let ast::Expr::ParenExpr(parexpr) = pe.expr()? {
237 parexpr.expr()
238 } else {
239 pe.expr()
240 }
241 }
242 // FIXME:
243 // ast::Expr::Literal(true | false )
244 _ => None,
245 }
246}
247
248pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
249 [Direction::Next, Direction::Prev].iter().copied()
250}