mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-29 13:25:17 +00:00
Implement B017 (no assertRaises(Exception)) (#467)
This commit is contained in:
parent
66089052ee
commit
bcf7519eb3
9 changed files with 104 additions and 3 deletions
|
@ -382,6 +382,7 @@ The 🛠 emoji indicates that a rule is automatically fixable by the `--fix` com
|
||||||
| ---- | ---- | ------- | --- |
|
| ---- | ---- | ------- | --- |
|
||||||
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
| B011 | DoNotAssertFalse | Do not `assert False` (`python -O` removes these calls), raise `AssertionError()` | 🛠 |
|
||||||
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
| B014 | DuplicateHandlerException | Exception handler with duplicate exception: `ValueError` | 🛠 |
|
||||||
|
| B017 | NoAssertRaisesException | `assertRaises(Exception):` should be considered evil. | |
|
||||||
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
| B025 | DuplicateTryBlockException | try-except block with duplicate exception `Exception` | |
|
||||||
|
|
||||||
### flake8-builtins
|
### flake8-builtins
|
||||||
|
@ -471,7 +472,7 @@ including:
|
||||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (4/32)
|
||||||
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
- [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34)
|
||||||
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
- [`autoflake`](https://pypi.org/project/autoflake/) (1/7)
|
||||||
|
|
||||||
|
@ -491,7 +492,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl
|
||||||
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
- [`flake8-super`](https://pypi.org/project/flake8-super/)
|
||||||
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
- [`flake8-print`](https://pypi.org/project/flake8-print/)
|
||||||
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
- [`flake8-comprehensions`](https://pypi.org/project/flake8-comprehensions/)
|
||||||
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (3/32)
|
- [`flake8-bugbear`](https://pypi.org/project/flake8-bugbear/) (4/32)
|
||||||
|
|
||||||
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
|
Ruff also implements the functionality that you get from [`yesqa`](https://github.com/asottile/yesqa),
|
||||||
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
|
and a subset of the rules implemented in [`pyupgrade`](https://pypi.org/project/pyupgrade/) (8/34).
|
||||||
|
|
|
@ -19,7 +19,7 @@ fn main() {
|
||||||
"| {} | {} | {} | {} |",
|
"| {} | {} | {} | {} |",
|
||||||
check_kind.code().as_ref(),
|
check_kind.code().as_ref(),
|
||||||
check_kind.as_ref(),
|
check_kind.as_ref(),
|
||||||
check_kind.body().replace("|", r"\|"),
|
check_kind.summary().replace("|", r"\|"),
|
||||||
fix_token
|
fix_token
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
36
resources/test/fixtures/B017.py
vendored
Normal file
36
resources/test/fixtures/B017.py
vendored
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
"""
|
||||||
|
Should emit:
|
||||||
|
B017 - on lines 20
|
||||||
|
"""
|
||||||
|
import asyncio
|
||||||
|
import unittest
|
||||||
|
|
||||||
|
CONSTANT = True
|
||||||
|
|
||||||
|
|
||||||
|
def something_else() -> None:
|
||||||
|
for i in (1, 2, 3):
|
||||||
|
print(i)
|
||||||
|
|
||||||
|
|
||||||
|
class Foo:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class Foobar(unittest.TestCase):
|
||||||
|
def evil_raises(self) -> None:
|
||||||
|
with self.assertRaises(Exception):
|
||||||
|
raise Exception("Evil I say!")
|
||||||
|
|
||||||
|
def context_manager_raises(self) -> None:
|
||||||
|
with self.assertRaises(Exception) as ex:
|
||||||
|
raise Exception("Context manager is good")
|
||||||
|
self.assertEqual("Context manager is good", str(ex.exception))
|
||||||
|
|
||||||
|
def regex_raises(self) -> None:
|
||||||
|
with self.assertRaisesRegex(Exception, "Regex is good"):
|
||||||
|
raise Exception("Regex is good")
|
||||||
|
|
||||||
|
def raises_with_absolute_reference(self):
|
||||||
|
with self.assertRaises(asyncio.CancelledError):
|
||||||
|
Foo()
|
|
@ -667,6 +667,11 @@ where
|
||||||
flake8_bugbear::plugins::assert_false(self, stmt, test, msg);
|
flake8_bugbear::plugins::assert_false(self, stmt, test, msg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
StmtKind::With { items, .. } | StmtKind::AsyncWith { items, .. } => {
|
||||||
|
if self.settings.enabled.contains(&CheckCode::B017) {
|
||||||
|
flake8_bugbear::plugins::assert_raises_exception(self, stmt, items);
|
||||||
|
}
|
||||||
|
}
|
||||||
StmtKind::Try { handlers, .. } => {
|
StmtKind::Try { handlers, .. } => {
|
||||||
if self.settings.enabled.contains(&CheckCode::F707) {
|
if self.settings.enabled.contains(&CheckCode::F707) {
|
||||||
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
|
if let Some(check) = pyflakes::checks::default_except_not_last(handlers) {
|
||||||
|
|
|
@ -77,6 +77,7 @@ pub enum CheckCode {
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
B011,
|
B011,
|
||||||
B014,
|
B014,
|
||||||
|
B017,
|
||||||
B025,
|
B025,
|
||||||
// flake8-comprehensions
|
// flake8-comprehensions
|
||||||
C400,
|
C400,
|
||||||
|
@ -267,6 +268,7 @@ pub enum CheckKind {
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
DoNotAssertFalse,
|
DoNotAssertFalse,
|
||||||
DuplicateHandlerException(Vec<String>),
|
DuplicateHandlerException(Vec<String>),
|
||||||
|
NoAssertRaisesException,
|
||||||
DuplicateTryBlockException(String),
|
DuplicateTryBlockException(String),
|
||||||
// flake8-comprehensions
|
// flake8-comprehensions
|
||||||
UnnecessaryGeneratorList,
|
UnnecessaryGeneratorList,
|
||||||
|
@ -426,6 +428,7 @@ impl CheckCode {
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
CheckCode::B011 => CheckKind::DoNotAssertFalse,
|
||||||
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
CheckCode::B014 => CheckKind::DuplicateHandlerException(vec!["ValueError".to_string()]),
|
||||||
|
CheckCode::B017 => CheckKind::NoAssertRaisesException,
|
||||||
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
CheckCode::B025 => CheckKind::DuplicateTryBlockException("Exception".to_string()),
|
||||||
// flake8-comprehensions
|
// flake8-comprehensions
|
||||||
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
CheckCode::C400 => CheckKind::UnnecessaryGeneratorList,
|
||||||
|
@ -600,6 +603,7 @@ impl CheckCode {
|
||||||
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
CheckCode::A003 => CheckCategory::Flake8Builtins,
|
||||||
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
CheckCode::B011 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
CheckCode::B014 => CheckCategory::Flake8Bugbear,
|
||||||
|
CheckCode::B017 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
CheckCode::B025 => CheckCategory::Flake8Bugbear,
|
||||||
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
CheckCode::C400 => CheckCategory::Flake8Comprehensions,
|
||||||
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
CheckCode::C401 => CheckCategory::Flake8Comprehensions,
|
||||||
|
@ -743,6 +747,7 @@ impl CheckKind {
|
||||||
// flake8-bugbear
|
// flake8-bugbear
|
||||||
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
CheckKind::DoNotAssertFalse => &CheckCode::B011,
|
||||||
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
CheckKind::DuplicateHandlerException(_) => &CheckCode::B014,
|
||||||
|
CheckKind::NoAssertRaisesException => &CheckCode::B017,
|
||||||
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
CheckKind::DuplicateTryBlockException(_) => &CheckCode::B025,
|
||||||
// flake8-comprehensions
|
// flake8-comprehensions
|
||||||
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
CheckKind::UnnecessaryGeneratorList => &CheckCode::C400,
|
||||||
|
@ -991,6 +996,9 @@ impl CheckKind {
|
||||||
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
let names = names.iter().map(|name| format!("`{name}`")).join(", ");
|
||||||
format!("Exception handler with duplicate exceptions: {names}")
|
format!("Exception handler with duplicate exceptions: {names}")
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
CheckKind::NoAssertRaisesException => {
|
||||||
|
"`assertRaises(Exception):` should be considered evil. It can lead to your test passing even if the code being tested is never executed due to a typo. Either assert for a more specific exception (builtin or custom), use `assertRaisesRegex`, or use the context manager form of `assertRaises`.".to_string()
|
||||||
}
|
}
|
||||||
CheckKind::DuplicateTryBlockException(name) => {
|
CheckKind::DuplicateTryBlockException(name) => {
|
||||||
format!("try-except block with duplicate exception `{name}`")
|
format!("try-except block with duplicate exception `{name}`")
|
||||||
|
@ -1258,6 +1266,16 @@ impl CheckKind {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The summary text for the check.
|
||||||
|
pub fn summary(&self) -> String {
|
||||||
|
match self {
|
||||||
|
CheckKind::NoAssertRaisesException => {
|
||||||
|
"`assertRaises(Exception):` should be considered evil.".to_string()
|
||||||
|
}
|
||||||
|
_ => self.body(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Whether the check kind is (potentially) fixable.
|
/// Whether the check kind is (potentially) fixable.
|
||||||
pub fn fixable(&self) -> bool {
|
pub fn fixable(&self) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
|
|
25
src/flake8_bugbear/plugins/assert_raises_exception.rs
Normal file
25
src/flake8_bugbear/plugins/assert_raises_exception.rs
Normal file
|
@ -0,0 +1,25 @@
|
||||||
|
use rustpython_ast::{ExprKind, Stmt, Withitem};
|
||||||
|
|
||||||
|
use crate::ast::helpers::match_name_or_attr;
|
||||||
|
use crate::ast::types::Range;
|
||||||
|
use crate::check_ast::Checker;
|
||||||
|
use crate::checks::{Check, CheckKind};
|
||||||
|
|
||||||
|
/// B017
|
||||||
|
pub fn assert_raises_exception(checker: &mut Checker, stmt: &Stmt, items: &[Withitem]) {
|
||||||
|
if let Some(item) = items.first() {
|
||||||
|
let item_context = &item.context_expr;
|
||||||
|
if let ExprKind::Call { func, args, .. } = &item_context.node {
|
||||||
|
if match_name_or_attr(func, "assertRaises")
|
||||||
|
&& args.len() == 1
|
||||||
|
&& match_name_or_attr(args.first().unwrap(), "Exception")
|
||||||
|
&& item.optional_vars.is_none()
|
||||||
|
{
|
||||||
|
checker.add_check(Check::new(
|
||||||
|
CheckKind::NoAssertRaisesException,
|
||||||
|
Range::from_located(stmt),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,6 +1,8 @@
|
||||||
pub use assert_false::assert_false;
|
pub use assert_false::assert_false;
|
||||||
|
pub use assert_raises_exception::assert_raises_exception;
|
||||||
pub use duplicate_exceptions::duplicate_exceptions;
|
pub use duplicate_exceptions::duplicate_exceptions;
|
||||||
pub use duplicate_exceptions::duplicate_handler_exceptions;
|
pub use duplicate_exceptions::duplicate_handler_exceptions;
|
||||||
|
|
||||||
mod assert_false;
|
mod assert_false;
|
||||||
|
mod assert_raises_exception;
|
||||||
mod duplicate_exceptions;
|
mod duplicate_exceptions;
|
||||||
|
|
|
@ -247,6 +247,7 @@ mod tests {
|
||||||
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
|
#[test_case(CheckCode::A003, Path::new("A003.py"); "A003")]
|
||||||
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
#[test_case(CheckCode::B011, Path::new("B011.py"); "B011")]
|
||||||
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
#[test_case(CheckCode::B014, Path::new("B014.py"); "B014")]
|
||||||
|
#[test_case(CheckCode::B017, Path::new("B017.py"); "B017")]
|
||||||
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
#[test_case(CheckCode::B025, Path::new("B025.py"); "B025")]
|
||||||
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
|
#[test_case(CheckCode::C400, Path::new("C400.py"); "C400")]
|
||||||
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
|
#[test_case(CheckCode::C401, Path::new("C401.py"); "C401")]
|
||||||
|
|
13
src/snapshots/ruff__linter__tests__B017_B017.py.snap
Normal file
13
src/snapshots/ruff__linter__tests__B017_B017.py.snap
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
---
|
||||||
|
source: src/linter.rs
|
||||||
|
expression: checks
|
||||||
|
---
|
||||||
|
- kind: NoAssertRaisesException
|
||||||
|
location:
|
||||||
|
row: 22
|
||||||
|
column: 9
|
||||||
|
end_location:
|
||||||
|
row: 25
|
||||||
|
column: 5
|
||||||
|
fix: ~
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue