[flake8-blind-except] Change BLE001 to correctly parse exception tuples (#19747)
Some checks are pending
CI / Determine changes (push) Waiting to run
CI / cargo fmt (push) Waiting to run
CI / cargo clippy (push) Blocked by required conditions
CI / cargo test (linux) (push) Blocked by required conditions
CI / cargo test (linux, release) (push) Blocked by required conditions
CI / cargo test (windows) (push) Blocked by required conditions
CI / cargo test (wasm) (push) Blocked by required conditions
CI / cargo build (release) (push) Waiting to run
CI / cargo build (msrv) (push) Blocked by required conditions
CI / cargo fuzz build (push) Blocked by required conditions
CI / fuzz parser (push) Blocked by required conditions
CI / test scripts (push) Blocked by required conditions
CI / ecosystem (push) Blocked by required conditions
CI / Fuzz for new ty panics (push) Blocked by required conditions
CI / cargo shear (push) Blocked by required conditions
CI / python package (push) Waiting to run
CI / pre-commit (push) Waiting to run
CI / mkdocs (push) Waiting to run
CI / formatter instabilities and black similarity (push) Blocked by required conditions
CI / test ruff-lsp (push) Blocked by required conditions
CI / check playground (push) Blocked by required conditions
CI / benchmarks-instrumented (push) Blocked by required conditions
CI / benchmarks-walltime (push) Blocked by required conditions
[ty Playground] Release / publish (push) Waiting to run

## Summary

This PR enhances the `BLE001` rule to correctly detect blind exception
handling in tuple exceptions. Previously, the rule only checked single
exception types, but Python allows catching multiple exceptions using
tuples like `except (Exception, ValueError):`.

## Test Plan

It fails the following (whereas the main branch does not):

```bash
cargo run -p ruff -- check somefile.py --no-cache --select=BLE001
```

```python
# somefile.py

try:
    1/0
except (ValueError, Exception) as e:
    print(e)
```

```
somefile.py:3:21: BLE001 Do not catch blind exception: `Exception`
  |
1 | try:
2 |     1/0
3 | except (ValueError, Exception) as e:
  |                     ^^^^^^^^^ BLE001
4 |     print(e)
  |

Found 1 error.
```
This commit is contained in:
Roman Kitaev 2025-08-05 04:12:45 +07:00 committed by GitHub
parent 3a9341f7be
commit b0f01ba514
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 192 additions and 6 deletions

View file

@ -162,3 +162,86 @@ except Exception:
exception("An error occurred") exception("An error occurred")
else: else:
exception("An error occurred") exception("An error occurred")
# Test tuple exceptions
try:
pass
except (Exception,):
pass
try:
pass
except (Exception, ValueError):
pass
try:
pass
except (ValueError, Exception):
pass
try:
pass
except (ValueError, Exception) as e:
print(e)
try:
pass
except (BaseException, TypeError):
pass
try:
pass
except (TypeError, BaseException):
pass
try:
pass
except (Exception, BaseException):
pass
try:
pass
except (BaseException, Exception):
pass
# Test nested tuples
try:
pass
except ((Exception, ValueError), TypeError):
pass
try:
pass
except (ValueError, (BaseException, TypeError)):
pass
# Test valid tuple exceptions (should not trigger)
try:
pass
except (ValueError, TypeError):
pass
try:
pass
except (OSError, FileNotFoundError):
pass
try:
pass
except (OSError, FileNotFoundError) as e:
print(e)
try:
pass
except (Exception, ValueError):
critical("...", exc_info=True)
try:
pass
except (Exception, ValueError):
raise
try:
pass
except (Exception, ValueError) as e:
raise e

View file

@ -75,6 +75,22 @@ impl Violation for BlindExcept {
} }
} }
fn contains_blind_exception<'a>(
semantic: &'a SemanticModel,
expr: &'a Expr,
) -> Option<(&'a str, ruff_text_size::TextRange)> {
match expr {
Expr::Tuple(ast::ExprTuple { elts, .. }) => elts
.iter()
.find_map(|elt| contains_blind_exception(semantic, elt)),
_ => {
let builtin_exception_type = semantic.resolve_builtin_symbol(expr)?;
matches!(builtin_exception_type, "BaseException" | "Exception")
.then(|| (builtin_exception_type, expr.range()))
}
}
}
/// BLE001 /// BLE001
pub(crate) fn blind_except( pub(crate) fn blind_except(
checker: &Checker, checker: &Checker,
@ -87,12 +103,9 @@ pub(crate) fn blind_except(
}; };
let semantic = checker.semantic(); let semantic = checker.semantic();
let Some(builtin_exception_type) = semantic.resolve_builtin_symbol(type_) else { let Some((builtin_exception_type, range)) = contains_blind_exception(semantic, type_) else {
return; return;
}; };
if !matches!(builtin_exception_type, "BaseException" | "Exception") {
return;
}
// If the exception is re-raised, don't flag an error. // If the exception is re-raised, don't flag an error.
let mut visitor = ReraiseVisitor::new(name); let mut visitor = ReraiseVisitor::new(name);
@ -110,9 +123,9 @@ pub(crate) fn blind_except(
checker.report_diagnostic( checker.report_diagnostic(
BlindExcept { BlindExcept {
name: builtin_exception_type.to_string(), name: builtin_exception_type.into(),
}, },
type_.range(), range,
); );
} }

View file

@ -147,3 +147,93 @@ BLE.py:131:8: BLE001 Do not catch blind exception: `Exception`
| ^^^^^^^^^ BLE001 | ^^^^^^^^^ BLE001
132 | critical("...", exc_info=None) 132 | critical("...", exc_info=None)
| |
BLE.py:169:9: BLE001 Do not catch blind exception: `Exception`
|
167 | try:
168 | pass
169 | except (Exception,):
| ^^^^^^^^^ BLE001
170 | pass
|
BLE.py:174:9: BLE001 Do not catch blind exception: `Exception`
|
172 | try:
173 | pass
174 | except (Exception, ValueError):
| ^^^^^^^^^ BLE001
175 | pass
|
BLE.py:179:21: BLE001 Do not catch blind exception: `Exception`
|
177 | try:
178 | pass
179 | except (ValueError, Exception):
| ^^^^^^^^^ BLE001
180 | pass
|
BLE.py:184:21: BLE001 Do not catch blind exception: `Exception`
|
182 | try:
183 | pass
184 | except (ValueError, Exception) as e:
| ^^^^^^^^^ BLE001
185 | print(e)
|
BLE.py:189:9: BLE001 Do not catch blind exception: `BaseException`
|
187 | try:
188 | pass
189 | except (BaseException, TypeError):
| ^^^^^^^^^^^^^ BLE001
190 | pass
|
BLE.py:194:20: BLE001 Do not catch blind exception: `BaseException`
|
192 | try:
193 | pass
194 | except (TypeError, BaseException):
| ^^^^^^^^^^^^^ BLE001
195 | pass
|
BLE.py:199:9: BLE001 Do not catch blind exception: `Exception`
|
197 | try:
198 | pass
199 | except (Exception, BaseException):
| ^^^^^^^^^ BLE001
200 | pass
|
BLE.py:204:9: BLE001 Do not catch blind exception: `BaseException`
|
202 | try:
203 | pass
204 | except (BaseException, Exception):
| ^^^^^^^^^^^^^ BLE001
205 | pass
|
BLE.py:210:10: BLE001 Do not catch blind exception: `Exception`
|
208 | try:
209 | pass
210 | except ((Exception, ValueError), TypeError):
| ^^^^^^^^^ BLE001
211 | pass
|
BLE.py:215:22: BLE001 Do not catch blind exception: `BaseException`
|
213 | try:
214 | pass
215 | except (ValueError, (BaseException, TypeError)):
| ^^^^^^^^^^^^^ BLE001
216 | pass
|