Skip to content

Commit cf05b7d

Browse files
committed
Auto merge of #13051 - DropDemBits:attrs-and-comments-on-enum-variant, r=jonas-schievink
fix: Only move comments when extracting a struct from an enum variant Motivating example: ```rs #[derive(Debug, thiserror::Error)] enum Error { /// Some explanation for this error #[error("message")] $0Woops { code: u32 } } ``` now becomes ```rs /// Some explanation for this error #[derive(Debug, thiserror::Error)] struct Woops{ code: u32 } #[derive(Debug, thiserror::Error)] enum Error { #[error("message")] Woops(Woops) } ``` (the `thiserror::Error` derive being copied and the struct formatting aren't ideal, though those are issues for another day)
2 parents d9e2207 + 45dac9a commit cf05b7d

File tree

1 file changed

+68
-47
lines changed

1 file changed

+68
-47
lines changed

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

Lines changed: 68 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -101,21 +101,22 @@ pub(crate) fn extract_struct_from_enum_variant(
101101
});
102102
}
103103

104-
let indent = enum_ast.indent_level();
105104
let generic_params = enum_ast
106105
.generic_param_list()
107106
.and_then(|known_generics| extract_generic_params(&known_generics, &field_list));
108107
let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
109108
let def =
110109
create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
110+
111+
let enum_ast = variant.parent_enum();
112+
let indent = enum_ast.indent_level();
111113
def.reindent_to(indent);
112114

113-
let start_offset = &variant.parent_enum().syntax().clone();
114-
ted::insert_all_raw(
115-
ted::Position::before(start_offset),
115+
ted::insert_all(
116+
ted::Position::before(enum_ast.syntax()),
116117
vec![
117118
def.syntax().clone().into(),
118-
make::tokens::whitespace(&format!("\n\n{}", indent)).into(),
119+
make::tokens::whitespace(&format!("\n\n{indent}")).into(),
119120
],
120121
);
121122

@@ -227,7 +228,7 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b
227228
}
228229

229230
fn create_struct_def(
230-
variant_name: ast::Name,
231+
name: ast::Name,
231232
variant: &ast::Variant,
232233
field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
233234
generics: Option<ast::GenericParamList>,
@@ -269,43 +270,27 @@ fn create_struct_def(
269270
field_list.into()
270271
}
271272
};
272-
273273
field_list.reindent_to(IndentLevel::single());
274274

275-
let strukt = make::struct_(enum_vis, variant_name, generics, field_list).clone_for_update();
276-
277-
// FIXME: Consider making this an actual function somewhere (like in `AttrsOwnerEdit`) after some deliberation
278-
let attrs_and_docs = |node: &SyntaxNode| {
279-
let mut select_next_ws = false;
280-
node.children_with_tokens().filter(move |child| {
281-
let accept = match child.kind() {
282-
ATTR | COMMENT => {
283-
select_next_ws = true;
284-
return true;
285-
}
286-
WHITESPACE if select_next_ws => true,
287-
_ => false,
288-
};
289-
select_next_ws = false;
290-
291-
accept
292-
})
293-
};
275+
let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update();
294276

295-
// copy attributes & comments from variant
296-
let variant_attrs = attrs_and_docs(variant.syntax())
297-
.map(|tok| match tok.kind() {
298-
WHITESPACE => make::tokens::single_newline().into(),
299-
_ => tok,
300-
})
301-
.collect();
302-
ted::insert_all(ted::Position::first_child_of(strukt.syntax()), variant_attrs);
277+
// take comments from variant
278+
ted::insert_all(
279+
ted::Position::first_child_of(strukt.syntax()),
280+
take_all_comments(variant.syntax()),
281+
);
303282

304283
// copy attributes from enum
305284
ted::insert_all(
306285
ted::Position::first_child_of(strukt.syntax()),
307-
enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(),
286+
enum_
287+
.attrs()
288+
.flat_map(|it| {
289+
vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()]
290+
})
291+
.collect(),
308292
);
293+
309294
strukt
310295
}
311296

@@ -346,16 +331,48 @@ fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList
346331
})
347332
.unwrap_or_else(|| make::ty(&name.text()));
348333

334+
// change from a record to a tuple field list
349335
let tuple_field = make::tuple_field(None, ty);
350-
let replacement = make::variant(
351-
name,
352-
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
353-
)
354-
.clone_for_update();
355-
ted::replace(variant.syntax(), replacement.syntax());
336+
let field_list = make::tuple_field_list(iter::once(tuple_field)).clone_for_update();
337+
ted::replace(variant.field_list()?.syntax(), field_list.syntax());
338+
339+
// remove any ws after the name
340+
if let Some(ws) = name
341+
.syntax()
342+
.siblings_with_tokens(syntax::Direction::Next)
343+
.find_map(|tok| tok.into_token().filter(|tok| tok.kind() == WHITESPACE))
344+
{
345+
ted::remove(SyntaxElement::Token(ws));
346+
}
347+
356348
Some(())
357349
}
358350

351+
// Note: this also detaches whitespace after comments,
352+
// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
353+
// detaches nodes. If we only took the comments, we'd leave behind the old whitespace.
354+
fn take_all_comments(node: &SyntaxNode) -> Vec<SyntaxElement> {
355+
let mut remove_next_ws = false;
356+
node.children_with_tokens()
357+
.filter_map(move |child| match child.kind() {
358+
COMMENT => {
359+
remove_next_ws = true;
360+
child.detach();
361+
Some(child)
362+
}
363+
WHITESPACE if remove_next_ws => {
364+
remove_next_ws = false;
365+
child.detach();
366+
Some(make::tokens::single_newline().into())
367+
}
368+
_ => {
369+
remove_next_ws = false;
370+
None
371+
}
372+
})
373+
.collect()
374+
}
375+
359376
fn apply_references(
360377
insert_use_cfg: InsertUseConfig,
361378
segment: ast::PathSegment,
@@ -480,10 +497,14 @@ enum En<T> { Var(Var<T>) }"#,
480497
fn test_extract_struct_carries_over_attributes() {
481498
check_assist(
482499
extract_struct_from_enum_variant,
483-
r#"#[derive(Debug)]
500+
r#"
501+
#[derive(Debug)]
484502
#[derive(Clone)]
485503
enum Enum { Variant{ field: u32$0 } }"#,
486-
r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ field: u32 }
504+
r#"
505+
#[derive(Debug)]
506+
#[derive(Clone)]
507+
struct Variant{ field: u32 }
487508
488509
#[derive(Debug)]
489510
#[derive(Clone)]
@@ -614,7 +635,7 @@ enum A { One(One) }"#,
614635
}
615636

616637
#[test]
617-
fn test_extract_struct_keep_comments_and_attrs_on_variant_struct() {
638+
fn test_extract_struct_move_struct_variant_comments() {
618639
check_assist(
619640
extract_struct_from_enum_variant,
620641
r#"
@@ -631,19 +652,19 @@ enum A {
631652
/* comment */
632653
// other
633654
/// comment
634-
#[attr]
635655
struct One{
636656
a: u32
637657
}
638658
639659
enum A {
660+
#[attr]
640661
One(One)
641662
}"#,
642663
);
643664
}
644665

645666
#[test]
646-
fn test_extract_struct_keep_comments_and_attrs_on_variant_tuple() {
667+
fn test_extract_struct_move_tuple_variant_comments() {
647668
check_assist(
648669
extract_struct_from_enum_variant,
649670
r#"
@@ -658,10 +679,10 @@ enum A {
658679
/* comment */
659680
// other
660681
/// comment
661-
#[attr]
662682
struct One(u32, u32);
663683
664684
enum A {
685+
#[attr]
665686
One(One)
666687
}"#,
667688
);

0 commit comments

Comments
 (0)