mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-28 04:45:01 +00:00
Format the comment content (#4786)
This commit is contained in:
parent
602b4b3519
commit
4cd4b37e74
5 changed files with 88 additions and 94 deletions
|
@ -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
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue