]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/handlers/add_derive.rs
2a6bb1caedfea40827e3c1b10dc644bae79a044e
[rust.git] / crates / ra_assists / src / handlers / add_derive.rs
1 use ra_syntax::{
2     ast::{self, AstNode, AttrsOwner},
3     SyntaxKind::{COMMENT, WHITESPACE},
4     TextSize,
5 };
6
7 use crate::{Assist, AssistCtx, AssistId};
8
9 // Assist: add_derive
10 //
11 // Adds a new `#[derive()]` clause to a struct or enum.
12 //
13 // ```
14 // struct Point {
15 //     x: u32,
16 //     y: u32,<|>
17 // }
18 // ```
19 // ->
20 // ```
21 // #[derive()]
22 // struct Point {
23 //     x: u32,
24 //     y: u32,
25 // }
26 // ```
27 pub(crate) fn add_derive(ctx: AssistCtx) -> Option<Assist> {
28     let nominal = ctx.find_node_at_offset::<ast::NominalDef>()?;
29     let node_start = derive_insertion_offset(&nominal)?;
30     let target = nominal.syntax().text_range();
31     ctx.add_assist(AssistId("add_derive"), "Add `#[derive]`", target, |edit| {
32         let derive_attr = nominal
33             .attrs()
34             .filter_map(|x| x.as_simple_call())
35             .filter(|(name, _arg)| name == "derive")
36             .map(|(_name, arg)| arg)
37             .next();
38         let offset = match derive_attr {
39             None => {
40                 edit.insert(node_start, "#[derive()]\n");
41                 node_start + TextSize::of("#[derive(")
42             }
43             Some(tt) => tt.syntax().text_range().end() - TextSize::of(')'),
44         };
45         edit.set_cursor(offset)
46     })
47 }
48
49 // Insert `derive` after doc comments.
50 fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextSize> {
51     let non_ws_child = nominal
52         .syntax()
53         .children_with_tokens()
54         .find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
55     Some(non_ws_child.text_range().start())
56 }
57
58 #[cfg(test)]
59 mod tests {
60     use super::*;
61     use crate::tests::{check_assist, check_assist_target};
62
63     #[test]
64     fn add_derive_new() {
65         check_assist(
66             add_derive,
67             "struct Foo { a: i32, <|>}",
68             "#[derive(<|>)]\nstruct Foo { a: i32, }",
69         );
70         check_assist(
71             add_derive,
72             "struct Foo { <|> a: i32, }",
73             "#[derive(<|>)]\nstruct Foo {  a: i32, }",
74         );
75     }
76
77     #[test]
78     fn add_derive_existing() {
79         check_assist(
80             add_derive,
81             "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
82             "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
83         );
84     }
85
86     #[test]
87     fn add_derive_new_with_doc_comment() {
88         check_assist(
89             add_derive,
90             "
91 /// `Foo` is a pretty important struct.
92 /// It does stuff.
93 struct Foo { a: i32<|>, }
94             ",
95             "
96 /// `Foo` is a pretty important struct.
97 /// It does stuff.
98 #[derive(<|>)]
99 struct Foo { a: i32, }
100             ",
101         );
102     }
103
104     #[test]
105     fn add_derive_target() {
106         check_assist_target(
107             add_derive,
108             "
109 struct SomeThingIrrelevant;
110 /// `Foo` is a pretty important struct.
111 /// It does stuff.
112 struct Foo { a: i32<|>, }
113 struct EvenMoreIrrelevant;
114             ",
115             "/// `Foo` is a pretty important struct.
116 /// It does stuff.
117 struct Foo { a: i32, }",
118         );
119     }
120 }