Skip to main content

ide_assists/handlers/
generate_derive.rs

1use syntax::{
2    SyntaxKind::{ATTR, COMMENT, WHITESPACE},
3    T,
4    ast::{self, AstNode, HasAttrs, edit::IndentLevel},
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 editor = edit.make_editor(nominal.syntax());
48                let make = editor.make();
49                let derive =
50                    make.attr_outer(make.meta_token_tree(
51                        make.ident_path("derive"),
52                        make.token_tree(T!['('], vec![]),
53                    ));
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
61                editor.insert_all(
62                    after_attrs_and_comments,
63                    vec![
64                        derive.syntax().syntax_element(),
65                        make.whitespace(&format!("\n{indent}")).syntax_element(),
66                    ],
67                );
68
69                let meta = derive.meta().expect("make::attr_outer was expected to have Meta");
70                let ast::Meta::TokenTreeMeta(meta) = meta else {
71                    unreachable!("make::attr_outer was passed a token tree meta");
72                };
73                let delimiter = meta
74                    .token_tree()
75                    .expect("failed to get token tree out of Meta")
76                    .r_paren_token()
77                    .expect("make::attr_outer was expected to have a R_PAREN");
78
79                let tabstop_before = edit.make_tabstop_before(cap);
80
81                editor.add_annotation(delimiter, tabstop_before);
82                edit.add_file_edits(ctx.vfs_file_id(), editor);
83            }
84            Some(_) => {
85                // Just move the cursor.
86                edit.add_tabstop_before_token(
87                    cap,
88                    delimiter.expect("Right delim token could not be found."),
89                );
90            }
91        };
92    })
93}
94
95#[cfg(test)]
96mod tests {
97    use crate::tests::{check_assist, check_assist_target};
98
99    use super::*;
100
101    #[test]
102    fn add_derive_new() {
103        check_assist(
104            generate_derive,
105            "struct Foo { a: i32, $0}",
106            "#[derive($0)]\nstruct Foo { a: i32, }",
107        );
108        check_assist(
109            generate_derive,
110            "struct Foo { $0 a: i32, }",
111            "#[derive($0)]\nstruct Foo {  a: i32, }",
112        );
113        check_assist(
114            generate_derive,
115            "
116mod m {
117    struct Foo { a: i32,$0 }
118}
119            ",
120            "
121mod m {
122    #[derive($0)]
123    struct Foo { a: i32, }
124}
125            ",
126        );
127    }
128
129    #[test]
130    fn add_derive_existing() {
131        check_assist(
132            generate_derive,
133            "#[derive(Clone)]\nstruct Foo { a: i32$0, }",
134            "#[derive(Clone$0)]\nstruct Foo { a: i32, }",
135        );
136    }
137
138    #[test]
139    fn add_derive_existing_with_brackets() {
140        check_assist(
141            generate_derive,
142            "
143#[derive[Clone]]
144struct Foo { a: i32$0, }
145",
146            "
147#[derive[Clone$0]]
148struct Foo { a: i32, }
149",
150        );
151    }
152
153    #[test]
154    fn add_derive_existing_missing_delimiter() {
155        // since `#[derive]` isn't a simple attr call (i.e. `#[derive()]`)
156        // we don't consider it as a proper derive attr and generate a new
157        // one instead
158        check_assist(
159            generate_derive,
160            "
161#[derive]
162struct Foo { a: i32$0, }",
163            "
164#[derive]
165#[derive($0)]
166struct Foo { a: i32, }",
167        );
168    }
169
170    #[test]
171    fn add_derive_new_with_doc_comment() {
172        check_assist(
173            generate_derive,
174            "
175/// `Foo` is a pretty important struct.
176/// It does stuff.
177struct Foo { a: i32$0, }
178            ",
179            "
180/// `Foo` is a pretty important struct.
181/// It does stuff.
182#[derive($0)]
183struct Foo { a: i32, }
184            ",
185        );
186        check_assist(
187            generate_derive,
188            "
189mod m {
190    /// `Foo` is a pretty important struct.
191    /// It does stuff.
192    struct Foo { a: i32,$0 }
193}
194            ",
195            "
196mod m {
197    /// `Foo` is a pretty important struct.
198    /// It does stuff.
199    #[derive($0)]
200    struct Foo { a: i32, }
201}
202            ",
203        );
204    }
205
206    #[test]
207    fn add_derive_target() {
208        check_assist_target(
209            generate_derive,
210            "
211struct SomeThingIrrelevant;
212/// `Foo` is a pretty important struct.
213/// It does stuff.
214struct Foo { a: i32$0, }
215struct EvenMoreIrrelevant;
216            ",
217            "/// `Foo` is a pretty important struct.
218/// It does stuff.
219struct Foo { a: i32, }",
220        );
221    }
222}