From 537b4c63053ade616dcf080830a4ccca97f77e02 Mon Sep 17 00:00:00 2001 From: Luuk Wester Date: Fri, 17 May 2024 21:54:35 +0200 Subject: [PATCH 1/4] implement assist to switch between doc and normal comments --- .../convert_comment_from_or_to_doc.rs | 645 ++++++++++++++++++ crates/ide-assists/src/lib.rs | 2 + crates/ide-assists/src/tests/generated.rs | 15 + 3 files changed, 662 insertions(+) create mode 100644 crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs diff --git a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs new file mode 100644 index 0000000000..1ac30d71f2 --- /dev/null +++ b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs @@ -0,0 +1,645 @@ +use itertools::Itertools; +use syntax::{ + ast::{self, edit::IndentLevel, Comment, CommentPlacement, Whitespace}, + AstToken, Direction, SyntaxElement, TextRange, +}; + +use crate::{AssistContext, AssistId, AssistKind, Assists}; + +// Assist: comment_to_doc +// +// Converts comments to documentation. +// +// ``` +// // Wow what $0a nice function +// // I sure hope this shows up when I hover over it +// ``` +// -> +// ``` +// //! Wow what a nice function +// //! I sure hope this shows up when I hover over it +// ``` +pub(crate) fn convert_comment_from_or_to_doc( + acc: &mut Assists, + ctx: &AssistContext<'_>, +) -> Option<()> { + let comment = ctx.find_token_at_offset::()?; + + match comment.kind().doc { + Some(_) => doc_to_comment(acc, comment), + None => match can_be_doc_comment(&comment) { + Some(doc_comment_style) => comment_to_doc(acc, comment, doc_comment_style), + None => None, + }, + } +} + +fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { + let target = if comment.kind().shape.is_line() { + line_comments_text_range(&comment)? + } else { + comment.syntax().text_range() + }; + + acc.add( + AssistId("doc_to_comment", AssistKind::RefactorRewrite), + "Replace a comment with doc comment", + target, + |edit| { + // We need to either replace the first occurrence of /* with /***, or we need to replace + // the occurrences // at the start of each line with /// + let output = match comment.kind().shape { + ast::CommentShape::Line => { + let indentation = IndentLevel::from_token(comment.syntax()); + let line_start = comment.prefix(); + relevant_line_comments(&comment) + .iter() + .map(|comment| comment.text()) + .flat_map(|text| text.lines()) + .map(|line| indentation.to_string() + &line.replacen(line_start, "//", 1)) + .join("\n") + } + ast::CommentShape::Block => { + let block_start = comment.prefix(); + comment + .text() + .lines() + .enumerate() + .map(|(idx, line)| { + if idx == 0 { + line.replacen(block_start, "/*", 1) + } else { + line.replacen("* ", "* ", 1) + } + }) + .join("\n") + } + }; + edit.replace(target, output) + }, + ) +} + +fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacement) -> Option<()> { + let target = if comment.kind().shape.is_line() { + line_comments_text_range(&comment)? + } else { + comment.syntax().text_range() + }; + + acc.add( + AssistId("comment_to_doc", AssistKind::RefactorRewrite), + "Replace a doc comment with comment", + target, + |edit| { + // We need to either replace the first occurrence of /* with /***, or we need to replace + // the occurrences // at the start of each line with /// + let output = match comment.kind().shape { + ast::CommentShape::Line => { + let line_start = match style { + CommentPlacement::Inner => "//!", + CommentPlacement::Outer => "///", + }; + let indentation = IndentLevel::from_token(comment.syntax()); + relevant_line_comments(&comment) + .iter() + .map(|comment| comment.text()) + .flat_map(|text| text.lines()) + .map(|line| indentation.to_string() + &line.replacen("//", line_start, 1)) + .join("\n") + } + ast::CommentShape::Block => { + let block_start = match style { + CommentPlacement::Inner => "/*!", + CommentPlacement::Outer => "/**", + }; + comment + .text() + .lines() + .enumerate() + .map(|(idx, line)| { + if idx == 0 { + // On the first line we replace the comment start with a doc comment + // start. + line.replacen("/*", block_start, 1) + } else { + // put one extra space after each * since we moved the first line to + // the right by one column as well. + line.replacen("* ", "* ", 1) + } + }) + .join("\n") + } + }; + edit.replace(target, output) + }, + ) +} + +/// Not all comments are valid candidates for conversion into doc comments. For example, the +/// comments in the code: +/// ```rust +/// // foos the bar +/// fn foo_bar(foo: Foo) -> Bar { +/// // Bar the foo +/// foo.into_bar() +/// } +/// +/// trait A { +/// // The A trait +/// } +/// ``` +/// can be converted to doc comments. However, the comments in this example: +/// ```rust +/// fn foo_bar(foo: Foo /* not bar yet */) -> Bar { +/// foo.into_bar() +/// // Nicely done +/// } +/// // end of function +/// +/// struct S { +/// // The S struct +/// } +/// ``` +/// are not allowed to become doc comments. +fn can_be_doc_comment(comment: &ast::Comment) -> Option { + use syntax::SyntaxKind::*; + + // if the comment is not on its own line, then we do not propose anything. + match comment.syntax().prev_token() { + Some(prev) => { + // There was a previous token, now check if it was a newline + Whitespace::cast(prev).filter(|w| w.text().contains('\n'))?; + } + // There is no previous token, this is the start of the file. + None => return Some(CommentPlacement::Inner), + } + + // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`, `use`, `const` + let parent = comment.syntax().parent(); + let parent_kind = parent.as_ref().map(|parent| parent.kind()); + if matches!( + parent_kind, + Some(STRUCT | TRAIT | MODULE | FN | TYPE_KW | EXTERN_CRATE | USE | CONST) + ) { + return Some(CommentPlacement::Outer); + } + + // check if comment is preceded by: `fn f() {`, `trait T {`, `mod M {`: + let third_parent_kind = comment + .syntax() + .parent() + .and_then(|p| p.parent()) + .and_then(|p| p.parent()) + .map(|parent| parent.kind()); + let is_first_item_in_parent = comment + .syntax() + .siblings_with_tokens(Direction::Prev) + .filter_map(|not| not.into_node()) + .next() + .is_none(); + + if matches!(parent_kind, Some(STMT_LIST)) + && is_first_item_in_parent + && matches!(third_parent_kind, Some(FN | TRAIT | MODULE)) + { + return Some(CommentPlacement::Inner); + } + + None +} + +/// 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. +pub(crate) fn relevant_line_comments(comment: &ast::Comment) -> Vec { + // The prefix identifies the kind of comment we're dealing with + let prefix = comment.prefix(); + let same_prefix = |c: &ast::Comment| c.prefix() == prefix; + + // These tokens are allowed to exist between comments + let skippable = |not: &SyntaxElement| { + not.clone() + .into_token() + .and_then(Whitespace::cast) + .map(|w| !w.spans_multiple_lines()) + .unwrap_or(false) + }; + + // Find all preceding comments (in reverse order) that have the same prefix + let prev_comments = comment + .syntax() + .siblings_with_tokens(Direction::Prev) + .filter(|s| !skippable(s)) + .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) + .take_while(|opt_com| opt_com.is_some()) + .flatten() + .skip(1); // skip the first element so we don't duplicate it in next_comments + + let next_comments = comment + .syntax() + .siblings_with_tokens(Direction::Next) + .filter(|s| !skippable(s)) + .map(|not| not.into_token().and_then(Comment::cast).filter(same_prefix)) + .take_while(|opt_com| opt_com.is_some()) + .flatten(); + + let mut comments: Vec<_> = prev_comments.collect(); + comments.reverse(); + comments.extend(next_comments); + comments +} + +fn line_comments_text_range(comment: &ast::Comment) -> Option { + let comments = relevant_line_comments(comment); + let first = comments.first()?; + let indentation = IndentLevel::from_token(first.syntax()); + let start = + first.syntax().text_range().start().checked_sub((indentation.0 as u32 * 4).into())?; + let end = comments.last()?.syntax().text_range().end(); + Some(TextRange::new(start, end)) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn module_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + // such a nice module$0 + fn main() { + foo(); + } + "#, + r#" + //! such a nice module + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn single_line_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + // unseen$0 docs + fn main() { + foo(); + } + "#, + r#" + + /// unseen docs + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn multi_line_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + // unseen$0 docs + // make me seen! + fn main() { + foo(); + } + "#, + r#" + + /// unseen docs + /// make me seen! + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn single_line_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + /// visible$0 docs + fn main() { + foo(); + } + "#, + r#" + + // visible docs + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn multi_line_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + /// visible$0 docs + /// Hide me! + fn main() { + foo(); + } + "#, + r#" + + // visible docs + // Hide me! + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn single_line_block_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + /* unseen$0 docs */ + fn main() { + foo(); + } + "#, + r#" + + /** unseen docs */ + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn multi_line_block_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + /* unseen$0 docs + * make me seen! + */ + fn main() { + foo(); + } + "#, + r#" + + /** unseen docs + * make me seen! + */ + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn single_line_block_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + /** visible$0 docs */ + fn main() { + foo(); + } + "#, + r#" + + /* visible docs */ + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn multi_line_block_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + + /** visible$0 docs + * Hide me! + */ + fn main() { + foo(); + } + "#, + r#" + + /* visible docs + * Hide me! + */ + fn main() { + foo(); + } + "#, + ); + } + + #[test] + fn single_inner_line_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + // unseen$0 docs + foo(); + } + "#, + r#" + fn main() { + //! unseen docs + foo(); + } + "#, + ); + } + + #[test] + fn multi_inner_line_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + // unseen$0 docs + // make me seen! + foo(); + } + "#, + r#" + fn main() { + //! unseen docs + //! make me seen! + foo(); + } + "#, + ); + } + + #[test] + fn single_inner_line_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + //! visible$0 docs + foo(); + } + "#, + r#" + fn main() { + // visible docs + foo(); + } + "#, + ); + } + + #[test] + fn multi_inner_line_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + //! visible$0 docs + //! Hide me! + foo(); + } + "#, + r#" + fn main() { + // visible docs + // Hide me! + foo(); + } + "#, + ); + } + + #[test] + fn single_inner_line_block_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + /* unseen$0 docs */ + foo(); + } + "#, + r#" + fn main() { + /*! unseen docs */ + foo(); + } + "#, + ); + } + + #[test] + fn multi_inner_line_block_comment_to_doc() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + /* unseen$0 docs + * make me seen! + */ + foo(); + } + "#, + r#" + fn main() { + /*! unseen docs + * make me seen! + */ + foo(); + } + "#, + ); + } + + #[test] + fn single_inner_line_block_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + /*! visible$0 docs */ + foo(); + } + "#, + r#" + fn main() { + /* visible docs */ + foo(); + } + "#, + ); + } + + #[test] + fn multi_inner_line_block_doc_to_comment() { + check_assist( + convert_comment_from_or_to_doc, + r#" + fn main() { + /*! visible$0 docs + * Hide me! + */ + foo(); + } + "#, + r#" + fn main() { + /* visible docs + * Hide me! + */ + foo(); + } + "#, + ); + } + + #[test] + fn not_overeager() { + check_assist_not_applicable( + convert_comment_from_or_to_doc, + r#" + fn main() { + foo(); + // $0well that settles main + } + // $1 nicely done + "#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 0df5e913a5..cbaf03e4d1 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -116,6 +116,7 @@ mod handlers { mod change_visibility; mod convert_bool_then; mod convert_comment_block; + mod convert_comment_from_or_to_doc; mod convert_from_to_tryfrom; mod convert_integer_literal; mod convert_into_to_from; @@ -239,6 +240,7 @@ mod handlers { convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, convert_comment_block::convert_comment_block, + convert_comment_from_or_to_doc::convert_comment_from_or_to_doc, convert_from_to_tryfrom::convert_from_to_tryfrom, convert_integer_literal::convert_integer_literal, convert_into_to_from::convert_into_to_from, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index 937e78f8d7..bd841d72e6 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -345,6 +345,21 @@ pub(crate) fn frobnicate() {} ) } +#[test] +fn doctest_comment_to_doc() { + check_doc_test( + "comment_to_doc", + r#####" +// Wow what $0a nice function +// I sure hope this shows up when I hover over it +"#####, + r#####" +//! Wow what a nice function +//! I sure hope this shows up when I hover over it +"#####, + ) +} + #[test] fn doctest_convert_bool_then_to_if() { check_doc_test( From 2400673ca6aa3d4066df416bc4831705068c6aa2 Mon Sep 17 00:00:00 2001 From: Luuk Wester Date: Fri, 24 May 2024 21:51:05 +0200 Subject: [PATCH 2/4] cosmetic and performance fixes, and drop support for adding //! comments anywhere, except for at the top of files. --- .../convert_comment_from_or_to_doc.rs | 189 +++++++----------- crates/ide-assists/src/tests/generated.rs | 4 +- 2 files changed, 79 insertions(+), 114 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs index 1ac30d71f2..2e73883447 100644 --- a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs +++ b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs @@ -11,12 +11,12 @@ use crate::{AssistContext, AssistId, AssistKind, Assists}; // Converts comments to documentation. // // ``` -// // Wow what $0a nice function +// // Wow what $0a nice module // // I sure hope this shows up when I hover over it // ``` // -> // ``` -// //! Wow what a nice function +// //! Wow what a nice module // //! I sure hope this shows up when I hover over it // ``` pub(crate) fn convert_comment_from_or_to_doc( @@ -43,7 +43,7 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { acc.add( AssistId("doc_to_comment", AssistKind::RefactorRewrite), - "Replace a comment with doc comment", + "Replace comment with doc comment", target, |edit| { // We need to either replace the first occurrence of /* with /***, or we need to replace @@ -52,11 +52,12 @@ fn doc_to_comment(acc: &mut Assists, comment: ast::Comment) -> Option<()> { ast::CommentShape::Line => { let indentation = IndentLevel::from_token(comment.syntax()); let line_start = comment.prefix(); + let prefix = format!("{indentation}//"); relevant_line_comments(&comment) .iter() .map(|comment| comment.text()) .flat_map(|text| text.lines()) - .map(|line| indentation.to_string() + &line.replacen(line_start, "//", 1)) + .map(|line| line.replacen(line_start, &prefix, 1)) .join("\n") } ast::CommentShape::Block => { @@ -89,23 +90,23 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem acc.add( AssistId("comment_to_doc", AssistKind::RefactorRewrite), - "Replace a doc comment with comment", + "Replace doc comment with comment", target, |edit| { // We need to either replace the first occurrence of /* with /***, or we need to replace // the occurrences // at the start of each line with /// let output = match comment.kind().shape { ast::CommentShape::Line => { - let line_start = match style { - CommentPlacement::Inner => "//!", - CommentPlacement::Outer => "///", - }; let indentation = IndentLevel::from_token(comment.syntax()); + let line_start = match style { + CommentPlacement::Inner => format!("{indentation}//!"), + CommentPlacement::Outer => format!("{indentation}///"), + }; relevant_line_comments(&comment) .iter() .map(|comment| comment.text()) .flat_map(|text| text.lines()) - .map(|line| indentation.to_string() + &line.replacen("//", line_start, 1)) + .map(|line| line.replacen("//", &line_start, 1)) .join("\n") } ast::CommentShape::Block => { @@ -139,21 +140,21 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem /// Not all comments are valid candidates for conversion into doc comments. For example, the /// comments in the code: /// ```rust -/// // foos the bar -/// fn foo_bar(foo: Foo) -> Bar { -/// // Bar the foo -/// foo.into_bar() +/// // Brilliant module right here +/// +/// // Really good right +/// fn good_function(foo: Foo) -> Bar { +/// foo.into_bar() /// } /// -/// trait A { -/// // The A trait -/// } +/// // So nice +/// mod nice_module {} /// ``` /// can be converted to doc comments. However, the comments in this example: /// ```rust /// fn foo_bar(foo: Foo /* not bar yet */) -> Bar { -/// foo.into_bar() -/// // Nicely done +/// foo.into_bar() +/// // Nicely done /// } /// // end of function /// @@ -161,7 +162,23 @@ fn comment_to_doc(acc: &mut Assists, comment: ast::Comment, style: CommentPlacem /// // The S struct /// } /// ``` -/// are not allowed to become doc comments. +/// are not allowed to become doc comments. Moreover, some comments _are_ allowed, but aren't common +/// style in Rust. For example, the following comments are allowed to be doc comments, but it is not +/// common style for them to be: +/// ```rust +/// fn foo_bar(foo: Foo) -> Bar { +/// // this could be an inner comment with //! +/// foo.into_bar() +/// } +/// +/// trait T { +/// // The T struct could also be documented from within +/// } +/// +/// mod mymod { +/// // Modules only normally get inner documentation when they are defined as a separate file. +/// } +/// ``` fn can_be_doc_comment(comment: &ast::Comment) -> Option { use syntax::SyntaxKind::*; @@ -175,38 +192,12 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option { None => return Some(CommentPlacement::Inner), } - // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`, `use`, `const` + // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`, + // `use` or `const`. let parent = comment.syntax().parent(); let parent_kind = parent.as_ref().map(|parent| parent.kind()); - if matches!( - parent_kind, - Some(STRUCT | TRAIT | MODULE | FN | TYPE_KW | EXTERN_CRATE | USE | CONST) - ) { - return Some(CommentPlacement::Outer); - } - - // check if comment is preceded by: `fn f() {`, `trait T {`, `mod M {`: - let third_parent_kind = comment - .syntax() - .parent() - .and_then(|p| p.parent()) - .and_then(|p| p.parent()) - .map(|parent| parent.kind()); - let is_first_item_in_parent = comment - .syntax() - .siblings_with_tokens(Direction::Prev) - .filter_map(|not| not.into_node()) - .next() - .is_none(); - - if matches!(parent_kind, Some(STMT_LIST)) - && is_first_item_in_parent - && matches!(third_parent_kind, Some(FN | TRAIT | MODULE)) - { - return Some(CommentPlacement::Inner); - } - - None + matches!(parent_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_KW | EXTERN_CRATE | USE | CONST)) + .then_some(CommentPlacement::Outer) } /// The line -> block assist can be invoked from anywhere within a sequence of line comments. @@ -467,39 +458,26 @@ mod tests { #[test] fn single_inner_line_comment_to_doc() { - check_assist( + check_assist_not_applicable( convert_comment_from_or_to_doc, r#" - fn main() { + mod mymod { // unseen$0 docs foo(); } "#, - r#" - fn main() { - //! unseen docs - foo(); - } - "#, ); } #[test] fn multi_inner_line_comment_to_doc() { - check_assist( + check_assist_not_applicable( convert_comment_from_or_to_doc, r#" - fn main() { + mod mymod { // unseen$0 docs // make me seen! - foo(); - } - "#, - r#" - fn main() { - //! unseen docs - //! make me seen! - foo(); + type Int = i32; } "#, ); @@ -510,13 +488,13 @@ mod tests { check_assist( convert_comment_from_or_to_doc, r#" - fn main() { + mod mymod { //! visible$0 docs foo(); } "#, r#" - fn main() { + mod mymod { // visible docs foo(); } @@ -529,58 +507,33 @@ mod tests { check_assist( convert_comment_from_or_to_doc, r#" - fn main() { + mod mymod { //! visible$0 docs //! Hide me! foo(); } "#, r#" - fn main() { + mod mymod { // visible docs // Hide me! foo(); } "#, ); - } - - #[test] - fn single_inner_line_block_comment_to_doc() { check_assist( convert_comment_from_or_to_doc, r#" - fn main() { - /* unseen$0 docs */ + mod mymod { + /// visible$0 docs + /// Hide me! foo(); } "#, r#" - fn main() { - /*! unseen docs */ - foo(); - } - "#, - ); - } - - #[test] - fn multi_inner_line_block_comment_to_doc() { - check_assist( - convert_comment_from_or_to_doc, - r#" - fn main() { - /* unseen$0 docs - * make me seen! - */ - foo(); - } - "#, - r#" - fn main() { - /*! unseen docs - * make me seen! - */ + mod mymod { + // visible docs + // Hide me! foo(); } "#, @@ -592,15 +545,15 @@ mod tests { check_assist( convert_comment_from_or_to_doc, r#" - fn main() { + mod mymod { /*! visible$0 docs */ - foo(); + type Int = i32; } "#, r#" - fn main() { + mod mymod { /* visible docs */ - foo(); + type Int = i32; } "#, ); @@ -611,21 +564,21 @@ mod tests { check_assist( convert_comment_from_or_to_doc, r#" - fn main() { + mod mymod { /*! visible$0 docs * Hide me! */ - foo(); + type Int = i32; } "#, r#" - fn main() { + mod mymod { /* visible docs * Hide me! */ - foo(); + type Int = i32; } - "#, + "#, ); } @@ -642,4 +595,16 @@ mod tests { "#, ); } + + #[test] + fn no_inner_comments() { + check_assist_not_applicable( + convert_comment_from_or_to_doc, + r#" + mod mymod { + // aaa$0aa + } + "#, + ); + } } diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index bd841d72e6..5ecce3cbb6 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -350,11 +350,11 @@ fn doctest_comment_to_doc() { check_doc_test( "comment_to_doc", r#####" -// Wow what $0a nice function +// Wow what $0a nice module // I sure hope this shows up when I hover over it "#####, r#####" -//! Wow what a nice function +//! Wow what a nice module //! I sure hope this shows up when I hover over it "#####, ) From f5d740aa3d56e1d36c6806f2cc1bf996eef05591 Mon Sep 17 00:00:00 2001 From: Luuk Wester Date: Fri, 24 May 2024 22:24:35 +0200 Subject: [PATCH 3/4] add test for every keyword, fix bug --- .../convert_comment_from_or_to_doc.rs | 110 +++++++++++++++--- 1 file changed, 94 insertions(+), 16 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs index 2e73883447..d714f63f11 100644 --- a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs +++ b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs @@ -195,8 +195,8 @@ fn can_be_doc_comment(comment: &ast::Comment) -> Option { // check if comment is followed by: `struct`, `trait`, `mod`, `fn`, `type`, `extern crate`, // `use` or `const`. let parent = comment.syntax().parent(); - let parent_kind = parent.as_ref().map(|parent| parent.kind()); - matches!(parent_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_KW | EXTERN_CRATE | USE | CONST)) + let par_kind = parent.as_ref().map(|parent| parent.kind()); + matches!(par_kind, Some(STRUCT | TRAIT | MODULE | FN | TYPE_ALIAS | EXTERN_CRATE | USE | CONST)) .then_some(CommentPlacement::Outer) } @@ -469,20 +469,6 @@ mod tests { ); } - #[test] - fn multi_inner_line_comment_to_doc() { - check_assist_not_applicable( - convert_comment_from_or_to_doc, - r#" - mod mymod { - // unseen$0 docs - // make me seen! - type Int = i32; - } - "#, - ); - } - #[test] fn single_inner_line_doc_to_comment() { check_assist( @@ -596,6 +582,98 @@ mod tests { ); } + #[test] + fn all_possible_items() { + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice struct$0 */ + struct S {} + }"#, + r#"mod m { + /** Nice struct */ + struct S {} + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice trait$0 */ + trait T {} + }"#, + r#"mod m { + /** Nice trait */ + trait T {} + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice module$0 */ + mod module {} + }"#, + r#"mod m { + /** Nice module */ + mod module {} + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice function$0 */ + fn function() {} + }"#, + r#"mod m { + /** Nice function */ + fn function() {} + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice type$0 */ + type Type Int = i32; + }"#, + r#"mod m { + /** Nice type */ + type Type Int = i32; + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice crate$0 */ + extern crate rust_analyzer; + }"#, + r#"mod m { + /** Nice crate */ + extern crate rust_analyzer; + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice import$0 */ + use ide_assists::convert_comment_from_or_to_doc::tests + }"#, + r#"mod m { + /** Nice import */ + use ide_assists::convert_comment_from_or_to_doc::tests + }"#, + ); + check_assist( + convert_comment_from_or_to_doc, + r#"mod m { + /* Nice constant$0 */ + const CONST: &str = "very const"; + }"#, + r#"mod m { + /** Nice constant */ + const CONST: &str = "very const"; + }"#, + ); + } + #[test] fn no_inner_comments() { check_assist_not_applicable( From 9e5ff0dce7ce55adcf478b6c091900bbf5380018 Mon Sep 17 00:00:00 2001 From: Luuk Wester Date: Fri, 24 May 2024 22:57:35 +0200 Subject: [PATCH 4/4] remove nested match with and_then --- .../src/handlers/convert_comment_from_or_to_doc.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs index d714f63f11..953119fd1f 100644 --- a/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs +++ b/crates/ide-assists/src/handlers/convert_comment_from_or_to_doc.rs @@ -27,10 +27,7 @@ pub(crate) fn convert_comment_from_or_to_doc( match comment.kind().doc { Some(_) => doc_to_comment(acc, comment), - None => match can_be_doc_comment(&comment) { - Some(doc_comment_style) => comment_to_doc(acc, comment, doc_comment_style), - None => None, - }, + None => can_be_doc_comment(&comment).and_then(|style| comment_to_doc(acc, comment, style)), } }