[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: except Exception as e:
raise ValueError from e raise ValueError from e
try:
...
except Exception as e:
raise e from ValueError("hello")
try: try:
pass pass
@ -245,3 +250,9 @@ try:
pass pass
except (Exception, ValueError) as e: except (Exception, ValueError) as e:
raise 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 /// ## What it does
/// Checks for `except` clauses that catch all exceptions. This includes /// 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? /// ## Why is this bad?
@ -149,26 +149,30 @@ impl<'a> ReraiseVisitor<'a> {
impl<'a> StatementVisitor<'a> for ReraiseVisitor<'a> { impl<'a> StatementVisitor<'a> for ReraiseVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) { fn visit_stmt(&mut self, stmt: &'a Stmt) {
if self.seen {
return;
}
match stmt { match stmt {
Stmt::Raise(ast::StmtRaise { exc, cause, .. }) => { Stmt::Raise(ast::StmtRaise { exc, cause, .. }) => {
if let Some(cause) = cause { // except Exception [as <name>]:
if let Expr::Name(ast::ExprName { id, .. }) = cause.as_ref() { // raise [<exc> [from <cause>]]
if self.name.is_some_and(|name| id == name) { 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
}
// `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; self.seen = 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;
}
}
}
Stmt::Try(_) | Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {} Stmt::Try(_) | Stmt::FunctionDef(_) | Stmt::ClassDef(_) => {}
_ => walk_stmt(self, stmt), _ => walk_stmt(self, stmt),
} }
@ -200,6 +204,9 @@ impl<'a> LogExceptionVisitor<'a> {
impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> { impl<'a> StatementVisitor<'a> for LogExceptionVisitor<'a> {
fn visit_stmt(&mut self, stmt: &'a Stmt) { fn visit_stmt(&mut self, stmt: &'a Stmt) {
if self.seen {
return;
}
match stmt { match stmt {
Stmt::Expr(ast::StmtExpr { value, .. }) => { Stmt::Expr(ast::StmtExpr { value, .. }) => {
if let Expr::Call(ast::ExprCall { if let Expr::Call(ast::ExprCall {

View file

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