1
1
use hir:: { Function , ModuleDef } ;
2
2
use ide_db:: { RootDatabase , assists:: AssistId , path_transform:: PathTransform } ;
3
3
use itertools:: Itertools ;
4
- use stdx:: { to_camel_case, to_lower_snake_case} ;
5
4
use syntax:: {
6
5
AstNode , SyntaxElement , SyntaxKind , SyntaxNode , T ,
7
6
ast:: {
8
7
self , HasAttrs , HasGenericParams , HasName , HasVisibility ,
9
8
edit:: { AstNodeEdit , IndentLevel } ,
10
9
make,
11
10
} ,
12
- match_ast, ted,
11
+ match_ast,
12
+ ted:: { self , Element } ,
13
13
} ;
14
14
15
15
use crate :: { AssistContext , Assists } ;
@@ -18,7 +18,7 @@ use crate::{AssistContext, Assists};
18
18
// Extracts a struct (part) of the signature of a function.
19
19
//
20
20
// ```
21
- // fn foo($0bar: u32, baz: u32) { ... }
21
+ // fn foo($0bar: u32, baz: u32$0 ) { ... }
22
22
// ```
23
23
// ->
24
24
// ```
@@ -31,35 +31,36 @@ pub(crate) fn extract_struct_from_function_signature(
31
31
acc : & mut Assists ,
32
32
ctx : & AssistContext < ' _ > ,
33
33
) -> Option < ( ) > {
34
- // TODO: get more specific than param list
35
- // how to get function name and param list/part of param list the is selected separately
36
- // or maybe just auto generate random name not based on function name?
37
34
let fn_ast = ctx. find_node_at_offset :: < ast:: Fn > ( ) ?;
38
- // we independently get the param list without going through fn (fn_ast.param_list()), because for some reason when we
39
- // go through the fn, the text_range is the whole function.
40
- let params_list = ctx. find_node_at_offset :: < ast:: ParamList > ( ) ?;
35
+ let param_list = fn_ast. param_list ( ) ?;
36
+ let used_param_list = param_list
37
+ . params ( )
38
+ // filter to only parameters in selection
39
+ . filter ( |p| p. syntax ( ) . text_range ( ) . intersect ( ctx. selection_trimmed ( ) ) . is_some ( ) )
40
+ . collect_vec ( ) ;
41
+ // TODO: make sure at least one thing there
42
+ let target =
43
+ used_param_list. iter ( ) . map ( |p| p. syntax ( ) . text_range ( ) ) . reduce ( |t, t2| t. cover ( t2) ) ?;
41
44
let fn_name = fn_ast. name ( ) ?;
42
- let name = make:: name ( & format ! ( "{}Struct" , to_camel_case( fn_name. text_non_mutable( ) ) ) ) ;
45
+ let name = make:: name ( & format ! ( "{}Struct" , stdx :: to_camel_case( fn_name. text_non_mutable( ) ) ) ) ;
43
46
44
47
let fn_hir = ctx. sema . to_def ( & fn_ast) ?;
45
48
if existing_definition ( ctx. db ( ) , & name, & fn_hir) {
46
49
cov_mark:: hit!( test_extract_function_signature_not_applicable_if_struct_exists) ;
47
50
return None ;
48
51
}
49
52
50
- // TODO: does this capture parenthesis
51
- let target = params_list. syntax ( ) . text_range ( ) ;
52
53
// TODO: special handiling for self?
53
54
// TODO: special handling for destrutered types (or maybe just don't support code action on
54
55
// destructed types yet
55
56
56
57
let field_list = make:: record_field_list (
57
- fn_ast
58
- . param_list ( ) ?
59
- . params ( )
58
+ used_param_list
59
+ . iter ( )
60
60
. map ( |param| {
61
61
Some ( make:: record_field (
62
62
fn_ast. visibility ( ) ,
63
+ // only works if its an ident pattern
63
64
param. pat ( ) . and_then ( pat_to_name) ?,
64
65
// TODO: how are we going to handle references without explicit lifetimes
65
66
param. ty ( ) ?,
@@ -76,7 +77,10 @@ pub(crate) fn extract_struct_from_function_signature(
76
77
tracing:: info!( "extract_struct_from_function_signature: starting edit" ) ;
77
78
builder. edit_file ( ctx. vfs_file_id ( ) ) ;
78
79
// this has to be after the edit_file (order matters)
80
+ // fn_ast and param_list must be "mut" for the effect to work on used_param_lsit
79
81
let fn_ast = builder. make_mut ( fn_ast) ;
82
+ let param_list = builder. make_mut ( param_list) ;
83
+ let used_param_list = used_param_list. into_iter ( ) . map ( |p| builder. make_mut ( p) ) . collect_vec ( ) ;
80
84
tracing:: info!( "extract_struct_from_function_signature: editing main file" ) ;
81
85
82
86
let generic_params = fn_ast
@@ -92,7 +96,7 @@ pub(crate) fn extract_struct_from_function_signature(
92
96
// So I do the resolving while its still param list
93
97
// and then apply it into record list after
94
98
let field_list = if let Some ( ( target_scope, source_scope) ) =
95
- ctx. sema . scope ( fn_ast. syntax ( ) ) . zip ( ctx. sema . scope ( params_list . syntax ( ) ) )
99
+ ctx. sema . scope ( fn_ast. syntax ( ) ) . zip ( ctx. sema . scope ( param_list . syntax ( ) ) )
96
100
{
97
101
let field_list = field_list. reset_indent ( ) ;
98
102
let field_list =
@@ -108,12 +112,13 @@ pub(crate) fn extract_struct_from_function_signature(
108
112
field_list. clone_for_update ( )
109
113
} ;
110
114
tracing:: info!( "extract_struct_from_function_signature: collecting fields" ) ;
111
- let def = create_struct_def ( name. clone ( ) , & fn_ast, & params_list , & field_list, generics) ;
115
+ let def = create_struct_def ( name. clone ( ) , & fn_ast, & used_param_list , & field_list, generics) ;
112
116
tracing:: info!( "extract_struct_from_function_signature: creating struct" ) ;
113
117
114
118
let indent = fn_ast. indent_level ( ) ;
115
119
let def = def. indent ( indent) ;
116
120
121
+
117
122
ted:: insert_all (
118
123
ted:: Position :: before ( fn_ast. syntax ( ) ) ,
119
124
vec ! [
@@ -122,16 +127,16 @@ pub(crate) fn extract_struct_from_function_signature(
122
127
] ,
123
128
) ;
124
129
tracing:: info!( "extract_struct_from_function_signature: inserting struct {def}" ) ;
125
- update_function ( name, & fn_ast , generic_params. map ( |g| g. clone_for_update ( ) ) ) . unwrap ( ) ;
130
+ update_function ( name, generic_params. map ( |g| g. clone_for_update ( ) ) , & used_param_list ) . unwrap ( ) ;
126
131
tracing:: info!( "extract_struct_from_function_signature: updating function signature and parameter uses" ) ;
127
132
} ,
128
133
)
129
134
}
130
135
131
136
fn update_function (
132
137
name : ast:: Name ,
133
- fn_ast : & ast:: Fn ,
134
138
generics : Option < ast:: GenericParamList > ,
139
+ used_param_list : & [ ast:: Param ] ,
135
140
) -> Option < ( ) > {
136
141
let generic_args = generics
137
142
. filter ( |generics| generics. generic_params ( ) . count ( ) > 0 )
@@ -143,24 +148,26 @@ fn update_function(
143
148
} ;
144
149
145
150
let param = make:: param (
146
- // TODO: do we want to destructure the struct
147
- // would make it easier in that we would not have to update all the uses of the variables in
151
+ // do we want to destructure the struct
152
+ // makes it easier in that we would not have to update all the uses of the variables in
148
153
// the function
149
- ast:: Pat :: IdentPat ( make:: ident_pat (
150
- false ,
151
- fn_ast. param_list ( ) ?. params ( ) . any ( |p| {
152
- p. pat ( )
153
- . is_some_and ( |p| matches ! ( p, ast:: Pat :: IdentPat ( p) if p. mut_token( ) . is_some( ) ) )
154
- } ) ,
155
- // TODO: maybe make a method that maps over a name's text
156
- make:: name ( & to_lower_snake_case ( name. text_non_mutable ( ) ) ) ,
154
+ ast:: Pat :: RecordPat ( make:: record_pat (
155
+ make:: path_from_text ( name. text_non_mutable ( ) ) ,
156
+ used_param_list
157
+ . iter ( )
158
+ . map ( |p| p. pat ( ) )
159
+ . chain ( std:: iter:: once ( Some ( ast:: Pat :: RestPat ( make:: rest_pat ( ) ) ) ) )
160
+ . collect :: < Option < Vec < _ > > > ( ) ?,
157
161
) ) ,
158
162
ty,
159
- ) ;
163
+ )
164
+ . clone_for_update ( ) ;
160
165
// TODO: will eventually need to handle self too
161
- let params_list = make:: param_list ( None , std:: iter:: once ( param) ) . clone_for_update ( ) ;
162
- ted:: replace ( fn_ast. param_list ( ) ?. syntax ( ) , params_list. syntax ( ) ) ;
163
- // TODO: update uses of parameters in function, if we do not destructure
166
+
167
+ let range = used_param_list. first ( ) ?. syntax ( ) . syntax_element ( )
168
+ ..=used_param_list. last ( ) ?. syntax ( ) . syntax_element ( ) ;
169
+ ted:: replace_all ( range, vec ! [ param. syntax( ) . syntax_element( ) ] ) ;
170
+ // no need update uses of parameters in function, because we destructure the struct
164
171
Some ( ( ) )
165
172
}
166
173
@@ -173,7 +180,7 @@ fn pat_to_name(pat: ast::Pat) -> Option<ast::Name> {
173
180
fn create_struct_def (
174
181
name : ast:: Name ,
175
182
fn_ast : & ast:: Fn ,
176
- param_ast : & ast:: ParamList ,
183
+ param_ast : & [ ast:: Param ] ,
177
184
field_list : & ast:: RecordFieldList ,
178
185
generics : Option < ast:: GenericParamList > ,
179
186
) -> ast:: Struct {
@@ -203,15 +210,15 @@ fn create_struct_def(
203
210
// take comments from only inside signature
204
211
ted:: insert_all (
205
212
ted:: Position :: first_child_of ( strukt. syntax ( ) ) ,
206
- take_all_comments ( param_ast. syntax ( ) ) ,
213
+ take_all_comments ( param_ast. iter ( ) ) ,
207
214
) ;
208
215
209
216
// TODO: this may not be correct as we shouldn't put all the attributes at the top
210
217
// copy attributes from each parameter
211
218
ted:: insert_all (
212
219
ted:: Position :: first_child_of ( strukt. syntax ( ) ) ,
213
220
param_ast
214
- . params ( )
221
+ . iter ( )
215
222
. flat_map ( |p| p. attrs ( ) )
216
223
. flat_map ( |it| {
217
224
vec ! [ it. syntax( ) . clone_for_update( ) . into( ) , make:: tokens:: single_newline( ) . into( ) ]
@@ -224,9 +231,9 @@ fn create_struct_def(
224
231
// Note: this also detaches whitespace after comments,
225
232
// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
226
233
// detaches nodes. If we only took the comments, we'd leave behind the old whitespace.
227
- fn take_all_comments ( node : & SyntaxNode ) -> Vec < SyntaxElement > {
234
+ fn take_all_comments < ' a > ( node : impl Iterator < Item = & ' a ast :: Param > ) -> Vec < SyntaxElement > {
228
235
let mut remove_next_ws = false ;
229
- node. children_with_tokens ( )
236
+ node. flat_map ( |p| p . syntax ( ) . children_with_tokens ( ) )
230
237
. filter_map ( move |child| match child. kind ( ) {
231
238
SyntaxKind :: COMMENT => {
232
239
remove_next_ws = true ;
@@ -330,7 +337,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Fu
330
337
#[ cfg( test) ]
331
338
mod tests {
332
339
use super :: * ;
333
- use crate :: tests:: check_assist_not_applicable;
340
+ use crate :: tests:: { check_assist , check_assist_not_applicable} ;
334
341
335
342
#[ test]
336
343
fn test_extract_function_signature_not_applicable_if_struct_exists ( ) {
@@ -340,6 +347,34 @@ mod tests {
340
347
r#"
341
348
struct OneStruct;
342
349
fn one($0x: u8, y: u32) {}
350
+ "# ,
351
+ ) ;
352
+ }
353
+ #[ test]
354
+ fn test_extract_function_signature_single_parameters ( ) {
355
+ check_assist (
356
+ extract_struct_from_function_signature,
357
+ r#"
358
+ fn foo($0bar: i32$0, baz: i32) {}
359
+ "# ,
360
+ r#"
361
+ struct FooStruct{ bar: i32 }
362
+
363
+ fn foo(FooStruct { bar, .. }: FooStruct, baz: i32) {}
364
+ "# ,
365
+ ) ;
366
+ }
367
+ #[ test]
368
+ fn test_extract_function_signature_all_parameters ( ) {
369
+ check_assist (
370
+ extract_struct_from_function_signature,
371
+ r#"
372
+ fn foo($0bar: i32, baz: i32$0) {}
373
+ "# ,
374
+ r#"
375
+ struct FooStruct{ bar: i32, baz: i32 }
376
+
377
+ fn foo(FooStruct { bar, baz, .. }: FooStruct) {}
343
378
"# ,
344
379
) ;
345
380
}
0 commit comments