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

View file

@ -349,7 +349,7 @@ instruction()#comment with bad spacing
# yup
for element in collection.select_elements()
# right
@@ -140,34 +132,26 @@
@@ -140,28 +132,20 @@
# and round and round we go
# and round and round we go
@ -386,13 +386,6 @@ instruction()#comment with bad spacing
#######################
### SECTION COMMENT ###
#######################
-instruction() # comment with bad spacing
+instruction() #comment with bad spacing
# END COMMENTS
# MORE END COMMENTS
```
## Ruff Output
@ -551,7 +544,7 @@ class Test:
#######################
instruction() #comment with bad spacing
instruction() # comment with bad spacing
# END COMMENTS
# MORE END COMMENTS

View file

@ -1,53 +0,0 @@
---
source: crates/ruff_python_formatter/src/lib.rs
expression: snapshot
input_file: crates/ruff_python_formatter/resources/test/fixtures/black/simple_cases/comments8.py
---
## Input
```py
# The percent-percent comments are Spyder IDE cells.
# Both `#%%`` and `# %%` are accepted, so `black` standardises
# to the latter.
#%%
# %%
```
## Black Differences
```diff
--- Black
+++ Ruff
@@ -2,5 +2,5 @@
# Both `#%%`` and `# %%` are accepted, so `black` standardises
# to the latter.
-# %%
+#%%
# %%
```
## Ruff Output
```py
# The percent-percent comments are Spyder IDE cells.
# Both `#%%`` and `# %%` are accepted, so `black` standardises
# to the latter.
#%%
# %%
```
## Black Output
```py
# The percent-percent comments are Spyder IDE cells.
# Both `#%%`` and `# %%` are accepted, so `black` standardises
# to the latter.
# %%
# %%
```

View file

@ -42,23 +42,19 @@ def function(a:int=42):
+ # DEFAULT_TYPE_ATTRIBUTES,
)
-
-result = 1 # A simple comment
result = 1 # A simple comment
-result = (1,) # Another one
-
-result = 1 #  type: ignore
-result = 1 # This comment is talking about type: ignore
-square = Square(4) #  type: Optional[Square]
+result = ( 1, ) # Another one
result = 1 #  type: ignore
result = 1 # This comment is talking about type: ignore
square = Square(4) #  type: Optional[Square]
-
-
-def function(a: int = 42):
- """This docstring is already formatted
- a
- b
+result = 1 # A simple comment
+result = ( 1, ) # Another one
+result = 1 # type: ignore
+result = 1 # This comment is talking about type: ignore
+square = Square(4) # type: Optional[Square]
+def function(a:int=42):
+ """ This docstring is already formatted
+ a
@ -76,11 +72,11 @@ def function(a:int=42):
from .config import ( ConfigTypeAttributes, Int, Path, # String,
# DEFAULT_TYPE_ATTRIBUTES,
)
result = 1 # A simple comment
result = ( 1, ) # Another one
result = 1 # type: ignore
result = 1 # This comment is talking about type: ignore
square = Square(4) # type: Optional[Square]
result = 1 # A simple comment
result = ( 1, ) # Another one
result = 1 #  type: ignore
result = 1 # This comment is talking about type: ignore
square = Square(4) #  type: Optional[Square]
def function(a:int=42):
""" This docstring is already formatted
a

View file

@ -19,22 +19,19 @@ d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasu
+++ Ruff
@@ -1,4 +1,4 @@
-a = "this is some code"
-b = 5 # fmt:skip
-c = 9 # fmt: skip
-d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
+a = "this is some code"
+b = 5 #fmt:skip
+c = 9 #fmt: skip
+d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip
b = 5 # fmt:skip
c = 9 # fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
```
## Ruff Output
```py
a = "this is some code"
b = 5 #fmt:skip
c = 9 #fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" #fmt:skip
b = 5 # fmt:skip
c = 9 # fmt: skip
d = "thisisasuperlongstringthisisasuperlongstringthisisasuperlongstringthisisasuperlongstring" # fmt:skip
```
## Black Output