summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs')
-rw-r--r--src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs312
1 files changed, 312 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
new file mode 100644
index 000000000..226a5dd9f
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/desugar_doc_comment.rs
@@ -0,0 +1,312 @@
+use either::Either;
+use itertools::Itertools;
+use syntax::{
+ ast::{self, edit::IndentLevel, CommentPlacement, Whitespace},
+ AstToken, TextRange,
+};
+
+use crate::{
+ handlers::convert_comment_block::{line_comment_text, relevant_line_comments},
+ utils::required_hashes,
+ AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: desugar_doc_comment
+//
+// Desugars doc-comments to the attribute form.
+//
+// ```
+// /// Multi-line$0
+// /// comment
+// ```
+// ->
+// ```
+// #[doc = r"Multi-line
+// comment"]
+// ```
+pub(crate) fn desugar_doc_comment(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let comment = ctx.find_token_at_offset::<ast::Comment>()?;
+ // Only allow doc comments
+ let Some(placement) = comment.kind().doc else { return None; };
+
+ // Only allow comments which are alone on their line
+ if let Some(prev) = comment.syntax().prev_token() {
+ if Whitespace::cast(prev).filter(|w| w.text().contains('\n')).is_none() {
+ return None;
+ }
+ }
+
+ let indentation = IndentLevel::from_token(comment.syntax()).to_string();
+
+ let (target, comments) = match comment.kind().shape {
+ ast::CommentShape::Block => (comment.syntax().text_range(), Either::Left(comment)),
+ ast::CommentShape::Line => {
+ // Find all the comments we'll be desugaring
+ let comments = relevant_line_comments(&comment);
+
+ // Establish the target of our edit based on the comments we found
+ (
+ TextRange::new(
+ comments[0].syntax().text_range().start(),
+ comments.last().unwrap().syntax().text_range().end(),
+ ),
+ Either::Right(comments),
+ )
+ }
+ };
+
+ acc.add(
+ AssistId("desugar_doc_comment", AssistKind::RefactorRewrite),
+ "Desugar doc-comment to attribute macro",
+ target,
+ |edit| {
+ let text = match comments {
+ Either::Left(comment) => {
+ let text = comment.text();
+ text[comment.prefix().len()..(text.len() - "*/".len())]
+ .trim()
+ .lines()
+ .map(|l| l.strip_prefix(&indentation).unwrap_or(l))
+ .join("\n")
+ }
+ Either::Right(comments) => {
+ comments.into_iter().map(|c| line_comment_text(IndentLevel(0), c)).join("\n")
+ }
+ };
+
+ let hashes = "#".repeat(required_hashes(&text));
+
+ let prefix = match placement {
+ CommentPlacement::Inner => "#!",
+ CommentPlacement::Outer => "#",
+ };
+
+ let output = format!(r#"{prefix}[doc = r{hashes}"{text}"{hashes}]"#);
+
+ edit.replace(target, output)
+ },
+ )
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn single_line() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+/// line$0 comment
+fn main() {
+ foo();
+}
+"#,
+ r#"
+#[doc = r"line comment"]
+fn main() {
+ foo();
+}
+"#,
+ );
+ check_assist(
+ desugar_doc_comment,
+ r#"
+//! line$0 comment
+fn main() {
+ foo();
+}
+"#,
+ r#"
+#![doc = r"line comment"]
+fn main() {
+ foo();
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_line_indented() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+fn main() {
+ /// line$0 comment
+ struct Foo;
+}
+"#,
+ r#"
+fn main() {
+ #[doc = r"line comment"]
+ struct Foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiline() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+fn main() {
+ /// above
+ /// line$0 comment
+ ///
+ /// below
+ struct Foo;
+}
+"#,
+ r#"
+fn main() {
+ #[doc = r"above
+line comment
+
+below"]
+ struct Foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn end_of_line() {
+ check_assist_not_applicable(
+ desugar_doc_comment,
+ r#"
+fn main() { /// end-of-line$0 comment
+ struct Foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_line_different_kinds() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+fn main() {
+ //! different prefix
+ /// line$0 comment
+ /// below
+ struct Foo;
+}
+"#,
+ r#"
+fn main() {
+ //! different prefix
+ #[doc = r"line comment
+below"]
+ struct Foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn single_line_separate_chunks() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+/// different chunk
+
+/// line$0 comment
+/// below
+"#,
+ r#"
+/// different chunk
+
+#[doc = r"line comment
+below"]
+"#,
+ );
+ }
+
+ #[test]
+ fn block_comment() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+/**
+ hi$0 there
+*/
+"#,
+ r#"
+#[doc = r"hi there"]
+"#,
+ );
+ }
+
+ #[test]
+ fn inner_doc_block() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+/*!
+ hi$0 there
+*/
+"#,
+ r#"
+#![doc = r"hi there"]
+"#,
+ );
+ }
+
+ #[test]
+ fn block_indent() {
+ check_assist(
+ desugar_doc_comment,
+ r#"
+fn main() {
+ /*!
+ hi$0 there
+
+ ```
+ code_sample
+ ```
+ */
+}
+"#,
+ r#"
+fn main() {
+ #![doc = r"hi there
+
+```
+ code_sample
+```"]
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn end_of_line_block() {
+ check_assist_not_applicable(
+ desugar_doc_comment,
+ r#"
+fn main() {
+ foo(); /** end-of-line$0 comment */
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn regular_comment() {
+ check_assist_not_applicable(desugar_doc_comment, r#"// some$0 comment"#);
+ check_assist_not_applicable(desugar_doc_comment, r#"/* some$0 comment*/"#);
+ }
+
+ #[test]
+ fn quotes_and_escapes() {
+ check_assist(
+ desugar_doc_comment,
+ r###"/// some$0 "\ "## comment"###,
+ r####"#[doc = r###"some "\ "## comment"###]"####,
+ );
+ }
+}