ide_assists/handlers/
generate_derive.rs1use 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
10pub(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 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 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}