diff options
-rw-r--r-- | crates/ra_assists/src/handlers/reorder_fields.rs | 207 | ||||
-rw-r--r-- | crates/ra_assists/src/lib.rs | 2 | ||||
-rw-r--r-- | crates/ra_hir_def/src/body/lower.rs | 3 | ||||
-rw-r--r-- | crates/ra_syntax/src/ast/generated/nodes.rs | 8 | ||||
-rw-r--r-- | xtask/src/ast_src.rs | 1 |
5 files changed, 219 insertions, 2 deletions
diff --git a/crates/ra_assists/src/handlers/reorder_fields.rs b/crates/ra_assists/src/handlers/reorder_fields.rs new file mode 100644 index 000000000..c17c1288c --- /dev/null +++ b/crates/ra_assists/src/handlers/reorder_fields.rs | |||
@@ -0,0 +1,207 @@ | |||
1 | use std::collections::HashMap; | ||
2 | |||
3 | use itertools::Itertools; | ||
4 | |||
5 | use hir::{Adt, ModuleDef, PathResolution, Semantics, Struct}; | ||
6 | use ra_ide_db::RootDatabase; | ||
7 | use ra_syntax::ast::{Name, Pat}; | ||
8 | use ra_syntax::{ | ||
9 | ast, | ||
10 | ast::{Path, RecordField, RecordLit, RecordPat}, | ||
11 | AstNode, | ||
12 | }; | ||
13 | |||
14 | use crate::{ | ||
15 | assist_ctx::{Assist, AssistCtx}, | ||
16 | AssistId, | ||
17 | }; | ||
18 | |||
19 | pub(crate) fn reorder_fields(ctx: AssistCtx) -> Option<Assist> { | ||
20 | reorder_struct(ctx.clone()).or_else(|| reorder_struct_pat(ctx)) | ||
21 | } | ||
22 | |||
23 | fn reorder_struct(ctx: AssistCtx) -> Option<Assist> { | ||
24 | let record: RecordLit = ctx.find_node_at_offset()?; | ||
25 | reorder(ctx, &record, &record.path()?, field_name) | ||
26 | } | ||
27 | |||
28 | fn field_name(r: &RecordField) -> String { | ||
29 | r.name_ref() | ||
30 | .map(|name| name.syntax().text()) | ||
31 | .unwrap_or_else(|| r.expr().unwrap().syntax().text()) | ||
32 | .to_string() | ||
33 | } | ||
34 | |||
35 | fn reorder_struct_pat(ctx: AssistCtx) -> Option<Assist> { | ||
36 | let record: RecordPat = ctx.find_node_at_offset()?; | ||
37 | reorder(ctx, &record, &record.path()?, field_pat_name) | ||
38 | } | ||
39 | |||
40 | fn field_pat_name(field: &Pat) -> String { | ||
41 | field.syntax().children().find_map(Name::cast).map(|n| n.to_string()).unwrap_or_default() | ||
42 | } | ||
43 | |||
44 | fn reorder<R: AstNode, F: AstNode + Eq + Clone>( | ||
45 | ctx: AssistCtx, | ||
46 | record: &R, | ||
47 | path: &Path, | ||
48 | field_name: fn(&F) -> String, | ||
49 | ) -> Option<Assist> { | ||
50 | let ranks = compute_fields_ranks(path, &ctx)?; | ||
51 | let fields: Vec<F> = get_fields(record); | ||
52 | let sorted_fields: Vec<F> = | ||
53 | sort_by_rank(&fields, |f| *ranks.get(&field_name(f)).unwrap_or(&usize::max_value())); | ||
54 | |||
55 | if sorted_fields == fields { | ||
56 | return None; | ||
57 | } | ||
58 | |||
59 | ctx.add_assist(AssistId("reorder_fields"), "Reorder record fields", |edit| { | ||
60 | for (old, new) in fields.into_iter().zip(sorted_fields) { | ||
61 | edit.replace_ast(old, new); | ||
62 | } | ||
63 | edit.target(record.syntax().text_range()) | ||
64 | }) | ||
65 | } | ||
66 | |||
67 | fn get_fields<R: AstNode, F: AstNode>(record: &R) -> Vec<F> { | ||
68 | record.syntax().children().flat_map(|n1| n1.children()).filter_map(|n3| F::cast(n3)).collect() | ||
69 | } | ||
70 | |||
71 | fn sort_by_rank<F: AstNode + Clone>(fields: &[F], get_rank: impl FnMut(&F) -> usize) -> Vec<F> { | ||
72 | fields.iter().cloned().sorted_by_key(get_rank).collect() | ||
73 | } | ||
74 | |||
75 | fn struct_definition(path: &ast::Path, sema: &Semantics<RootDatabase>) -> Option<Struct> { | ||
76 | match sema.resolve_path(path) { | ||
77 | Some(PathResolution::Def(ModuleDef::Adt(Adt::Struct(s)))) => Some(s), | ||
78 | _ => None, | ||
79 | } | ||
80 | } | ||
81 | |||
82 | fn compute_fields_ranks(path: &Path, ctx: &AssistCtx) -> Option<HashMap<String, usize>> { | ||
83 | Some( | ||
84 | struct_definition(path, ctx.sema)? | ||
85 | .fields(ctx.db) | ||
86 | .iter() | ||
87 | .enumerate() | ||
88 | .map(|(idx, field)| (field.name(ctx.db).to_string(), idx)) | ||
89 | .collect(), | ||
90 | ) | ||
91 | } | ||
92 | |||
93 | #[cfg(test)] | ||
94 | mod tests { | ||
95 | use crate::helpers::{check_assist, check_assist_not_applicable}; | ||
96 | |||
97 | use super::*; | ||
98 | |||
99 | #[test] | ||
100 | fn not_applicable_if_sorted() { | ||
101 | check_assist_not_applicable( | ||
102 | reorder_fields, | ||
103 | r#" | ||
104 | struct Foo { | ||
105 | foo: i32, | ||
106 | bar: i32, | ||
107 | } | ||
108 | |||
109 | const test: Foo = <|>Foo { foo: 0, bar: 0 }; | ||
110 | "#, | ||
111 | ) | ||
112 | } | ||
113 | |||
114 | #[test] | ||
115 | fn trivial_empty_fields() { | ||
116 | check_assist_not_applicable( | ||
117 | reorder_fields, | ||
118 | r#" | ||
119 | struct Foo {}; | ||
120 | const test: Foo = <|>Foo {} | ||
121 | "#, | ||
122 | ) | ||
123 | } | ||
124 | |||
125 | #[test] | ||
126 | fn reorder_struct_fields() { | ||
127 | check_assist( | ||
128 | reorder_fields, | ||
129 | r#" | ||
130 | struct Foo {foo: i32, bar: i32}; | ||
131 | const test: Foo = <|>Foo {bar: 0, foo: 1} | ||
132 | "#, | ||
133 | r#" | ||
134 | struct Foo {foo: i32, bar: i32}; | ||
135 | const test: Foo = <|>Foo {foo: 1, bar: 0} | ||
136 | "#, | ||
137 | ) | ||
138 | } | ||
139 | |||
140 | #[test] | ||
141 | fn reorder_struct_pattern() { | ||
142 | check_assist( | ||
143 | reorder_fields, | ||
144 | r#" | ||
145 | struct Foo { foo: i64, bar: i64, baz: i64 } | ||
146 | |||
147 | fn f(f: Foo) -> { | ||
148 | match f { | ||
149 | <|>Foo { baz: 0, ref mut bar, .. } => (), | ||
150 | _ => () | ||
151 | } | ||
152 | } | ||
153 | "#, | ||
154 | r#" | ||
155 | struct Foo { foo: i64, bar: i64, baz: i64 } | ||
156 | |||
157 | fn f(f: Foo) -> { | ||
158 | match f { | ||
159 | <|>Foo { ref mut bar, baz: 0, .. } => (), | ||
160 | _ => () | ||
161 | } | ||
162 | } | ||
163 | "#, | ||
164 | ) | ||
165 | } | ||
166 | |||
167 | #[test] | ||
168 | fn reorder_with_extra_field() { | ||
169 | check_assist( | ||
170 | reorder_fields, | ||
171 | r#" | ||
172 | struct Foo { | ||
173 | foo: String, | ||
174 | bar: String, | ||
175 | } | ||
176 | |||
177 | impl Foo { | ||
178 | fn new() -> Foo { | ||
179 | let foo = String::new(); | ||
180 | <|>Foo { | ||
181 | bar: foo.clone(), | ||
182 | extra: "Extra field", | ||
183 | foo, | ||
184 | } | ||
185 | } | ||
186 | } | ||
187 | "#, | ||
188 | r#" | ||
189 | struct Foo { | ||
190 | foo: String, | ||
191 | bar: String, | ||
192 | } | ||
193 | |||
194 | impl Foo { | ||
195 | fn new() -> Foo { | ||
196 | let foo = String::new(); | ||
197 | <|>Foo { | ||
198 | foo, | ||
199 | bar: foo.clone(), | ||
200 | extra: "Extra field", | ||
201 | } | ||
202 | } | ||
203 | } | ||
204 | "#, | ||
205 | ) | ||
206 | } | ||
207 | } | ||
diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 5ba5254fd..a00136da1 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs | |||
@@ -129,6 +129,7 @@ mod handlers { | |||
129 | mod replace_unwrap_with_match; | 129 | mod replace_unwrap_with_match; |
130 | mod split_import; | 130 | mod split_import; |
131 | mod add_from_impl_for_enum; | 131 | mod add_from_impl_for_enum; |
132 | mod reorder_fields; | ||
132 | 133 | ||
133 | pub(crate) fn all() -> &'static [AssistHandler] { | 134 | pub(crate) fn all() -> &'static [AssistHandler] { |
134 | &[ | 135 | &[ |
@@ -170,6 +171,7 @@ mod handlers { | |||
170 | // These are manually sorted for better priorities | 171 | // These are manually sorted for better priorities |
171 | add_missing_impl_members::add_missing_impl_members, | 172 | add_missing_impl_members::add_missing_impl_members, |
172 | add_missing_impl_members::add_missing_default_members, | 173 | add_missing_impl_members::add_missing_default_members, |
174 | reorder_fields::reorder_fields, | ||
173 | ] | 175 | ] |
174 | } | 176 | } |
175 | } | 177 | } |
diff --git a/crates/ra_hir_def/src/body/lower.rs b/crates/ra_hir_def/src/body/lower.rs index b0d71eb3d..80492b733 100644 --- a/crates/ra_hir_def/src/body/lower.rs +++ b/crates/ra_hir_def/src/body/lower.rs | |||
@@ -689,9 +689,10 @@ impl ExprCollector<'_> { | |||
689 | Pat::Missing | 689 | Pat::Missing |
690 | } | 690 | } |
691 | } | 691 | } |
692 | |||
693 | // FIXME: implement | 692 | // FIXME: implement |
694 | ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, | 693 | ast::Pat::BoxPat(_) | ast::Pat::RangePat(_) | ast::Pat::MacroPat(_) => Pat::Missing, |
694 | // FIXME: implement | ||
695 | ast::Pat::RecordFieldPat(_) => Pat::Missing, | ||
695 | }; | 696 | }; |
696 | let ptr = AstPtr::new(&pat); | 697 | let ptr = AstPtr::new(&pat); |
697 | self.alloc_pat(pattern, Either::Left(ptr)) | 698 | self.alloc_pat(pattern, Either::Left(ptr)) |
diff --git a/crates/ra_syntax/src/ast/generated/nodes.rs b/crates/ra_syntax/src/ast/generated/nodes.rs index 20f663046..79b225622 100644 --- a/crates/ra_syntax/src/ast/generated/nodes.rs +++ b/crates/ra_syntax/src/ast/generated/nodes.rs | |||
@@ -3256,6 +3256,7 @@ pub enum Pat { | |||
3256 | RangePat(RangePat), | 3256 | RangePat(RangePat), |
3257 | LiteralPat(LiteralPat), | 3257 | LiteralPat(LiteralPat), |
3258 | MacroPat(MacroPat), | 3258 | MacroPat(MacroPat), |
3259 | RecordFieldPat(RecordFieldPat), | ||
3259 | } | 3260 | } |
3260 | impl From<OrPat> for Pat { | 3261 | impl From<OrPat> for Pat { |
3261 | fn from(node: OrPat) -> Pat { Pat::OrPat(node) } | 3262 | fn from(node: OrPat) -> Pat { Pat::OrPat(node) } |
@@ -3302,12 +3303,15 @@ impl From<LiteralPat> for Pat { | |||
3302 | impl From<MacroPat> for Pat { | 3303 | impl From<MacroPat> for Pat { |
3303 | fn from(node: MacroPat) -> Pat { Pat::MacroPat(node) } | 3304 | fn from(node: MacroPat) -> Pat { Pat::MacroPat(node) } |
3304 | } | 3305 | } |
3306 | impl From<RecordFieldPat> for Pat { | ||
3307 | fn from(node: RecordFieldPat) -> Pat { Pat::RecordFieldPat(node) } | ||
3308 | } | ||
3305 | impl AstNode for Pat { | 3309 | impl AstNode for Pat { |
3306 | fn can_cast(kind: SyntaxKind) -> bool { | 3310 | fn can_cast(kind: SyntaxKind) -> bool { |
3307 | match kind { | 3311 | match kind { |
3308 | OR_PAT | PAREN_PAT | REF_PAT | BOX_PAT | BIND_PAT | PLACEHOLDER_PAT | DOT_DOT_PAT | 3312 | OR_PAT | PAREN_PAT | REF_PAT | BOX_PAT | BIND_PAT | PLACEHOLDER_PAT | DOT_DOT_PAT |
3309 | | PATH_PAT | RECORD_PAT | TUPLE_STRUCT_PAT | TUPLE_PAT | SLICE_PAT | RANGE_PAT | 3313 | | PATH_PAT | RECORD_PAT | TUPLE_STRUCT_PAT | TUPLE_PAT | SLICE_PAT | RANGE_PAT |
3310 | | LITERAL_PAT | MACRO_PAT => true, | 3314 | | LITERAL_PAT | MACRO_PAT | RECORD_FIELD_PAT => true, |
3311 | _ => false, | 3315 | _ => false, |
3312 | } | 3316 | } |
3313 | } | 3317 | } |
@@ -3328,6 +3332,7 @@ impl AstNode for Pat { | |||
3328 | RANGE_PAT => Pat::RangePat(RangePat { syntax }), | 3332 | RANGE_PAT => Pat::RangePat(RangePat { syntax }), |
3329 | LITERAL_PAT => Pat::LiteralPat(LiteralPat { syntax }), | 3333 | LITERAL_PAT => Pat::LiteralPat(LiteralPat { syntax }), |
3330 | MACRO_PAT => Pat::MacroPat(MacroPat { syntax }), | 3334 | MACRO_PAT => Pat::MacroPat(MacroPat { syntax }), |
3335 | RECORD_FIELD_PAT => Pat::RecordFieldPat(RecordFieldPat { syntax }), | ||
3331 | _ => return None, | 3336 | _ => return None, |
3332 | }; | 3337 | }; |
3333 | Some(res) | 3338 | Some(res) |
@@ -3349,6 +3354,7 @@ impl AstNode for Pat { | |||
3349 | Pat::RangePat(it) => &it.syntax, | 3354 | Pat::RangePat(it) => &it.syntax, |
3350 | Pat::LiteralPat(it) => &it.syntax, | 3355 | Pat::LiteralPat(it) => &it.syntax, |
3351 | Pat::MacroPat(it) => &it.syntax, | 3356 | Pat::MacroPat(it) => &it.syntax, |
3357 | Pat::RecordFieldPat(it) => &it.syntax, | ||
3352 | } | 3358 | } |
3353 | } | 3359 | } |
3354 | } | 3360 | } |
diff --git a/xtask/src/ast_src.rs b/xtask/src/ast_src.rs index bb97b13fe..eb5c3abf7 100644 --- a/xtask/src/ast_src.rs +++ b/xtask/src/ast_src.rs | |||
@@ -741,6 +741,7 @@ pub(crate) const AST_SRC: AstSrc = AstSrc { | |||
741 | RangePat, | 741 | RangePat, |
742 | LiteralPat, | 742 | LiteralPat, |
743 | MacroPat, | 743 | MacroPat, |
744 | RecordFieldPat, | ||
744 | } | 745 | } |
745 | 746 | ||
746 | enum RecordInnerPat { | 747 | enum RecordInnerPat { |