Skip to content
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

Preserve trailing statement semicolons when using fmt: skip #8273

Merged
merged 1 commit into from
Oct 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
x = 1; # fmt: skip

x = 1 ; # fmt: skip

x = 1 \
; # fmt: skip

x = 1 # ; # fmt: skip

_; #unrelated semicolon
26 changes: 25 additions & 1 deletion crates/ruff_python_formatter/src/statement/mod.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
use ruff_formatter::{FormatOwnedWithRule, FormatRefWithRule};
use ruff_python_ast::Stmt;
use ruff_python_ast::{AnyNodeRef, Stmt};
use ruff_python_trivia::{SimpleToken, SimpleTokenKind, SimpleTokenizer};
use ruff_text_size::{Ranged, TextRange};

use crate::prelude::*;

Expand Down Expand Up @@ -81,3 +83,25 @@ impl<'ast> IntoFormat<PyFormatContext<'ast>> for Stmt {
FormatOwnedWithRule::new(self, FormatStmt)
}
}

/// Returns the range of the semicolon terminating the statement or `None` if the statement
/// isn't terminated by a semicolon.
pub(super) fn trailing_semicolon(node: AnyNodeRef, source: &str) -> Option<TextRange> {
debug_assert!(node.is_statement());

let tokenizer = SimpleTokenizer::starts_at(node.end(), source);

let next_token = tokenizer
.take_while(|token| !token.kind().is_comment())
.find(|token| !token.kind().is_trivia());

if let Some(SimpleToken {
kind: SimpleTokenKind::Semi,
range,
}) = next_token
{
Some(range)
} else {
None
}
}
12 changes: 11 additions & 1 deletion crates/ruff_python_formatter/src/verbatim.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::comments::{leading_comments, trailing_comments, SourceComment};
use crate::prelude::*;
use crate::statement::clause::ClauseHeader;
use crate::statement::suite::SuiteChildStatement;
use crate::statement::trailing_semicolon;

/// Disables formatting for all statements between the `first_suppressed` that has a leading `fmt: off` comment
/// and the first trailing or leading `fmt: on` comment. The statements are formatted as they appear in the source code.
Expand Down Expand Up @@ -902,14 +903,23 @@ impl Format<PyFormatContext<'_>> for FormatSuppressedNode<'_> {
}
}

// Some statements may end with a semicolon. Preserve the semicolon
let semicolon_range = self
.node
.is_statement()
.then(|| trailing_semicolon(self.node, f.context().source()))
.flatten();
let verbatim_range = semicolon_range.map_or(self.node.range(), |semicolon| {
TextRange::new(self.node.start(), semicolon.end())
});
comments.mark_verbatim_node_comments_formatted(self.node);

// Write the outer comments and format the node as verbatim
write!(
f,
[
leading_comments(node_comments.leading),
verbatim_text(self.node),
verbatim_text(verbatim_range),
trailing_comments(node_comments.trailing)
]
)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
source: crates/ruff_python_formatter/tests/fixtures.rs
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/fmt_skip/trailing_semi.py
---
## Input
```py
x = 1; # fmt: skip

x = 1 ; # fmt: skip

x = 1 \
; # fmt: skip

x = 1 # ; # fmt: skip

_; #unrelated semicolon
```

## Output
```py
x = 1; # fmt: skip

x = 1 ; # fmt: skip

x = 1 \
; # fmt: skip

x = 1 # ; # fmt: skip

_ # unrelated semicolon
```



6 changes: 5 additions & 1 deletion crates/ruff_python_trivia/src/tokenizer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -473,7 +473,7 @@ pub enum SimpleTokenKind {
}

impl SimpleTokenKind {
const fn is_trivia(self) -> bool {
pub const fn is_trivia(self) -> bool {
matches!(
self,
SimpleTokenKind::Whitespace
Expand All @@ -482,6 +482,10 @@ impl SimpleTokenKind {
| SimpleTokenKind::Continuation
)
}

pub const fn is_comment(self) -> bool {
matches!(self, SimpleTokenKind::Comment)
}
}

/// Simple zero allocation tokenizer handling most tokens.
Expand Down
Loading