mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-18 01:20:40 +00:00
Fix codeblock dynamic line length calculation for indented examples (#13523)
This commit is contained in:
parent
7706f561a9
commit
c046101b79
4 changed files with 1276 additions and 20 deletions
|
@ -44,3 +44,12 @@ pub(crate) fn is_empty_parameters_no_unnecessary_parentheses_around_return_value
|
|||
pub(crate) fn is_match_case_parentheses_enabled(context: &PyFormatContext) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
||||
/// This preview style fixes a bug with the docstring's `line-length` calculation when using the `dynamic` mode.
|
||||
/// The new style now respects the indent **inside** the docstring and reduces the `line-length` accordingly
|
||||
/// so that the docstring's code block fits into the global line-length setting.
|
||||
pub(crate) fn is_docstring_code_block_in_docstring_indent_enabled(
|
||||
context: &PyFormatContext,
|
||||
) -> bool {
|
||||
context.is_preview()
|
||||
}
|
||||
|
|
|
@ -18,11 +18,11 @@ use {
|
|||
ruff_text_size::{Ranged, TextLen, TextRange, TextSize},
|
||||
};
|
||||
|
||||
use super::NormalizedString;
|
||||
use crate::preview::is_docstring_code_block_in_docstring_indent_enabled;
|
||||
use crate::string::StringQuotes;
|
||||
use crate::{prelude::*, DocstringCodeLineWidth, FormatModuleError};
|
||||
|
||||
use super::NormalizedString;
|
||||
|
||||
/// Format a docstring by trimming whitespace and adjusting the indentation.
|
||||
///
|
||||
/// Summary of changes we make:
|
||||
|
@ -189,7 +189,7 @@ pub(crate) fn format(normalized: &NormalizedString, f: &mut PyFormatter) -> Form
|
|||
// We don't want to count whitespace-only lines as miss-indented
|
||||
.filter(|line| !line.trim().is_empty())
|
||||
.map(Indentation::from_str)
|
||||
.min_by_key(|indentation| indentation.width())
|
||||
.min_by_key(|indentation| indentation.columns())
|
||||
.unwrap_or_default();
|
||||
|
||||
DocstringLinePrinter {
|
||||
|
@ -353,7 +353,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
|||
};
|
||||
// This looks suspicious, but it's consistent with the whitespace
|
||||
// normalization that will occur anyway.
|
||||
let indent = " ".repeat(min_indent.width());
|
||||
let indent = " ".repeat(min_indent.columns());
|
||||
for docline in formatted_lines {
|
||||
self.print_one(
|
||||
&docline.map(|line| std::format!("{indent}{line}")),
|
||||
|
@ -363,7 +363,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
|||
CodeExampleKind::Markdown(fenced) => {
|
||||
// This looks suspicious, but it's consistent with the whitespace
|
||||
// normalization that will occur anyway.
|
||||
let indent = " ".repeat(fenced.opening_fence_indent.width());
|
||||
let indent = " ".repeat(fenced.opening_fence_indent.columns());
|
||||
for docline in formatted_lines {
|
||||
self.print_one(
|
||||
&docline.map(|line| std::format!("{indent}{line}")),
|
||||
|
@ -455,7 +455,7 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
|||
// (see example in [`format_docstring`] doc comment). We then
|
||||
// prepend the in-docstring indentation to the string.
|
||||
let indent_len =
|
||||
Indentation::from_str(trim_end).width() - self.stripped_indentation.width();
|
||||
Indentation::from_str(trim_end).columns() - self.stripped_indentation.columns();
|
||||
let in_docstring_indent = " ".repeat(indent_len) + trim_end.trim_start();
|
||||
text(&in_docstring_indent).fmt(self.f)?;
|
||||
};
|
||||
|
@ -500,11 +500,24 @@ impl<'ast, 'buf, 'fmt, 'src> DocstringLinePrinter<'ast, 'buf, 'fmt, 'src> {
|
|||
let global_line_width = self.f.options().line_width().value();
|
||||
let indent_width = self.f.options().indent_width();
|
||||
let indent_level = self.f.context().indent_level();
|
||||
let current_indent = indent_level
|
||||
let mut current_indent = indent_level
|
||||
.to_ascii_spaces(indent_width)
|
||||
.saturating_add(kind.extra_indent_ascii_spaces());
|
||||
|
||||
if is_docstring_code_block_in_docstring_indent_enabled(self.f.context()) {
|
||||
// Add the in-docstring indentation
|
||||
current_indent = current_indent.saturating_add(
|
||||
u16::try_from(
|
||||
kind.indent()
|
||||
.columns()
|
||||
.saturating_sub(self.stripped_indentation.columns()),
|
||||
)
|
||||
.unwrap_or(u16::MAX),
|
||||
);
|
||||
}
|
||||
|
||||
let width = std::cmp::max(1, global_line_width.saturating_sub(current_indent));
|
||||
LineWidth::try_from(width).expect("width is capped at a minimum of 1")
|
||||
LineWidth::try_from(width).expect("width should be capped at a minimum of 1")
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -828,6 +841,26 @@ impl<'src> CodeExampleKind<'src> {
|
|||
_ => 0,
|
||||
}
|
||||
}
|
||||
|
||||
/// The indent of the entire code block relative to the start of the line.
|
||||
///
|
||||
/// For example:
|
||||
/// ```python
|
||||
/// def test():
|
||||
/// """Docstring
|
||||
/// Example:
|
||||
/// >>> 1 + 1
|
||||
/// ```
|
||||
///
|
||||
/// The `>>> ` block has an indent of 8 columns: The shared indent with the docstring and the 4 spaces
|
||||
/// inside the docstring.
|
||||
fn indent(&self) -> Indentation {
|
||||
match self {
|
||||
CodeExampleKind::Doctest(doctest) => Indentation::from_str(doctest.ps1_indent),
|
||||
CodeExampleKind::Rst(rst) => rst.min_indent.unwrap_or(rst.opening_indent),
|
||||
CodeExampleKind::Markdown(markdown) => markdown.opening_fence_indent,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// State corresponding to a single doctest code example found in a docstring.
|
||||
|
@ -1663,7 +1696,7 @@ impl Indentation {
|
|||
/// to the next multiple of 8. This is effectively a port of
|
||||
/// [`str.expandtabs`](https://docs.python.org/3/library/stdtypes.html#str.expandtabs),
|
||||
/// which black [calls with the default tab width of 8](https://github.com/psf/black/blob/c36e468794f9256d5e922c399240d49782ba04f1/src/black/strings.py#L61).
|
||||
const fn width(self) -> usize {
|
||||
const fn columns(self) -> usize {
|
||||
match self {
|
||||
Self::Spaces(count) => count,
|
||||
Self::Tabs(count) => count * Self::TAB_INDENT_WIDTH,
|
||||
|
@ -1769,7 +1802,7 @@ impl Indentation {
|
|||
fn trim_start_str(self, line: &str) -> &str {
|
||||
let mut seen_indent_len = 0;
|
||||
let mut trimmed = line;
|
||||
let indent_len = self.width();
|
||||
let indent_len = self.columns();
|
||||
|
||||
for char in line.chars() {
|
||||
if seen_indent_len >= indent_len {
|
||||
|
@ -1797,13 +1830,13 @@ impl Indentation {
|
|||
|
||||
impl PartialOrd for Indentation {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
|
||||
Some(self.width().cmp(&other.width()))
|
||||
Some(self.columns().cmp(&other.columns()))
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Indentation {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.width() == other.width()
|
||||
self.columns() == other.columns()
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1843,10 +1876,10 @@ mod tests {
|
|||
use crate::string::docstring::Indentation;
|
||||
|
||||
#[test]
|
||||
fn test_indentation_like_black() {
|
||||
assert_eq!(Indentation::from_str("\t \t \t").width(), 24);
|
||||
assert_eq!(Indentation::from_str("\t \t").width(), 24);
|
||||
assert_eq!(Indentation::from_str("\t\t\t").width(), 24);
|
||||
assert_eq!(Indentation::from_str(" ").width(), 4);
|
||||
fn indentation_like_black() {
|
||||
assert_eq!(Indentation::from_str("\t \t \t").columns(), 24);
|
||||
assert_eq!(Indentation::from_str("\t \t").columns(), 24);
|
||||
assert_eq!(Indentation::from_str("\t\t\t").columns(), 24);
|
||||
assert_eq!(Indentation::from_str(" ").columns(), 4);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue