-
Notifications
You must be signed in to change notification settings - Fork 1.6k
[formatter] Fix trailing newline insertion in fmt: off regions
#20604
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
|
MichaReiser
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thank you.
This will need some tests. Testing whether the source contains any #fmt: off is also not strict enough. E.g.
# fmt: off
a = 10
# fmt: onThe new line should be inserted for the case above
This might require integration into fmt_suite where we track the suppression state.
|
I've implemented proper suppression state tracking by using What I couldn't figure out was that the test snapshot for Test Case 3 isn't showing the expected trailing newline, even though the actual formatter behavior is correct. Wondering if this is an issue with the fixture or intended behavior. |
MichaReiser
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks better, but I think is still incorrect for:
def test():
# fmt: off
...This should only disable formatting inside the test function but not at the module level.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You'll need to create different test files or you only test whatever fmt: off pattern comes last
| let line_start = source.line_start(comment_range.start()); | ||
| let before_comment = | ||
| &source[TextRange::new(line_start, comment_range.start())]; | ||
| before_comment.trim_start().is_empty() | ||
| || before_comment.trim_start().starts_with('#') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunatelly, it's more complicated than this:
a = b
# fmt: off
b = 10This is a module level fmt: off comment
But this isn't
if True:
a = b
# fmt: offWe should also support yapf: disable comments
ruff/crates/ruff_python_formatter/src/comments/mod.rs
Lines 169 to 171 in 32be31d
| pub(crate) fn is_suppression_off_comment(&self, text: &str) -> bool { | |
| SuppressionKind::is_suppression_off(self.text(text), self.line_position) | |
| } |
We should also add a test for
# fmt: offI think the right approach is to iterate over all statements and, for each statement, get its comments (using context.comments.leading_trailing(statement)). Then test if the last comment is a fmt: off comment
| let mut module_suppressed_at_eof = false; | ||
| for statement in body { | ||
| for comment in comments.leading_trailing(statement) { | ||
| if comment.is_suppression_off_comment(source) { | ||
| module_suppressed_at_eof = true; | ||
| } else if comment.is_suppression_on_comment(source) { | ||
| module_suppressed_at_eof = false; | ||
| } | ||
| } | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We should avoid doing this check if the source ends with a \n.
| hard_line_break() | ||
| ] | ||
| ) | ||
| let comments = f.context().comments(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I suggest restructuring the entire code to
body.format.with_options(SuiteKind::TopLevel).fmt(f)?;
if source.ends_width('\n') || !has_unclosed_fmt_off(f.context()) {
hard_line_break().fmt(f)?;
}| # Support for yapf: disable at module level | ||
| # yapf: disable | ||
| print("yapf") | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is it intentional that this test case and module_level_trailing_off have a trailing newline?
|
I refactored the modules and extracted a helper. Feel free to commit directly if there's still some missing logic; I know we've been going back and forth for a bit on this one! |
| if source.ends_with(['\n', '\r']) || !has_unclosed_fmt_off(body, f.context()) { | ||
| hard_line_break().fmt(f)?; | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, I'm not sure what's happening. You have to do some debugging but I noticed that none of the tests are failing when I change this to (when I revert your changes)
| if source.ends_with(['\n', '\r']) || !has_unclosed_fmt_off(body, f.context()) { | |
| hard_line_break().fmt(f)?; | |
| } | |
| hard_line_break().fmt(f)?; | |
It seems we're still always inserting a trailing newline
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks. I updated the fixture files to remove all trailing new lines to ensure they test the right thing. I also "reverted" your changes locally to verify that the tests start failing, which they don't. I'm not sure why. Can you take a look.
We should also add a test to verify that we preserve the correct number of blank lines for
test
# fmt: off
# many blank lines follow
<eof>or
# empty file with many blank lines
# fmt: off
<eof>Hmm... it seems that Github truncates the trailing blank lines. So I added a marker to prevent this
|
I've spent some time debugging but wasn't able to make much headway. Not too sure what's going on either; I've tried multiple iterations of the same solution. Might take another look later. |
|
I suspect that the statement formatting might insert a new line |
Summary
Fixes #19492