]> git.lizzy.rs Git - rust.git/blob - crates/ra_assists/src/add_derive.rs
ra_assists: assist "providers" can produce multiple assists
[rust.git] / crates / ra_assists / src / add_derive.rs
1 use hir::db::HirDatabase;
2 use ra_syntax::{
3     ast::{self, AstNode, AttrsOwner},
4     SyntaxKind::{WHITESPACE, COMMENT},
5     TextUnit,
6 };
7
8 use crate::{AssistCtx, Assist};
9
10 pub(crate) fn add_derive(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
11     let nominal = ctx.node_at_offset::<ast::NominalDef>()?;
12     let node_start = derive_insertion_offset(nominal)?;
13     ctx.add_action("add `#[derive]`", |edit| {
14         let derive_attr = nominal
15             .attrs()
16             .filter_map(|x| x.as_call())
17             .filter(|(name, _arg)| name == "derive")
18             .map(|(_name, arg)| arg)
19             .next();
20         let offset = match derive_attr {
21             None => {
22                 edit.insert(node_start, "#[derive()]\n");
23                 node_start + TextUnit::of_str("#[derive(")
24             }
25             Some(tt) => tt.syntax().range().end() - TextUnit::of_char(')'),
26         };
27         edit.target(nominal.syntax().range());
28         edit.set_cursor(offset)
29     });
30
31     ctx.build()
32 }
33
34 // Insert `derive` after doc comments.
35 fn derive_insertion_offset(nominal: &ast::NominalDef) -> Option<TextUnit> {
36     let non_ws_child =
37         nominal.syntax().children().find(|it| it.kind() != COMMENT && it.kind() != WHITESPACE)?;
38     Some(non_ws_child.range().start())
39 }
40
41 #[cfg(test)]
42 mod tests {
43     use super::*;
44     use crate::helpers::{check_assist, check_assist_target};
45
46     #[test]
47     fn add_derive_new() {
48         check_assist(
49             add_derive,
50             "struct Foo { a: i32, <|>}",
51             "#[derive(<|>)]\nstruct Foo { a: i32, }",
52         );
53         check_assist(
54             add_derive,
55             "struct Foo { <|> a: i32, }",
56             "#[derive(<|>)]\nstruct Foo {  a: i32, }",
57         );
58     }
59
60     #[test]
61     fn add_derive_existing() {
62         check_assist(
63             add_derive,
64             "#[derive(Clone)]\nstruct Foo { a: i32<|>, }",
65             "#[derive(Clone<|>)]\nstruct Foo { a: i32, }",
66         );
67     }
68
69     #[test]
70     fn add_derive_new_with_doc_comment() {
71         check_assist(
72             add_derive,
73             "
74 /// `Foo` is a pretty important struct.
75 /// It does stuff.
76 struct Foo { a: i32<|>, }
77             ",
78             "
79 /// `Foo` is a pretty important struct.
80 /// It does stuff.
81 #[derive(<|>)]
82 struct Foo { a: i32, }
83             ",
84         );
85     }
86
87     #[test]
88     fn add_derive_target() {
89         check_assist_target(
90             add_derive,
91             "
92 struct SomeThingIrrelevant;
93 /// `Foo` is a pretty important struct.
94 /// It does stuff.
95 struct Foo { a: i32<|>, }
96 struct EvenMoreIrrelevant;
97             ",
98             "/// `Foo` is a pretty important struct.
99 /// It does stuff.
100 struct Foo { a: i32, }",
101         );
102     }
103 }