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)
This commit is contained in:
bors 2022-08-31 13:34:43 +00:00
commit cf05b7db4d

View file

@ -101,21 +101,22 @@ pub(crate) fn extract_struct_from_enum_variant(
}); });
} }
let indent = enum_ast.indent_level();
let generic_params = enum_ast let generic_params = enum_ast
.generic_param_list() .generic_param_list()
.and_then(|known_generics| extract_generic_params(&known_generics, &field_list)); .and_then(|known_generics| extract_generic_params(&known_generics, &field_list));
let generics = generic_params.as_ref().map(|generics| generics.clone_for_update()); let generics = generic_params.as_ref().map(|generics| generics.clone_for_update());
let def = let def =
create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast); create_struct_def(variant_name.clone(), &variant, &field_list, generics, &enum_ast);
let enum_ast = variant.parent_enum();
let indent = enum_ast.indent_level();
def.reindent_to(indent); def.reindent_to(indent);
let start_offset = &variant.parent_enum().syntax().clone(); ted::insert_all(
ted::insert_all_raw( ted::Position::before(enum_ast.syntax()),
ted::Position::before(start_offset),
vec![ vec![
def.syntax().clone().into(), def.syntax().clone().into(),
make::tokens::whitespace(&format!("\n\n{}", indent)).into(), make::tokens::whitespace(&format!("\n\n{indent}")).into(),
], ],
); );
@ -227,7 +228,7 @@ fn tag_generics_in_variant(ty: &ast::Type, generics: &mut [(ast::GenericParam, b
} }
fn create_struct_def( fn create_struct_def(
variant_name: ast::Name, name: ast::Name,
variant: &ast::Variant, variant: &ast::Variant,
field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>, field_list: &Either<ast::RecordFieldList, ast::TupleFieldList>,
generics: Option<ast::GenericParamList>, generics: Option<ast::GenericParamList>,
@ -269,43 +270,27 @@ fn create_struct_def(
field_list.into() field_list.into()
} }
}; };
field_list.reindent_to(IndentLevel::single()); field_list.reindent_to(IndentLevel::single());
let strukt = make::struct_(enum_vis, variant_name, generics, field_list).clone_for_update(); let strukt = make::struct_(enum_vis, name, generics, field_list).clone_for_update();
// FIXME: Consider making this an actual function somewhere (like in `AttrsOwnerEdit`) after some deliberation // take comments from variant
let attrs_and_docs = |node: &SyntaxNode| { ted::insert_all(
let mut select_next_ws = false; ted::Position::first_child_of(strukt.syntax()),
node.children_with_tokens().filter(move |child| { take_all_comments(variant.syntax()),
let accept = match child.kind() { );
ATTR | COMMENT => {
select_next_ws = true;
return true;
}
WHITESPACE if select_next_ws => true,
_ => false,
};
select_next_ws = false;
accept
})
};
// copy attributes & comments from variant
let variant_attrs = attrs_and_docs(variant.syntax())
.map(|tok| match tok.kind() {
WHITESPACE => make::tokens::single_newline().into(),
_ => tok,
})
.collect();
ted::insert_all(ted::Position::first_child_of(strukt.syntax()), variant_attrs);
// copy attributes from enum // copy attributes from enum
ted::insert_all( ted::insert_all(
ted::Position::first_child_of(strukt.syntax()), ted::Position::first_child_of(strukt.syntax()),
enum_.attrs().map(|it| it.syntax().clone_for_update().into()).collect(), enum_
.attrs()
.flat_map(|it| {
vec![it.syntax().clone_for_update().into(), make::tokens::single_newline().into()]
})
.collect(),
); );
strukt strukt
} }
@ -346,16 +331,48 @@ fn update_variant(variant: &ast::Variant, generics: Option<ast::GenericParamList
}) })
.unwrap_or_else(|| make::ty(&name.text())); .unwrap_or_else(|| make::ty(&name.text()));
// change from a record to a tuple field list
let tuple_field = make::tuple_field(None, ty); let tuple_field = make::tuple_field(None, ty);
let replacement = make::variant( let field_list = make::tuple_field_list(iter::once(tuple_field)).clone_for_update();
name, ted::replace(variant.field_list()?.syntax(), field_list.syntax());
Some(ast::FieldList::TupleFieldList(make::tuple_field_list(iter::once(tuple_field)))),
) // remove any ws after the name
.clone_for_update(); if let Some(ws) = name
ted::replace(variant.syntax(), replacement.syntax()); .syntax()
.siblings_with_tokens(syntax::Direction::Next)
.find_map(|tok| tok.into_token().filter(|tok| tok.kind() == WHITESPACE))
{
ted::remove(SyntaxElement::Token(ws));
}
Some(()) Some(())
} }
// Note: this also detaches whitespace after comments,
// since `SyntaxNode::splice_children` (and by extension `ted::insert_all_raw`)
// detaches nodes. If we only took the comments, we'd leave behind the old whitespace.
fn take_all_comments(node: &SyntaxNode) -> Vec<SyntaxElement> {
let mut remove_next_ws = false;
node.children_with_tokens()
.filter_map(move |child| match child.kind() {
COMMENT => {
remove_next_ws = true;
child.detach();
Some(child)
}
WHITESPACE if remove_next_ws => {
remove_next_ws = false;
child.detach();
Some(make::tokens::single_newline().into())
}
_ => {
remove_next_ws = false;
None
}
})
.collect()
}
fn apply_references( fn apply_references(
insert_use_cfg: InsertUseConfig, insert_use_cfg: InsertUseConfig,
segment: ast::PathSegment, segment: ast::PathSegment,
@ -480,10 +497,14 @@ enum En<T> { Var(Var<T>) }"#,
fn test_extract_struct_carries_over_attributes() { fn test_extract_struct_carries_over_attributes() {
check_assist( check_assist(
extract_struct_from_enum_variant, extract_struct_from_enum_variant,
r#"#[derive(Debug)] r#"
#[derive(Debug)]
#[derive(Clone)] #[derive(Clone)]
enum Enum { Variant{ field: u32$0 } }"#, enum Enum { Variant{ field: u32$0 } }"#,
r#"#[derive(Debug)]#[derive(Clone)] struct Variant{ field: u32 } r#"
#[derive(Debug)]
#[derive(Clone)]
struct Variant{ field: u32 }
#[derive(Debug)] #[derive(Debug)]
#[derive(Clone)] #[derive(Clone)]
@ -614,7 +635,7 @@ enum A { One(One) }"#,
} }
#[test] #[test]
fn test_extract_struct_keep_comments_and_attrs_on_variant_struct() { fn test_extract_struct_move_struct_variant_comments() {
check_assist( check_assist(
extract_struct_from_enum_variant, extract_struct_from_enum_variant,
r#" r#"
@ -631,19 +652,19 @@ enum A {
/* comment */ /* comment */
// other // other
/// comment /// comment
#[attr]
struct One{ struct One{
a: u32 a: u32
} }
enum A { enum A {
#[attr]
One(One) One(One)
}"#, }"#,
); );
} }
#[test] #[test]
fn test_extract_struct_keep_comments_and_attrs_on_variant_tuple() { fn test_extract_struct_move_tuple_variant_comments() {
check_assist( check_assist(
extract_struct_from_enum_variant, extract_struct_from_enum_variant,
r#" r#"
@ -658,10 +679,10 @@ enum A {
/* comment */ /* comment */
// other // other
/// comment /// comment
#[attr]
struct One(u32, u32); struct One(u32, u32);
enum A { enum A {
#[attr]
One(One) One(One)
}"#, }"#,
); );