mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-12-28 14:03:35 +00:00
Auto merge of #13935 - ModProg:assist_desugar_doc_comment, r=Veykril
Assist: desugar doc-comment My need for this arose due to wanting to do feature dependent documentation and therefor convert parts of my doc-comments to attributes. Not sure about the pub-making of the other handlers functions, but I didn't think it made much sense to reimplement them.
This commit is contained in:
commit
455ef0c806
6 changed files with 353 additions and 24 deletions
|
@ -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);
|
||||
|
||||
|
|
312
crates/ide-assists/src/handlers/desugar_doc_comment.rs
Normal file
312
crates/ide-assists/src/handlers/desugar_doc_comment.rs
Normal file
|
@ -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"###]"####,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -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,33 +155,12 @@ 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};
|
||||
|
||||
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(
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -597,6 +597,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(
|
||||
|
|
|
@ -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"));
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue