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