ide_assists/handlers/
desugar_doc_comment.rs1use either::Either;
2use itertools::Itertools;
3use syntax::{
4 AstToken, TextRange,
5 ast::{self, CommentPlacement, Whitespace, edit::IndentLevel},
6};
7
8use crate::{
9 AssistContext, AssistId, Assists,
10 handlers::convert_comment_block::{line_comment_text, relevant_line_comments},
11 utils::required_hashes,
12};
13
14pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
28 let comment = ctx.find_token_at_offset::<ast::Comment>()?;
29 let placement = comment.kind().doc?;
31
32 if let Some(prev) = comment.syntax().prev_token() {
34 Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?;
35 }
36
37 let indentation = IndentLevel::from_token(comment.syntax()).to_string();
38
39 let (target, comments) = match comment.kind().shape {
40 ast::CommentShape::Block => (comment.syntax().text_range(), Either::Left(comment)),
41 ast::CommentShape::Line => {
42 let comments = relevant_line_comments(&comment);
44
45 (
47 TextRange::new(
48 comments[0].syntax().text_range().start(),
49 comments.last()?.syntax().text_range().end(),
50 ),
51 Either::Right(comments),
52 )
53 }
54 };
55
56 acc.add(
57 AssistId::refactor_rewrite("desugar_doc_comment"),
58 "Desugar doc-comment to attribute macro",
59 target,
60 |edit| {
61 let text = match comments {
62 Either::Left(comment) => {
63 let text = comment.text();
64 text[comment.prefix().len()..(text.len() - "*/".len())]
65 .trim()
66 .lines()
67 .map(|l| l.strip_prefix(&indentation).unwrap_or(l))
68 .join("\n")
69 }
70 Either::Right(comments) => comments
71 .into_iter()
72 .map(|cm| line_comment_text(IndentLevel(0), cm))
73 .collect::<Vec<_>>()
74 .join("\n"),
75 };
76
77 let hashes = "#".repeat(required_hashes(&text));
78
79 let prefix = match placement {
80 CommentPlacement::Inner => "#!",
81 CommentPlacement::Outer => "#",
82 };
83
84 let output = format!(r#"{prefix}[doc = r{hashes}"{text}"{hashes}]"#);
85
86 edit.replace(target, output)
87 },
88 )
89}
90
91#[cfg(test)]
92mod tests {
93 use crate::tests::{check_assist, check_assist_not_applicable};
94
95 use super::*;
96
97 #[test]
98 fn single_line() {
99 check_assist(
100 desugar_doc_comment,
101 r#"
102/// line$0 comment
103fn main() {
104 foo();
105}
106"#,
107 r#"
108#[doc = r"line comment"]
109fn main() {
110 foo();
111}
112"#,
113 );
114 check_assist(
115 desugar_doc_comment,
116 r#"
117//! line$0 comment
118fn main() {
119 foo();
120}
121"#,
122 r#"
123#![doc = r"line comment"]
124fn main() {
125 foo();
126}
127"#,
128 );
129 }
130
131 #[test]
132 fn single_line_indented() {
133 check_assist(
134 desugar_doc_comment,
135 r#"
136fn main() {
137 /// line$0 comment
138 struct Foo;
139}
140"#,
141 r#"
142fn main() {
143 #[doc = r"line comment"]
144 struct Foo;
145}
146"#,
147 );
148 }
149
150 #[test]
151 fn multiline() {
152 check_assist(
153 desugar_doc_comment,
154 r#"
155fn main() {
156 /// above
157 /// line$0 comment
158 ///
159 /// below
160 struct Foo;
161}
162"#,
163 r#"
164fn main() {
165 #[doc = r"above
166line comment
167
168below"]
169 struct Foo;
170}
171"#,
172 );
173 }
174
175 #[test]
176 fn end_of_line() {
177 check_assist_not_applicable(
178 desugar_doc_comment,
179 r#"
180fn main() { /// end-of-line$0 comment
181 struct Foo;
182}
183"#,
184 );
185 }
186
187 #[test]
188 fn single_line_different_kinds() {
189 check_assist(
190 desugar_doc_comment,
191 r#"
192fn main() {
193 //! different prefix
194 /// line$0 comment
195 /// below
196 struct Foo;
197}
198"#,
199 r#"
200fn main() {
201 //! different prefix
202 #[doc = r"line comment
203below"]
204 struct Foo;
205}
206"#,
207 );
208 }
209
210 #[test]
211 fn single_line_separate_chunks() {
212 check_assist(
213 desugar_doc_comment,
214 r#"
215/// different chunk
216
217/// line$0 comment
218/// below
219"#,
220 r#"
221/// different chunk
222
223#[doc = r"line comment
224below"]
225"#,
226 );
227 }
228
229 #[test]
230 fn block_comment() {
231 check_assist(
232 desugar_doc_comment,
233 r#"
234/**
235 hi$0 there
236*/
237"#,
238 r#"
239#[doc = r"hi there"]
240"#,
241 );
242 }
243
244 #[test]
245 fn inner_doc_block() {
246 check_assist(
247 desugar_doc_comment,
248 r#"
249/*!
250 hi$0 there
251*/
252"#,
253 r#"
254#![doc = r"hi there"]
255"#,
256 );
257 }
258
259 #[test]
260 fn block_indent() {
261 check_assist(
262 desugar_doc_comment,
263 r#"
264fn main() {
265 /*!
266 hi$0 there
267
268 ```
269 code_sample
270 ```
271 */
272}
273"#,
274 r#"
275fn main() {
276 #![doc = r"hi there
277
278```
279 code_sample
280```"]
281}
282"#,
283 );
284 }
285
286 #[test]
287 fn end_of_line_block() {
288 check_assist_not_applicable(
289 desugar_doc_comment,
290 r#"
291fn main() {
292 foo(); /** end-of-line$0 comment */
293}
294"#,
295 );
296 }
297
298 #[test]
299 fn regular_comment() {
300 check_assist_not_applicable(desugar_doc_comment, r#"// some$0 comment"#);
301 check_assist_not_applicable(desugar_doc_comment, r#"/* some$0 comment*/"#);
302 }
303
304 #[test]
305 fn quotes_and_escapes() {
306 check_assist(
307 desugar_doc_comment,
308 r###"/// some$0 "\ "## comment"###,
309 r####"#[doc = r###"some "\ "## comment"###]"####,
310 );
311 }
312}