Skip to content

Commit d0029b2

Browse files
committed
feat: extract-struct-from-function-signature
now destructures struct in function signature and only add paramereters that are selected to the struct added another test
1 parent 5412ca6 commit d0029b2

File tree

2 files changed

+75
-40
lines changed

2 files changed

+75
-40
lines changed

crates/ide-assists/src/handlers/extract_struct_from_function_signature.rs

Lines changed: 74 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
11
use hir::{Function, ModuleDef};
22
use ide_db::{RootDatabase, assists::AssistId, path_transform::PathTransform};
33
use itertools::Itertools;
4-
use stdx::{to_camel_case, to_lower_snake_case};
54
use syntax::{
65
AstNode, SyntaxElement, SyntaxKind, SyntaxNode, T,
76
ast::{
87
self, HasAttrs, HasGenericParams, HasName, HasVisibility,
98
edit::{AstNodeEdit, IndentLevel},
109
make,
1110
},
12-
match_ast, ted,
11+
match_ast,
12+
ted::{self, Element},
1313
};
1414

1515
use crate::{AssistContext, Assists};
@@ -18,7 +18,7 @@ use crate::{AssistContext, Assists};
1818
// Extracts a struct (part) of the signature of a function.
1919
//
2020
// ```
21-
// fn foo($0bar: u32, baz: u32) { ... }
21+
// fn foo($0bar: u32, baz: u32$0) { ... }
2222
// ```
2323
// ->
2424
// ```
@@ -31,35 +31,36 @@ pub(crate) fn extract_struct_from_function_signature(
3131
acc: &mut Assists,
3232
ctx: &AssistContext<'_>,
3333
) -> 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?
3734
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))?;
4144
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())));
4346

4447
let fn_hir = ctx.sema.to_def(&fn_ast)?;
4548
if existing_definition(ctx.db(), &name, &fn_hir) {
4649
cov_mark::hit!(test_extract_function_signature_not_applicable_if_struct_exists);
4750
return None;
4851
}
4952

50-
// TODO: does this capture parenthesis
51-
let target = params_list.syntax().text_range();
5253
// TODO: special handiling for self?
5354
// TODO: special handling for destrutered types (or maybe just don't support code action on
5455
// destructed types yet
5556

5657
let field_list = make::record_field_list(
57-
fn_ast
58-
.param_list()?
59-
.params()
58+
used_param_list
59+
.iter()
6060
.map(|param| {
6161
Some(make::record_field(
6262
fn_ast.visibility(),
63+
// only works if its an ident pattern
6364
param.pat().and_then(pat_to_name)?,
6465
// TODO: how are we going to handle references without explicit lifetimes
6566
param.ty()?,
@@ -76,7 +77,10 @@ pub(crate) fn extract_struct_from_function_signature(
7677
tracing::info!("extract_struct_from_function_signature: starting edit");
7778
builder.edit_file(ctx.vfs_file_id());
7879
// 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
7981
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();
8084
tracing::info!("extract_struct_from_function_signature: editing main file");
8185

8286
let generic_params = fn_ast
@@ -92,7 +96,7 @@ pub(crate) fn extract_struct_from_function_signature(
9296
// So I do the resolving while its still param list
9397
// and then apply it into record list after
9498
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()))
96100
{
97101
let field_list = field_list.reset_indent();
98102
let field_list =
@@ -108,12 +112,13 @@ pub(crate) fn extract_struct_from_function_signature(
108112
field_list.clone_for_update()
109113
};
110114
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);
112116
tracing::info!("extract_struct_from_function_signature: creating struct");
113117

114118
let indent = fn_ast.indent_level();
115119
let def = def.indent(indent);
116120

121+
117122
ted::insert_all(
118123
ted::Position::before(fn_ast.syntax()),
119124
vec![
@@ -122,16 +127,16 @@ pub(crate) fn extract_struct_from_function_signature(
122127
],
123128
);
124129
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();
126131
tracing::info!("extract_struct_from_function_signature: updating function signature and parameter uses");
127132
},
128133
)
129134
}
130135

131136
fn update_function(
132137
name: ast::Name,
133-
fn_ast: &ast::Fn,
134138
generics: Option<ast::GenericParamList>,
139+
used_param_list: &[ast::Param],
135140
) -> Option<()> {
136141
let generic_args = generics
137142
.filter(|generics| generics.generic_params().count() > 0)
@@ -143,24 +148,26 @@ fn update_function(
143148
};
144149

145150
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
148153
// 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<_>>>()?,
157161
)),
158162
ty,
159-
);
163+
)
164+
.clone_for_update();
160165
// 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
164171
Some(())
165172
}
166173

@@ -173,7 +180,7 @@ fn pat_to_name(pat: ast::Pat) -> Option<ast::Name> {
173180
fn create_struct_def(
174181
name: ast::Name,
175182
fn_ast: &ast::Fn,
176-
param_ast: &ast::ParamList,
183+
param_ast: &[ast::Param],
177184
field_list: &ast::RecordFieldList,
178185
generics: Option<ast::GenericParamList>,
179186
) -> ast::Struct {
@@ -203,15 +210,15 @@ fn create_struct_def(
203210
// take comments from only inside signature
204211
ted::insert_all(
205212
ted::Position::first_child_of(strukt.syntax()),
206-
take_all_comments(param_ast.syntax()),
213+
take_all_comments(param_ast.iter()),
207214
);
208215

209216
// TODO: this may not be correct as we shouldn't put all the attributes at the top
210217
// copy attributes from each parameter
211218
ted::insert_all(
212219
ted::Position::first_child_of(strukt.syntax()),
213220
param_ast
214-
.params()
221+
.iter()
215222
.flat_map(|p| p.attrs())
216223
.flat_map(|it| {
217224
vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()]
@@ -224,9 +231,9 @@ fn create_struct_def(
224231
// Note: this also detaches whitespace after comments,
225232
// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
226233
// 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> {
228235
let mut remove_next_ws = false;
229-
node.children_with_tokens()
236+
node.flat_map(|p| p.syntax().children_with_tokens())
230237
.filter_map(move |child| match child.kind() {
231238
SyntaxKind::COMMENT => {
232239
remove_next_ws = true;
@@ -330,7 +337,7 @@ fn existing_definition(db: &RootDatabase, variant_name: &ast::Name, variant: &Fu
330337
#[cfg(test)]
331338
mod tests {
332339
use super::*;
333-
use crate::tests::check_assist_not_applicable;
340+
use crate::tests::{check_assist, check_assist_not_applicable};
334341

335342
#[test]
336343
fn test_extract_function_signature_not_applicable_if_struct_exists() {
@@ -340,6 +347,34 @@ mod tests {
340347
r#"
341348
struct OneStruct;
342349
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) {}
343378
"#,
344379
);
345380
}

crates/ide-assists/src/tests/generated.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1192,7 +1192,7 @@ fn doctest_extract_struct_from_function_signature() {
11921192
check_doc_test(
11931193
"extract_struct_from_function_signature",
11941194
r#####"
1195-
fn foo($0bar: u32, baz: u32) { ... }
1195+
fn foo($0bar: u32, baz: u32$0) { ... }
11961196
"#####,
11971197
r#####"
11981198
struct FooStruct{ bar: u32, baz: u32 }

0 commit comments

Comments
 (0)