diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E20.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E20.py index ada032e6dc..4d54b546e8 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E20.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E20.py @@ -189,3 +189,18 @@ f"{ham[lower + 1 :, "columnname"]}" #: Okay: https://github.com/astral-sh/ruff/issues/12023 f"{x = :.2f}" f"{(x) = :.2f}" + +# t-strings +t"{ {'a': 1} }" +t"{[ { {'a': 1} } ]}" +t"normal { {t"{ { [1, 2] } }" } } normal" + +t"{x = :.2f}" +t"{(x) = :.2f}" + +#: Okay +t"{ham[lower +1 :, "columnname"]}" + +#: E203:1:13 +t"{ham[lower + 1 :, "columnname"]}" + diff --git a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E23.py b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E23.py index b4eddee8b4..3d12987c34 100644 --- a/crates/ruff_linter/resources/test/fixtures/pycodestyle/E23.py +++ b/crates/ruff_linter/resources/test/fixtures/pycodestyle/E23.py @@ -142,3 +142,20 @@ class PEP696GoodWithEmptyBases[A: object="foo"[::-1], B: object =[[["foo", "bar" class PEP696GoodWithNonEmptyBases[A: object="foo"[::-1], B: object =[[["foo", "bar"]]], C: object= bytes](object, something_dynamic[x::-1]): pass + +# E231 +t"{(a,b)}" + +# Okay because it's hard to differentiate between the usages of a colon in a t-string +t"{a:=1}" +t"{ {'a':1} }" +t"{a:.3f}" +t"{(a:=1)}" +t"{(lambda x:x)}" +t"normal{t"{a:.3f}"}normal" + +#: Okay +snapshot.file_uri[len(t's3://{self.s3_bucket_name}/'):] + +#: E231 +{len(t's3://{self.s3_bucket_name}/'):1} diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs index 2044af0ba5..1419525033 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/extraneous_whitespace.rs @@ -126,7 +126,7 @@ impl AlwaysFixableViolation for WhitespaceBeforePunctuation { /// E201, E202, E203 pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { - let mut fstrings = 0u32; + let mut interpolated_strings = 0u32; let mut brackets = vec![]; let mut prev_token = None; let mut iter = line.tokens().iter().peekable(); @@ -134,8 +134,10 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { while let Some(token) = iter.next() { let kind = token.kind(); match kind { - TokenKind::FStringStart => fstrings += 1, - TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1), + TokenKind::FStringStart | TokenKind::TStringStart => interpolated_strings += 1, + TokenKind::FStringEnd | TokenKind::TStringEnd => { + interpolated_strings = interpolated_strings.saturating_sub(1); + } TokenKind::Lsqb => { brackets.push(kind); } @@ -161,7 +163,9 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { // Here, `{{` / `}} would be interpreted as a single raw `{` / `}` // character. match symbol { - BracketOrPunctuation::OpenBracket(symbol) if symbol != '{' || fstrings == 0 => { + BracketOrPunctuation::OpenBracket(symbol) + if symbol != '{' || interpolated_strings == 0 => + { let (trailing, trailing_len) = line.trailing_whitespace(token); if !matches!(trailing, Whitespace::None) { if let Some(mut diagnostic) = context.report_diagnostic_if_enabled( @@ -173,7 +177,9 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { } } } - BracketOrPunctuation::CloseBracket(symbol) if symbol != '}' || fstrings == 0 => { + BracketOrPunctuation::CloseBracket(symbol) + if symbol != '}' || interpolated_strings == 0 => + { if !matches!(prev_token, Some(TokenKind::Comma)) { if let (Whitespace::Single | Whitespace::Many | Whitespace::Tab, offset) = line.leading_whitespace(token) @@ -286,7 +292,7 @@ pub(crate) fn extraneous_whitespace(line: &LogicalLine, context: &LintContext) { } } } else { - if fstrings > 0 + if interpolated_strings > 0 && symbol == ':' && matches!(prev_token, Some(TokenKind::Equal)) { diff --git a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs index 19d5d7ca01..7b9e82c4f7 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs +++ b/crates/ruff_linter/src/rules/pycodestyle/rules/logical_lines/missing_whitespace.rs @@ -41,7 +41,7 @@ impl AlwaysFixableViolation for MissingWhitespace { /// E231 pub(crate) fn missing_whitespace(line: &LogicalLine, context: &LintContext) { - let mut fstrings = 0u32; + let mut interpolated_strings = 0u32; let mut definition_state = DefinitionState::from_tokens(line.tokens()); let mut brackets = Vec::new(); let mut iter = line.tokens().iter().peekable(); @@ -50,21 +50,23 @@ pub(crate) fn missing_whitespace(line: &LogicalLine, context: &LintContext) { let kind = token.kind(); definition_state.visit_token_kind(kind); match kind { - TokenKind::FStringStart => fstrings += 1, - TokenKind::FStringEnd => fstrings = fstrings.saturating_sub(1), - TokenKind::Lsqb if fstrings == 0 => { + TokenKind::FStringStart | TokenKind::TStringStart => interpolated_strings += 1, + TokenKind::FStringEnd | TokenKind::TStringEnd => { + interpolated_strings = interpolated_strings.saturating_sub(1); + } + TokenKind::Lsqb if interpolated_strings == 0 => { brackets.push(kind); } - TokenKind::Rsqb if fstrings == 0 => { + TokenKind::Rsqb if interpolated_strings == 0 => { brackets.pop(); } - TokenKind::Lbrace if fstrings == 0 => { + TokenKind::Lbrace if interpolated_strings == 0 => { brackets.push(kind); } - TokenKind::Rbrace if fstrings == 0 => { + TokenKind::Rbrace if interpolated_strings == 0 => { brackets.pop(); } - TokenKind::Colon if fstrings > 0 => { + TokenKind::Colon if interpolated_strings > 0 => { // Colon in f-string, no space required. This will yield false // negatives for cases like the following as it's hard to // differentiate between the usage of a colon in a f-string. diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E201_E20.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E201_E20.py.snap index 0aea3ff5f7..12bf2237c6 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E201_E20.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E201_E20.py.snap @@ -183,3 +183,23 @@ E20.py:145:5: E201 [*] Whitespace after '[' 146 146 | 147 147 | #: Okay 148 148 | ham[lower + offset :: upper + offset] + +E20.py:195:5: E201 [*] Whitespace after '[' + | +193 | # t-strings +194 | t"{ {'a': 1} }" +195 | t"{[ { {'a': 1} } ]}" + | ^ E201 +196 | t"normal { {t"{ { [1, 2] } }" } } normal" + | + = help: Remove whitespace before '[' + +ℹ Safe fix +192 192 | +193 193 | # t-strings +194 194 | t"{ {'a': 1} }" +195 |-t"{[ { {'a': 1} } ]}" + 195 |+t"{[{ {'a': 1} } ]}" +196 196 | t"normal { {t"{ { [1, 2] } }" } } normal" +197 197 | +198 198 | t"{x = :.2f}" diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E202_E20.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E202_E20.py.snap index 23b47a9a92..64fb876766 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E202_E20.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E202_E20.py.snap @@ -165,3 +165,23 @@ E20.py:172:12: E202 [*] Whitespace before ']' 173 173 | 174 174 | #: E203:1:10 175 175 | ham[upper :] + +E20.py:195:18: E202 [*] Whitespace before ']' + | +193 | # t-strings +194 | t"{ {'a': 1} }" +195 | t"{[ { {'a': 1} } ]}" + | ^ E202 +196 | t"normal { {t"{ { [1, 2] } }" } } normal" + | + = help: Remove whitespace before ']' + +ℹ Safe fix +192 192 | +193 193 | # t-strings +194 194 | t"{ {'a': 1} }" +195 |-t"{[ { {'a': 1} } ]}" + 195 |+t"{[ { {'a': 1} }]}" +196 196 | t"normal { {t"{ { [1, 2] } }" } } normal" +197 197 | +198 198 | t"{x = :.2f}" diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E203_E20.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E203_E20.py.snap index bcac95a6e6..180b3f1078 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E203_E20.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E203_E20.py.snap @@ -345,3 +345,19 @@ E20.py:187:17: E203 [*] Whitespace before ':' 188 188 | 189 189 | #: Okay: https://github.com/astral-sh/ruff/issues/12023 190 190 | f"{x = :.2f}" + +E20.py:205:17: E203 [*] Whitespace before ':' + | +204 | #: E203:1:13 +205 | t"{ham[lower + 1 :, "columnname"]}" + | ^^ E203 + | + = help: Remove whitespace before ':' + +ℹ Safe fix +202 202 | t"{ham[lower +1 :, "columnname"]}" +203 203 | +204 204 | #: E203:1:13 +205 |-t"{ham[lower + 1 :, "columnname"]}" + 205 |+t"{ham[lower + 1:, "columnname"]}" +206 206 | diff --git a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E231_E23.py.snap b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E231_E23.py.snap index 8846098bcc..a1d31b52ea 100644 --- a/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E231_E23.py.snap +++ b/crates/ruff_linter/src/rules/pycodestyle/snapshots/ruff_linter__rules__pycodestyle__tests__E231_E23.py.snap @@ -905,3 +905,38 @@ E23.py:126:99: E231 [*] Missing whitespace after ':' 127 127 | pass 128 128 | 129 129 | # Should be no E231 errors on any of these: + +E23.py:147:6: E231 [*] Missing whitespace after ',' + | +146 | # E231 +147 | t"{(a,b)}" + | ^ E231 +148 | +149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string + | + = help: Add missing whitespace + +ℹ Safe fix +144 144 | pass +145 145 | +146 146 | # E231 +147 |-t"{(a,b)}" + 147 |+t"{(a, b)}" +148 148 | +149 149 | # Okay because it's hard to differentiate between the usages of a colon in a t-string +150 150 | t"{a:=1}" + +E23.py:161:37: E231 [*] Missing whitespace after ':' + | +160 | #: E231 +161 | {len(t's3://{self.s3_bucket_name}/'):1} + | ^ E231 + | + = help: Add missing whitespace + +ℹ Safe fix +158 158 | snapshot.file_uri[len(t's3://{self.s3_bucket_name}/'):] +159 159 | +160 160 | #: E231 +161 |-{len(t's3://{self.s3_bucket_name}/'):1} + 161 |+{len(t's3://{self.s3_bucket_name}/'): 1}