mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-19 18:10:30 +00:00
Fix formatter instability for lines only consisting of zero-width characters (#11748)
This commit is contained in:
parent
b0b4706e2d
commit
5806bc915d
6 changed files with 49 additions and 16 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -2215,7 +2215,6 @@ dependencies = [
|
||||||
"static_assertions",
|
"static_assertions",
|
||||||
"thiserror",
|
"thiserror",
|
||||||
"tracing",
|
"tracing",
|
||||||
"unicode-width",
|
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
|
|
@ -124,7 +124,7 @@ impl<'a> Printer<'a> {
|
||||||
self.flush_line_suffixes(queue, stack, Some(element));
|
self.flush_line_suffixes(queue, stack, Some(element));
|
||||||
} else {
|
} else {
|
||||||
// Only print a newline if the current line isn't already empty
|
// Only print a newline if the current line isn't already empty
|
||||||
if self.state.line_width > 0 {
|
if !self.state.buffer[self.state.line_start..].is_empty() {
|
||||||
self.push_marker();
|
self.push_marker();
|
||||||
self.print_char('\n');
|
self.print_char('\n');
|
||||||
}
|
}
|
||||||
|
@ -830,6 +830,7 @@ impl<'a> Printer<'a> {
|
||||||
.push_str(self.options.line_ending.as_str());
|
.push_str(self.options.line_ending.as_str());
|
||||||
|
|
||||||
self.state.line_width = 0;
|
self.state.line_width = 0;
|
||||||
|
self.state.line_start = self.state.buffer.len();
|
||||||
|
|
||||||
// Fit's only tests if groups up to the first line break fit.
|
// Fit's only tests if groups up to the first line break fit.
|
||||||
// The next group must re-measure if it still fits.
|
// The next group must re-measure if it still fits.
|
||||||
|
@ -872,12 +873,29 @@ enum FillPairLayout {
|
||||||
/// position the printer currently is.
|
/// position the printer currently is.
|
||||||
#[derive(Default, Debug)]
|
#[derive(Default, Debug)]
|
||||||
struct PrinterState<'a> {
|
struct PrinterState<'a> {
|
||||||
|
/// The formatted output.
|
||||||
buffer: String,
|
buffer: String,
|
||||||
|
|
||||||
|
/// The source markers that map source positions to formatted positions.
|
||||||
source_markers: Vec<SourceMarker>,
|
source_markers: Vec<SourceMarker>,
|
||||||
|
|
||||||
|
/// The next source position that should be flushed when writing the next text.
|
||||||
pending_source_position: Option<TextSize>,
|
pending_source_position: Option<TextSize>,
|
||||||
|
|
||||||
|
/// The current indentation that should be written before the next text.
|
||||||
pending_indent: Indention,
|
pending_indent: Indention,
|
||||||
|
|
||||||
|
/// Caches if the code up to the next newline has been measured to fit on a single line.
|
||||||
|
/// This is used to avoid re-measuring the same content multiple times.
|
||||||
measured_group_fits: bool,
|
measured_group_fits: bool,
|
||||||
|
|
||||||
|
/// The offset at which the current line in `buffer` starts.
|
||||||
|
line_start: usize,
|
||||||
|
|
||||||
|
/// The accumulated unicode-width of all characters on the current line.
|
||||||
line_width: u32,
|
line_width: u32,
|
||||||
|
|
||||||
|
/// The line suffixes that should be printed at the end of the line.
|
||||||
line_suffixes: LineSuffixes<'a>,
|
line_suffixes: LineSuffixes<'a>,
|
||||||
verbatim_markers: Vec<TextRange>,
|
verbatim_markers: Vec<TextRange>,
|
||||||
group_modes: GroupModes,
|
group_modes: GroupModes,
|
||||||
|
|
|
@ -37,7 +37,6 @@ smallvec = { workspace = true }
|
||||||
static_assertions = { workspace = true }
|
static_assertions = { workspace = true }
|
||||||
thiserror = { workspace = true }
|
thiserror = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
unicode-width = { workspace = true }
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
ruff_formatter = { workspace = true }
|
ruff_formatter = { workspace = true }
|
||||||
|
|
|
@ -0,0 +1,6 @@
|
||||||
|
# Regresssion test for https://github.com/astral-sh/ruff/issues/11724
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
|
@ -2,8 +2,6 @@ use std::borrow::Cow;
|
||||||
use std::iter::FusedIterator;
|
use std::iter::FusedIterator;
|
||||||
use std::slice::Iter;
|
use std::slice::Iter;
|
||||||
|
|
||||||
use unicode_width::UnicodeWidthStr;
|
|
||||||
|
|
||||||
use ruff_formatter::{write, FormatError};
|
use ruff_formatter::{write, FormatError};
|
||||||
use ruff_python_ast::AnyNodeRef;
|
use ruff_python_ast::AnyNodeRef;
|
||||||
use ruff_python_ast::Stmt;
|
use ruff_python_ast::Stmt;
|
||||||
|
@ -760,19 +758,9 @@ impl Format<PyFormatContext<'_>> for FormatVerbatimStatementRange {
|
||||||
|
|
||||||
// Write the line separator that terminates the line, except if it is the last line (that isn't separated by a hard line break).
|
// Write the line separator that terminates the line, except if it is the last line (that isn't separated by a hard line break).
|
||||||
if logical_line.has_trailing_newline {
|
if logical_line.has_trailing_newline {
|
||||||
// Insert an empty line if the text is non-empty but all characters have a width of zero.
|
|
||||||
// This is necessary to work around the fact that the Printer omits hard line breaks if the line width is 0.
|
|
||||||
// The alternative is to "fix" the printer and explicitly track the width and whether the line is empty.
|
|
||||||
// There's currently no use case for zero-width content outside of the verbatim context (and, form feeds are a Python specific speciality).
|
|
||||||
// It, therefore, feels wrong to add additional complexity to the very hot `Printer::print_char` function,
|
|
||||||
// to work around this special case. Therefore, work around the Printer behavior here, in the cold verbatim-formatting.
|
|
||||||
if f.context().source()[trimmed_line_range].width() == 0 {
|
|
||||||
empty_line().fmt(f)?;
|
|
||||||
} else {
|
|
||||||
hard_line_break().fmt(f)?;
|
hard_line_break().fmt(f)?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
first = false;
|
first = false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,23 @@
|
||||||
|
---
|
||||||
|
source: crates/ruff_python_formatter/tests/fixtures.rs
|
||||||
|
input_file: crates/ruff_python_formatter/resources/test/fixtures/ruff/docstring_non_visible_characters.py
|
||||||
|
---
|
||||||
|
## Input
|
||||||
|
```python
|
||||||
|
# Regresssion test for https://github.com/astral-sh/ruff/issues/11724
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
'''
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
```python
|
||||||
|
# Regresssion test for https://github.com/astral-sh/ruff/issues/11724
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
"""
|
||||||
|
```
|
Loading…
Add table
Add a link
Reference in a new issue