[flake8-blind-except] Fix BLE001 false-positive on raise ... from None (#19755)

## Summary

- Refactored `BLE001` logic for clarity and minor speed-up.
- Improved documentation and comments (previously, `BLE001` docs claimed
it catches bare `except:`s, but it doesn't).
- Fixed a false-positive bug with `from None` cause:

```python
# somefile.py

try:
    pass
except BaseException as e:
    raise e from None
```

### main branch
```
somefile.py:3:8: BLE001 Do not catch blind exception: `BaseException`
  |
1 | try:
2 |     pass
3 | except BaseException as e:
  |        ^^^^^^^^^^^^^ BLE001
4 |     raise e from None
  |

Found 1 error.
```

### this change

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

```
All checks passed!
```

## Test Plan

- Added a test case to cover `raise X from Y` clause
- Added a test case to cover `raise X from None` clause
This commit is contained in:
Roman Kitaev 2025-08-13 20:01:47 +03:00 committed by GitHub
parent f0b03c3e86
commit df0648aae0
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 88 additions and 70 deletions

View file

@ -154,6 +154,11 @@ try:
except Exception as e:
raise ValueError from e
try:
...
except Exception as e:
raise e from ValueError("hello")
try:
pass
@ -245,3 +250,9 @@ try:
pass
except (Exception, ValueError) as e:
raise e
# `from None` cause
try:
pass
except BaseException as e:
raise e from None

View file

@ -11,7 +11,7 @@ use crate::checkers::ast::Checker;
/// ## What it does
/// Checks for `except` clauses that catch all exceptions. This includes
/// bare `except`, `except BaseException` and `except Exception`.
/// `except BaseException` and `except Exception`.
///
///
/// ## Why is this bad?
@ -149,24 +149,28 @@ impl<'a> ReraiseVisitor<'a> {
impl<'a> StatementVisitor<'a> for ReraiseVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
if self.seen {
return;
}
match stmt {
Stmt::Raise(ast::StmtRaise { exc, cause, .. }) => {
if let Some(cause) = cause {
if let Expr::Name(ast::ExprName { id, .. }) = cause.as_ref() {
if self.name.is_some_and(|name| id == name) {
self.seen = true;
}
// except Exception [as <name>]:
// raise [<exc> [from <cause>]]
let reraised = match (self.name, exc.as_deref(), cause.as_deref()) {
// `raise`
(_, None, None) => true,
// `raise SomeExc from <name>`
(Some(name), _, Some(Expr::Name(ast::ExprName { id, .. }))) if name == id => {
true
}
} else {
if let Some(exc) = exc {
if let Expr::Name(ast::ExprName { id, .. }) = exc.as_ref() {
if self.name.is_some_and(|name| id == name) {
self.seen = true;
}
}
} else {
self.seen = true;
// `raise <name>` and `raise <name> from SomeCause`
(Some(name), Some(Expr::Name(ast::ExprName { id, .. })), _) if name == id => {
true
}
_ => false,
};
if reraised {
self.seen = true;
}
}
Stmt::Try(_) | Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {}
@ -200,6 +204,9 @@ impl<'a> LogExceptionVisitor<'a> {
impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) {
if self.seen {
return;
}
match stmt {
Stmt::Expr(ast::StmtExpr { value, .. }) => {
if let Expr::Call(ast::ExprCall {

View file

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