Format the comment content (#4786)

This commit is contained in:
Micha Reiser 2023-06-02 13:22:34 +02:00 committed by GitHub
parent 602b4b3519
commit 4cd4b37e74
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 88 additions and 94 deletions

View file

@ -1,9 +1,11 @@
use crate::comments::SourceComment;
use crate::context::NodeLevel;
use crate::prelude::*;
use crate::trivia::{lines_after, lines_before};
use ruff_formatter::{format_args, write};
use ruff_formatter::{format_args, write, FormatError, SourceCode};
use ruff_python_ast::node::AnyNodeRef;
use ruff_python_ast::prelude::AstNode;
use ruff_text_size::{TextLen, TextRange, TextSize};
/// Formats the leading comments of a node.
pub(crate) fn leading_comments<T>(node: &T) -> FormatLeadingComments
@ -30,10 +32,7 @@ impl Format<PyFormatContext<'_>> for FormatLeadingComments<'_> {
let lines_after_comment = lines_after(f.context().contents(), slice.end());
write!(
f,
[
source_text_slice(slice.range(), ContainsNewlines::No),
empty_lines(lines_after_comment)
]
[format_comment(comment), empty_lines(lines_after_comment)]
)?;
comment.mark_formatted();
@ -64,7 +63,6 @@ impl Format<PyFormatContext<'_>> for FormatTrailingComments<'_> {
for trailing in comments.trailing_comments(self.node) {
let slice = trailing.slice();
let content = source_text_slice(slice.range(), ContainsNewlines::No);
let lines_before_comment = lines_before(f.context().contents(), slice.start());
has_empty_lines_before |= lines_before_comment > 0;
@ -81,7 +79,10 @@ impl Format<PyFormatContext<'_>> for FormatTrailingComments<'_> {
f,
[
line_suffix(&format_with(|f| {
write!(f, [empty_lines(lines_before_comment), content])
write!(
f,
[empty_lines(lines_before_comment), format_comment(trailing)]
)
})),
expand_parent()
]
@ -90,7 +91,7 @@ impl Format<PyFormatContext<'_>> for FormatTrailingComments<'_> {
write!(
f,
[
line_suffix(&format_args![space(), space(), content]),
line_suffix(&format_args![space(), space(), format_comment(trailing)]),
expand_parent()
]
)?;
@ -132,7 +133,7 @@ impl Format<PyFormatContext<'_>> for FormatDanglingComments<'_> {
write!(
f,
[
source_text_slice(comment.slice().range(), ContainsNewlines::No),
format_comment(comment),
empty_lines(lines_after(f.context().contents(), comment.slice().end()))
]
)?;
@ -146,6 +147,66 @@ impl Format<PyFormatContext<'_>> for FormatDanglingComments<'_> {
}
}
/// Formats the content of the passed comment.
///
/// * Adds a whitespace between `#` and the comment text except if the first character is a `#`, `:`, `'`, or `!`
/// * Replaces non breaking whitespaces with regular whitespaces except if in front of a `types:` comment
const fn format_comment(comment: &SourceComment) -> FormatComment {
FormatComment { comment }
}
struct FormatComment<'a> {
comment: &'a SourceComment,
}
impl Format<PyFormatContext<'_>> for FormatComment<'_> {
fn fmt(&self, f: &mut Formatter<PyFormatContext<'_>>) -> FormatResult<()> {
let slice = self.comment.slice();
let comment_text = slice.text(SourceCode::new(f.context().contents()));
let trimmed = comment_text.trim_end();
let trailing_whitespace_len = comment_text.text_len() - trimmed.text_len();
let Some(content) = trimmed.strip_prefix('#') else {
return Err(FormatError::SyntaxError);
};
// Fast path for correctly formatted comments:
// * Start with a `#` and are followed by a space
// * Have no trailing whitespace.
if trailing_whitespace_len == TextSize::new(0) && content.starts_with(' ') {
return source_text_slice(slice.range(), ContainsNewlines::No).fmt(f);
}
write!(f, [source_position(slice.start()), text("#")])?;
// Starts with a non breaking space
let start_offset =
if content.starts_with('\u{A0}') && !content.trim_start().starts_with("type:") {
// Replace non-breaking space with a space (if not followed by a normal space)
"#\u{A0}".text_len()
} else {
'#'.text_len()
};
// Add a space between the `#` and the text if the source contains none.
if !content.is_empty() && !content.starts_with([' ', '!', ':', '#', '\'']) {
write!(f, [space()])?;
}
let start = slice.start() + start_offset;
let end = slice.range().end() - trailing_whitespace_len;
write!(
f,
[
source_text_slice(TextRange::new(start, end), ContainsNewlines::No),
source_position(slice.end())
]
)
}
}
// Helper that inserts the appropriate number of empty lines before a comment, depending on the node level.
// Top level: Up to two empty lines
// parenthesized: A single empty line