Skip to content

Conversation

@danparizher
Copy link
Contributor

Summary

Fixes #19492

@github-actions
Copy link
Contributor

github-actions bot commented Sep 27, 2025

ruff-ecosystem results

Formatter (stable)

✅ ecosystem check detected no format changes.

Formatter (preview)

✅ ecosystem check detected no format changes.

@MichaReiser MichaReiser added the formatter Related to the formatter label Sep 29, 2025
Copy link
Member

@MichaReiser MichaReiser left a 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: on

The new line should be inserted for the case above

This might require integration into fmt_suite where we track the suppression state.

@danparizher
Copy link
Contributor Author

I've implemented proper suppression state tracking by using SuppressionKind—the behavior works correctly with real files—it adds trailing newlines for non-suppressed regions and preserves formatting for suppressed regions.

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.

Copy link
Member

@MichaReiser MichaReiser left a 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.

Copy link
Member

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

Comment on lines 49 to 53
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('#')
Copy link
Member

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 = 10

This is a module level fmt: off comment

But this isn't

if True:
	a = b
  # fmt: off

We should also support yapf: disable comments

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: off

I 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

Comment on lines 35 to 44
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;
}
}
}
Copy link
Member

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();
Copy link
Member

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")

Copy link
Member

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?

@danparizher
Copy link
Contributor Author

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!

Comment on lines +33 to +35
if source.ends_with(['\n', '\r']) || !has_unclosed_fmt_off(body, f.context()) {
hard_line_break().fmt(f)?;
}
Copy link
Member

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)

Suggested change
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

Copy link
Member

@MichaReiser MichaReiser left a 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

@danparizher
Copy link
Contributor Author

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.

@MichaReiser
Copy link
Member

MichaReiser commented Oct 6, 2025

I suspect that the statement formatting might insert a new line

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

formatter Related to the formatter

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Formatter inserts trailing newline at the end of a file even when inside a fmt: off region

2 participants