aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_assists/src/move_bounds.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ra_assists/src/move_bounds.rs')
-rw-r--r--crates/ra_assists/src/move_bounds.rs135
1 files changed, 135 insertions, 0 deletions
diff --git a/crates/ra_assists/src/move_bounds.rs b/crates/ra_assists/src/move_bounds.rs
new file mode 100644
index 000000000..526de1d98
--- /dev/null
+++ b/crates/ra_assists/src/move_bounds.rs
@@ -0,0 +1,135 @@
1use hir::db::HirDatabase;
2use ra_syntax::{
3 ast::{self, AstNode, NameOwner, TypeBoundsOwner},
4 SyntaxElement,
5 SyntaxKind::*,
6 TextRange,
7};
8
9use crate::{ast_editor::AstBuilder, Assist, AssistCtx, AssistId};
10
11pub(crate) fn move_bounds_to_where_clause(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
12 let type_param_list = ctx.node_at_offset::<ast::TypeParamList>()?;
13
14 let mut type_params = type_param_list.type_params();
15 if type_params.all(|p| p.type_bound_list().is_none()) {
16 return None;
17 }
18
19 let parent = type_param_list.syntax().parent()?;
20 if parent.children_with_tokens().find(|it| it.kind() == WHERE_CLAUSE).is_some() {
21 return None;
22 }
23
24 let anchor: SyntaxElement = match parent.kind() {
25 FN_DEF => ast::FnDef::cast(parent)?.body()?.syntax().clone().into(),
26 TRAIT_DEF => ast::TraitDef::cast(parent)?.item_list()?.syntax().clone().into(),
27 IMPL_BLOCK => ast::ImplBlock::cast(parent)?.item_list()?.syntax().clone().into(),
28 ENUM_DEF => ast::EnumDef::cast(parent)?.variant_list()?.syntax().clone().into(),
29 STRUCT_DEF => parent
30 .children_with_tokens()
31 .find(|it| it.kind() == RECORD_FIELD_DEF_LIST || it.kind() == SEMI)?,
32 _ => return None,
33 };
34
35 ctx.add_action(
36 AssistId("move_bounds_to_where_clause"),
37 "move_bounds_to_where_clause",
38 |edit| {
39 let type_params = type_param_list.type_params().collect::<Vec<_>>();
40
41 for param in &type_params {
42 if let Some(bounds) = param.type_bound_list() {
43 let colon = param
44 .syntax()
45 .children_with_tokens()
46 .find(|it| it.kind() == COLON)
47 .unwrap();
48 let start = colon.text_range().start();
49 let end = bounds.syntax().text_range().end();
50 edit.delete(TextRange::from_to(start, end));
51 }
52 }
53
54 let predicates = type_params.iter().filter_map(build_predicate);
55 let where_clause = AstBuilder::<ast::WhereClause>::from_predicates(predicates);
56
57 let to_insert = match anchor.prev_sibling_or_token() {
58 Some(ref elem) if elem.kind() == WHITESPACE => {
59 format!("{} ", where_clause.syntax())
60 }
61 _ => format!(" {}", where_clause.syntax()),
62 };
63 edit.insert(anchor.text_range().start(), to_insert);
64 edit.target(type_param_list.syntax().text_range());
65 },
66 );
67
68 ctx.build()
69}
70
71fn build_predicate(param: &ast::TypeParam) -> Option<ast::WherePred> {
72 let path = AstBuilder::<ast::Path>::from_name(param.name()?);
73 let predicate =
74 AstBuilder::<ast::WherePred>::from_pieces(path, param.type_bound_list()?.bounds());
75 Some(predicate)
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 use crate::helpers::check_assist;
83
84 #[test]
85 fn move_bounds_to_where_clause_fn() {
86 check_assist(
87 move_bounds_to_where_clause,
88 r#"
89 fn foo<T: u32, <|>F: FnOnce(T) -> T>() {}
90 "#,
91 r#"
92 fn foo<T, <|>F>() where T: u32, F: FnOnce(T) -> T {}
93 "#,
94 );
95 }
96
97 #[test]
98 fn move_bounds_to_where_clause_impl() {
99 check_assist(
100 move_bounds_to_where_clause,
101 r#"
102 impl<U: u32, <|>T> A<U, T> {}
103 "#,
104 r#"
105 impl<U, <|>T> A<U, T> where U: u32 {}
106 "#,
107 );
108 }
109
110 #[test]
111 fn move_bounds_to_where_clause_struct() {
112 check_assist(
113 move_bounds_to_where_clause,
114 r#"
115 struct A<<|>T: Iterator<Item = u32>> {}
116 "#,
117 r#"
118 struct A<<|>T> where T: Iterator<Item = u32> {}
119 "#,
120 );
121 }
122
123 #[test]
124 fn move_bounds_to_where_clause_tuple_struct() {
125 check_assist(
126 move_bounds_to_where_clause,
127 r#"
128 struct Pair<<|>T: u32>(T, T);
129 "#,
130 r#"
131 struct Pair<<|>T>(T, T) where T: u32;
132 "#,
133 );
134 }
135}