315 lines
6.0 KiB
Rust
315 lines
6.0 KiB
Rust
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"###]"####,
|
|
);
|
|
}
|
|
}
|