ide_assists/handlers/
generate_derive.rs

1use syntax::{
2    SyntaxKind::{ATTR, COMMENT, WHITESPACE},
3    T,
4    ast::{self, AstNode, HasAttrs, edit::IndentLevel, make},
5    syntax_editor::{Element, Position},
6};
7
8use crate::{AssistContext, AssistId, Assists};
9
10// Assist: generate_derive
11//
12// Adds a new `#[derive()]` clause to a struct or enum.
13//
14// ```
15// struct Point {
16//     x: u32,
17//     y: u32,$0
18// }
19// ```
20// ->
21// ```
22// #[derive($0)]
23// struct Point {
24//     x: u32,
25//     y: u32,
26// }
27// ```
28pub(crate) fn generate_derive(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
29    let cap = ctx.config.snippet_cap?;
30    let nominal = ctx.find_node_at_offset::<ast::Adt>()?;
31    let target = nominal.syntax().text_range();
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
39    let delimiter = match &derive_attr {
40        None => None,
41        Some(tt) => Some(tt.right_delimiter_token()?),
42    };
43
44    acc.add(AssistId::generate("generate_derive"), "Add `#[derive]`", target, |edit| {
45        match derive_attr {
46            None => {
47                let derive = make::attr_outer(make::meta_token_tree(
48                    make::ext::ident_path("derive"),
49                    make::token_tree(T!['('], vec![]).clone_for_update(),
50                ))
51                .clone_for_update();
52
53                let mut editor = edit.make_editor(nominal.syntax());
54                let indent = IndentLevel::from_node(nominal.syntax());
55                let after_attrs_and_comments = nominal
56                    .syntax()
57                    .children_with_tokens()
58                    .find(|it| !matches!(it.kind(), WHITESPACE | COMMENT | ATTR))
59                    .map_or(Position::first_child_of(nominal.syntax()), Position::before);
60                editor.insert_all(
61                    after_attrs_and_comments,
62                    vec![
63                        derive.syntax().syntax_element(),
64                        make::tokens::whitespace(&format!("\n{indent}")).syntax_element(),
65                    ],
66                );
67
68                let delimiter = derive
69                    .meta()
70                    .expect("make::attr_outer was expected to have Meta")
71                    .token_tree()
72                    .expect("failed to get token tree out of Meta")
73                    .r_paren_token()
74                    .expect("make::attr_outer was expected to have a R_PAREN");
75                let tabstop_before = edit.make_tabstop_before(cap);
76                editor.add_annotation(delimiter, tabstop_before);
77                edit.add_file_edits(ctx.vfs_file_id(), editor);
78            }
79            Some(_) => {
80                // Just move the cursor.
81                edit.add_tabstop_before_token(
82                    cap,
83                    delimiter.expect("Right delim token could not be found."),
84                );
85            }
86        };
87    })
88}
89
90#[cfg(test)]
91mod tests {
92    use crate::tests::{check_assist, check_assist_target};
93
94    use super::*;
95
96    #[test]
97    fn add_derive_new() {
98        check_assist(
99            generate_derive,
100            "struct Foo { a: i32, $0}",
101            "#[derive($0)]\nstruct Foo { a: i32, }",
102        );
103        check_assist(
104            generate_derive,
105            "struct Foo { $0 a: i32, }",
106            "#[derive($0)]\nstruct Foo {  a: i32, }",
107        );
108        check_assist(
109            generate_derive,
110            "
111mod m {
112    struct Foo { a: i32,$0 }
113}
114            ",
115            "
116mod m {
117    #[derive($0)]
118    struct Foo { a: i32, }
119}
120            ",
121        );
122    }
123
124    #[test]
125    fn add_derive_existing() {
126        check_assist(
127            generate_derive,
128            "#[derive(Clone)]\nstruct Foo { a: i32$0, }",
129            "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
130        );
131    }
132
133    #[test]
134    fn add_derive_existing_with_brackets() {
135        check_assist(
136            generate_derive,
137            "
138#[derive[Clone]]
139struct Foo { a: i32$0, }
140",
141            "
142#[derive[Clone$0]]
143struct Foo { a: i32, }
144",
145        );
146    }
147
148    #[test]
149    fn add_derive_existing_missing_delimiter() {
150        // since `#[derive]` isn't a simple attr call (i.e. `#[derive()]`)
151        // we don't consider it as a proper derive attr and generate a new
152        // one instead
153        check_assist(
154            generate_derive,
155            "
156#[derive]
157struct Foo { a: i32$0, }",
158            "
159#[derive]
160#[derive($0)]
161struct Foo { a: i32, }",
162        );
163    }
164
165    #[test]
166    fn add_derive_new_with_doc_comment() {
167        check_assist(
168            generate_derive,
169            "
170/// `Foo` is a pretty important struct.
171/// It does stuff.
172struct Foo { a: i32$0, }
173            ",
174            "
175/// `Foo` is a pretty important struct.
176/// It does stuff.
177#[derive($0)]
178struct Foo { a: i32, }
179            ",
180        );
181        check_assist(
182            generate_derive,
183            "
184mod m {
185    /// `Foo` is a pretty important struct.
186    /// It does stuff.
187    struct Foo { a: i32,$0 }
188}
189            ",
190            "
191mod m {
192    /// `Foo` is a pretty important struct.
193    /// It does stuff.
194    #[derive($0)]
195    struct Foo { a: i32, }
196}
197            ",
198        );
199    }
200
201    #[test]
202    fn add_derive_target() {
203        check_assist_target(
204            generate_derive,
205            "
206struct SomeThingIrrelevant;
207/// `Foo` is a pretty important struct.
208/// It does stuff.
209struct Foo { a: i32$0, }
210struct EvenMoreIrrelevant;
211            ",
212            "/// `Foo` is a pretty important struct.
213/// It does stuff.
214struct Foo { a: i32, }",
215        );
216    }
217}