mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:24:57 +00:00
[ruff
] Preserve relative whitespace in multi-line expressions (RUF033
) (#19647)
## Summary Fixes #19581 I decided to add in a `indent_first_line` function into [`textwrap.rs`](https://github.com/astral-sh/ruff/blob/main/crates/ruff_python_trivia/src/textwrap.rs), as it solely focuses on text manipulation utilities. It follows the same design as `indent()`, and there may be situations in the future where it can be reused as well. --------- Co-authored-by: Brent Westbrook <36778786+ntBre@users.noreply.github.com> Co-authored-by: Brent Westbrook <brentrwestbrook@gmail.com>
This commit is contained in:
parent
4b80f5fa4f
commit
89ca493fd9
4 changed files with 186 additions and 1 deletions
|
@ -124,3 +124,19 @@ def fun_with_python_syntax():
|
||||||
...
|
...
|
||||||
|
|
||||||
return Foo
|
return Foo
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class C:
|
||||||
|
def __post_init__(self, x: tuple[int, ...] = (
|
||||||
|
1,
|
||||||
|
2,
|
||||||
|
)) -> None:
|
||||||
|
self.x = x
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class D:
|
||||||
|
def __post_init__(self, x: int = """
|
||||||
|
""") -> None:
|
||||||
|
self.x = x
|
||||||
|
|
|
@ -186,7 +186,7 @@ fn use_initvar(
|
||||||
|
|
||||||
let indentation = indentation_at_offset(post_init_def.start(), checker.source())
|
let indentation = indentation_at_offset(post_init_def.start(), checker.source())
|
||||||
.context("Failed to calculate leading indentation of `__post_init__` method")?;
|
.context("Failed to calculate leading indentation of `__post_init__` method")?;
|
||||||
let content = textwrap::indent(&content, indentation);
|
let content = textwrap::indent_first_line(&content, indentation);
|
||||||
|
|
||||||
let initvar_edit = Edit::insertion(
|
let initvar_edit = Edit::insertion(
|
||||||
content.into_owned(),
|
content.into_owned(),
|
||||||
|
|
|
@ -455,3 +455,57 @@ help: Use `dataclasses.InitVar` instead
|
||||||
122 123 | ,
|
122 123 | ,
|
||||||
123 124 | ) -> None:
|
123 124 | ) -> None:
|
||||||
124 125 | ...
|
124 125 | ...
|
||||||
|
|
||||||
|
RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
--> RUF033.py:131:50
|
||||||
|
|
|
||||||
|
129 | @dataclass
|
||||||
|
130 | class C:
|
||||||
|
131 | def __post_init__(self, x: tuple[int, ...] = (
|
||||||
|
| __________________________________________________^
|
||||||
|
132 | | 1,
|
||||||
|
133 | | 2,
|
||||||
|
134 | | )) -> None:
|
||||||
|
| |_____^
|
||||||
|
135 | self.x = x
|
||||||
|
|
|
||||||
|
help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
128 128 |
|
||||||
|
129 129 | @dataclass
|
||||||
|
130 130 | class C:
|
||||||
|
131 |- def __post_init__(self, x: tuple[int, ...] = (
|
||||||
|
131 |+ x: InitVar[tuple[int, ...]] = (
|
||||||
|
132 132 | 1,
|
||||||
|
133 133 | 2,
|
||||||
|
134 |- )) -> None:
|
||||||
|
134 |+ )
|
||||||
|
135 |+ def __post_init__(self, x: tuple[int, ...]) -> None:
|
||||||
|
135 136 | self.x = x
|
||||||
|
136 137 |
|
||||||
|
137 138 |
|
||||||
|
|
||||||
|
RUF033 [*] `__post_init__` method with argument defaults
|
||||||
|
--> RUF033.py:140:38
|
||||||
|
|
|
||||||
|
138 | @dataclass
|
||||||
|
139 | class D:
|
||||||
|
140 | def __post_init__(self, x: int = """
|
||||||
|
| ______________________________________^
|
||||||
|
141 | | """) -> None:
|
||||||
|
| |_______^
|
||||||
|
142 | self.x = x
|
||||||
|
|
|
||||||
|
help: Use `dataclasses.InitVar` instead
|
||||||
|
|
||||||
|
ℹ Unsafe fix
|
||||||
|
137 137 |
|
||||||
|
138 138 | @dataclass
|
||||||
|
139 139 | class D:
|
||||||
|
140 |- def __post_init__(self, x: int = """
|
||||||
|
141 |- """) -> None:
|
||||||
|
140 |+ x: InitVar[int] = """
|
||||||
|
141 |+ """
|
||||||
|
142 |+ def __post_init__(self, x: int) -> None:
|
||||||
|
142 143 | self.x = x
|
||||||
|
|
|
@ -71,6 +71,66 @@ pub fn indent<'a>(text: &'a str, prefix: &str) -> Cow<'a, str> {
|
||||||
Cow::Owned(result)
|
Cow::Owned(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Indent only the first line by the given prefix.
|
||||||
|
///
|
||||||
|
/// This function is useful when you want to indent the first line of a multi-line
|
||||||
|
/// expression while preserving the relative indentation of subsequent lines.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use ruff_python_trivia::textwrap::indent_first_line;
|
||||||
|
///
|
||||||
|
/// assert_eq!(indent_first_line("First line.\nSecond line.\n", " "),
|
||||||
|
/// " First line.\nSecond line.\n");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// When indenting, trailing whitespace is stripped from the prefix.
|
||||||
|
/// This means that empty lines remain empty afterwards:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use ruff_python_trivia::textwrap::indent_first_line;
|
||||||
|
///
|
||||||
|
/// assert_eq!(indent_first_line("\n\n\nSecond line.\n", " "),
|
||||||
|
/// "\n\n\nSecond line.\n");
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Leading and trailing whitespace coming from the text itself is
|
||||||
|
/// kept unchanged:
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use ruff_python_trivia::textwrap::indent_first_line;
|
||||||
|
///
|
||||||
|
/// assert_eq!(indent_first_line(" \t Foo ", "->"), "-> \t Foo ");
|
||||||
|
/// ```
|
||||||
|
pub fn indent_first_line<'a>(text: &'a str, prefix: &str) -> Cow<'a, str> {
|
||||||
|
if prefix.is_empty() {
|
||||||
|
return Cow::Borrowed(text);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut lines = text.universal_newlines();
|
||||||
|
let Some(first_line) = lines.next() else {
|
||||||
|
return Cow::Borrowed(text);
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut result = String::with_capacity(text.len() + prefix.len());
|
||||||
|
|
||||||
|
// Indent only the first line
|
||||||
|
if first_line.trim_whitespace().is_empty() {
|
||||||
|
result.push_str(prefix.trim_whitespace_end());
|
||||||
|
} else {
|
||||||
|
result.push_str(prefix);
|
||||||
|
}
|
||||||
|
result.push_str(first_line.as_full_str());
|
||||||
|
|
||||||
|
// Add remaining lines without indentation
|
||||||
|
for line in lines {
|
||||||
|
result.push_str(line.as_full_str());
|
||||||
|
}
|
||||||
|
|
||||||
|
Cow::Owned(result)
|
||||||
|
}
|
||||||
|
|
||||||
/// Removes common leading whitespace from each line.
|
/// Removes common leading whitespace from each line.
|
||||||
///
|
///
|
||||||
/// This function will look at each non-empty line and determine the
|
/// This function will look at each non-empty line and determine the
|
||||||
|
@ -409,6 +469,61 @@ mod tests {
|
||||||
assert_eq!(dedent(text), text);
|
assert_eq!(dedent(text), text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn indent_first_line_empty() {
|
||||||
|
assert_eq!(indent_first_line("\n", " "), "\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn indent_first_line_nonempty() {
|
||||||
|
let text = [
|
||||||
|
" foo\n",
|
||||||
|
"bar\n",
|
||||||
|
" baz\n",
|
||||||
|
].join("");
|
||||||
|
let expected = [
|
||||||
|
"// foo\n",
|
||||||
|
"bar\n",
|
||||||
|
" baz\n",
|
||||||
|
].join("");
|
||||||
|
assert_eq!(indent_first_line(&text, "// "), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn indent_first_line_empty_line() {
|
||||||
|
let text = [
|
||||||
|
" foo",
|
||||||
|
"bar",
|
||||||
|
"",
|
||||||
|
" baz",
|
||||||
|
].join("\n");
|
||||||
|
let expected = [
|
||||||
|
"// foo",
|
||||||
|
"bar",
|
||||||
|
"",
|
||||||
|
" baz",
|
||||||
|
].join("\n");
|
||||||
|
assert_eq!(indent_first_line(&text, "// "), expected);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[rustfmt::skip]
|
||||||
|
fn indent_first_line_mixed_newlines() {
|
||||||
|
let text = [
|
||||||
|
" foo\r\n",
|
||||||
|
"bar\n",
|
||||||
|
" baz\r",
|
||||||
|
].join("");
|
||||||
|
let expected = [
|
||||||
|
"// foo\r\n",
|
||||||
|
"bar\n",
|
||||||
|
" baz\r",
|
||||||
|
].join("");
|
||||||
|
assert_eq!(indent_first_line(&text, "// "), expected);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
#[rustfmt::skip]
|
#[rustfmt::skip]
|
||||||
fn adjust_indent() {
|
fn adjust_indent() {
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue