Unnamed repository; edit this file 'description' to name the repository.
-rw-r--r--crates/ide-assists/src/handlers/convert_comment_block.rs4
-rw-r--r--crates/ide-assists/src/handlers/desugar_doc_comment.rs312
-rw-r--r--crates/ide-assists/src/handlers/raw_string.rs23
-rw-r--r--crates/ide-assists/src/lib.rs2
-rw-r--r--crates/ide-assists/src/tests/generated.rs15
-rw-r--r--crates/ide-assists/src/utils.rs21
6 files changed, 353 insertions, 24 deletions
diff --git a/crates/ide-assists/src/handlers/convert_comment_block.rs b/crates/ide-assists/src/handlers/convert_comment_block.rs
index 312cb65abd..1acd5ee972 100644
--- a/crates/ide-assists/src/handlers/convert_comment_block.rs
+++ b/crates/ide-assists/src/handlers/convert_comment_block.rs
@@ -107,7 +107,7 @@ fn line_to_block(acc: &mut Assists, comment: ast::Comment) -> Option<()> {
/// The line -> block assist can be invoked from anywhere within a sequence of line comments.
/// relevant_line_comments crawls backwards and forwards finding the complete sequence of comments that will
/// be joined.
-fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
+pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
// The prefix identifies the kind of comment we're dealing with
let prefix = comment.prefix();
let same_prefix = |c: &ast::Comment| c.prefix() == prefix;
@@ -159,7 +159,7 @@ fn relevant_line_comments(comment: &ast::Comment) -> Vec<Comment> {
// */
//
// But since such comments aren't idiomatic we're okay with this.
-fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
+pub(crate) fn line_comment_text(indentation: IndentLevel, comm: ast::Comment) -> String {
let contents_without_prefix = comm.text().strip_prefix(comm.prefix()).unwrap();
let contents = contents_without_prefix.strip_prefix(' ').unwrap_or(contents_without_prefix);
diff --git a/crates/ide-assists/src/handlers/desugar_doc_comment.rs b/crates/ide-assists/src/handlers/desugar_doc_comment.rs
new file mode 100644
index 0000000000..226a5dd9fa
--- /dev/null
+++ b/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"###]"####,
+ );
+ }
+}
diff --git a/crates/ide-assists/src/handlers/raw_string.rs b/crates/ide-assists/src/handlers/raw_string.rs
index c9bc25b27a..01420430bb 100644
--- a/crates/ide-assists/src/handlers/raw_string.rs
+++ b/crates/ide-assists/src/handlers/raw_string.rs
@@ -2,7 +2,7 @@ use std::borrow::Cow;
use syntax::{ast, ast::IsString, AstToken, TextRange, TextSize};
-use crate::{AssistContext, AssistId, AssistKind, Assists};
+use crate::{utils::required_hashes, AssistContext, AssistId, AssistKind, Assists};
// Assist: make_raw_string
//
@@ -155,16 +155,6 @@ pub(crate) fn remove_hash(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<
})
}
-fn required_hashes(s: &str) -> usize {
- let mut res = 0usize;
- for idx in s.match_indices('"').map(|(i, _)| i) {
- let (_, sub) = s.split_at(idx + 1);
- let n_hashes = sub.chars().take_while(|c| *c == '#').count();
- res = res.max(n_hashes + 1)
- }
- res
-}
-
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
@@ -172,17 +162,6 @@ mod tests {
use super::*;
#[test]
- fn test_required_hashes() {
- assert_eq!(0, required_hashes("abc"));
- assert_eq!(0, required_hashes("###"));
- assert_eq!(1, required_hashes("\""));
- assert_eq!(2, required_hashes("\"#abc"));
- assert_eq!(0, required_hashes("#abc"));
- assert_eq!(3, required_hashes("#ab\"##c"));
- assert_eq!(5, required_hashes("#ab\"##\"####c"));
- }
-
- #[test]
fn make_raw_string_target() {
check_assist_target(
make_raw_string,
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index 7813c9f9cb..546ef96260 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -126,6 +126,7 @@ mod handlers {
mod convert_to_guarded_return;
mod convert_two_arm_bool_match_to_matches_macro;
mod convert_while_to_loop;
+ mod desugar_doc_comment;
mod destructure_tuple_binding;
mod expand_glob_import;
mod extract_expressions_from_format_string;
@@ -231,6 +232,7 @@ mod handlers {
convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct,
convert_two_arm_bool_match_to_matches_macro::convert_two_arm_bool_match_to_matches_macro,
convert_while_to_loop::convert_while_to_loop,
+ desugar_doc_comment::desugar_doc_comment,
destructure_tuple_binding::destructure_tuple_binding,
expand_glob_import::expand_glob_import,
extract_expressions_from_format_string::extract_expressions_from_format_string,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 006ae4b303..16a06b60de 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -598,6 +598,21 @@ fn main() {
}
#[test]
+fn doctest_desugar_doc_comment() {
+ check_doc_test(
+ "desugar_doc_comment",
+ r#####"
+/// Multi-line$0
+/// comment
+"#####,
+ r#####"
+#[doc = r"Multi-line
+comment"]
+"#####,
+ )
+}
+
+#[test]
fn doctest_expand_glob_import() {
check_doc_test(
"expand_glob_import",
diff --git a/crates/ide-assists/src/utils.rs b/crates/ide-assists/src/utils.rs
index 7add660649..63f467bd30 100644
--- a/crates/ide-assists/src/utils.rs
+++ b/crates/ide-assists/src/utils.rs
@@ -758,3 +758,24 @@ pub(crate) fn convert_param_list_to_arg_list(list: ast::ParamList) -> ast::ArgLi
}
make::arg_list(args)
}
+
+/// Calculate the number of hashes required for a raw string containing `s`
+pub(crate) fn required_hashes(s: &str) -> usize {
+ let mut res = 0usize;
+ for idx in s.match_indices('"').map(|(i, _)| i) {
+ let (_, sub) = s.split_at(idx + 1);
+ let n_hashes = sub.chars().take_while(|c| *c == '#').count();
+ res = res.max(n_hashes + 1)
+ }
+ res
+}
+#[test]
+fn test_required_hashes() {
+ assert_eq!(0, required_hashes("abc"));
+ assert_eq!(0, required_hashes("###"));
+ assert_eq!(1, required_hashes("\""));
+ assert_eq!(2, required_hashes("\"#abc"));
+ assert_eq!(0, required_hashes("#abc"));
+ assert_eq!(3, required_hashes("#ab\"##c"));
+ assert_eq!(5, required_hashes("#ab\"##\"####c"));
+}