diff --git a/README.md b/README.md index 5aed4c0853..d34e0334ac 100644 --- a/README.md +++ b/README.md @@ -960,9 +960,10 @@ For more, see [flake8-simplify](https://pypi.org/project/flake8-simplify/0.19.3/ | Code | Name | Message | Fix | | ---- | ---- | ------- | --- | +| SIM105 | UseContextlibSuppress | Use 'contextlib.suppress(..)' instead of try-except-pass | | +| SIM118 | KeyInDict | Use `key in dict` instead of `key in dict.keys()` | 🛠 | | SIM220 | AAndNotA | Use `False` instead of `... and not ...` | 🛠 | | SIM221 | AOrNotA | Use `True` instead of `... or not ...` | 🛠 | -| SIM118 | KeyInDict | Use `key in dict` instead of `key in dict.keys()` | 🛠 | | SIM222 | OrTrue | Use `True` instead of `... or True` | 🛠 | | SIM223 | AndFalse | Use `False` instead of `... and False` | 🛠 | | SIM300 | YodaConditions | Use `left == right` instead of `right == left (Yoda-conditions)` | 🛠 | @@ -1354,7 +1355,7 @@ natively, including: - [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-return`](https://pypi.org/project/flake8-return/) -- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (6/30) +- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (7/30) - [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) - [`isort`](https://pypi.org/project/isort/) @@ -1412,7 +1413,7 @@ Today, Ruff can be used to replace Flake8 when used with any of the following pl - [`flake8-print`](https://pypi.org/project/flake8-print/) - [`flake8-quotes`](https://pypi.org/project/flake8-quotes/) - [`flake8-return`](https://pypi.org/project/flake8-return/) -- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (6/30) +- [`flake8-simplify`](https://pypi.org/project/flake8-simplify/) (7/30) - [`flake8-super`](https://pypi.org/project/flake8-super/) - [`flake8-tidy-imports`](https://pypi.org/project/flake8-tidy-imports/) - [`mccabe`](https://pypi.org/project/mccabe/) diff --git a/resources/test/fixtures/flake8_simplify/SIM105.py b/resources/test/fixtures/flake8_simplify/SIM105.py new file mode 100644 index 0000000000..8024bfd2a2 --- /dev/null +++ b/resources/test/fixtures/flake8_simplify/SIM105.py @@ -0,0 +1,31 @@ +def foo(): + pass + +try: + foo() +except ValueError: # SIM105 + pass + +try: + foo() +except (ValueError, OSError): # SIM105 + pass + +try: + foo() +except: # SIM105 + pass + +try: + foo() +except ValueError: + print('foo') +except OSError: + pass + +try: + foo() +except ValueError: + pass +else: + print('bar') diff --git a/src/checkers/ast.rs b/src/checkers/ast.rs index 8fdf100ce1..01f76dfda6 100644 --- a/src/checkers/ast.rs +++ b/src/checkers/ast.rs @@ -1247,7 +1247,9 @@ where flake8_simplify::plugins::key_in_dict_for(self, target, iter); } } - StmtKind::Try { handlers, .. } => { + StmtKind::Try { + handlers, orelse, .. + } => { if self.settings.enabled.contains(&CheckCode::F707) { if let Some(check) = pyflakes::checks::default_except_not_last(handlers, self.locator) @@ -1272,6 +1274,9 @@ where .into_iter(), ); } + if self.settings.enabled.contains(&CheckCode::SIM105) { + flake8_simplify::plugins::use_contextlib_suppress(self, stmt, handlers, orelse); + } } StmtKind::Assign { targets, value, .. } => { if self.settings.enabled.contains(&CheckCode::E731) { diff --git a/src/flake8_simplify/mod.rs b/src/flake8_simplify/mod.rs index 829f6d2e40..7a1f642494 100644 --- a/src/flake8_simplify/mod.rs +++ b/src/flake8_simplify/mod.rs @@ -12,6 +12,7 @@ mod tests { use crate::registry::CheckCode; use crate::settings; + #[test_case(CheckCode::SIM105, Path::new("SIM105.py"); "SIM105")] #[test_case(CheckCode::SIM118, Path::new("SIM118.py"); "SIM118")] #[test_case(CheckCode::SIM222, Path::new("SIM222.py"); "SIM222")] #[test_case(CheckCode::SIM223, Path::new("SIM223.py"); "SIM223")] diff --git a/src/flake8_simplify/plugins/mod.rs b/src/flake8_simplify/plugins/mod.rs index 27c0a0352d..3b0b27f74e 100644 --- a/src/flake8_simplify/plugins/mod.rs +++ b/src/flake8_simplify/plugins/mod.rs @@ -1,7 +1,9 @@ pub use bool_ops::{a_and_not_a, a_or_not_a, and_false, or_true}; pub use key_in_dict::{key_in_dict_compare, key_in_dict_for}; +pub use use_contextlib_suppress::use_contextlib_suppress; pub use yoda_conditions::yoda_conditions; mod bool_ops; mod key_in_dict; +mod use_contextlib_suppress; mod yoda_conditions; diff --git a/src/flake8_simplify/plugins/use_contextlib_suppress.rs b/src/flake8_simplify/plugins/use_contextlib_suppress.rs new file mode 100644 index 0000000000..a97d8a8f1d --- /dev/null +++ b/src/flake8_simplify/plugins/use_contextlib_suppress.rs @@ -0,0 +1,38 @@ +use rustpython_ast::{Excepthandler, ExcepthandlerKind, Stmt, StmtKind}; + +use crate::ast::helpers; +use crate::ast::types::Range; +use crate::checkers::ast::Checker; +use crate::registry::{Check, CheckKind}; + +/// SIM105 +pub fn use_contextlib_suppress( + checker: &mut Checker, + stmt: &Stmt, + handlers: &[Excepthandler], + orelse: &[Stmt], +) { + if handlers.len() != 1 || !orelse.is_empty() { + return; + } + let handler = &handlers[0]; + let ExcepthandlerKind::ExceptHandler { body, .. } = &handler.node; + if body.len() == 1 { + if matches!(body[0].node, StmtKind::Pass) { + let handler_names: Vec<_> = helpers::extract_handler_names(handlers) + .into_iter() + .flatten() + .collect(); + let exception = if handler_names.is_empty() { + "Exception".to_string() + } else { + handler_names.join(", ") + }; + let check = Check::new( + CheckKind::UseContextlibSuppress(exception), + Range::from_located(stmt), + ); + checker.add_check(check); + } + } +} diff --git a/src/flake8_simplify/snapshots/ruff__flake8_simplify__tests__SIM105_SIM105.py.snap b/src/flake8_simplify/snapshots/ruff__flake8_simplify__tests__SIM105_SIM105.py.snap new file mode 100644 index 0000000000..e062c1774b --- /dev/null +++ b/src/flake8_simplify/snapshots/ruff__flake8_simplify__tests__SIM105_SIM105.py.snap @@ -0,0 +1,35 @@ +--- +source: src/flake8_simplify/mod.rs +expression: checks +--- +- kind: + UseContextlibSuppress: ValueError + location: + row: 4 + column: 0 + end_location: + row: 7 + column: 8 + fix: ~ + parent: ~ +- kind: + UseContextlibSuppress: "ValueError, OSError" + location: + row: 9 + column: 0 + end_location: + row: 12 + column: 8 + fix: ~ + parent: ~ +- kind: + UseContextlibSuppress: Exception + location: + row: 14 + column: 0 + end_location: + row: 17 + column: 8 + fix: ~ + parent: ~ + diff --git a/src/registry.rs b/src/registry.rs index 8c1f7187be..4fc78ec42b 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -217,9 +217,10 @@ pub enum CheckCode { YTT302, YTT303, // flake8-simplify + SIM105, + SIM118, SIM220, SIM221, - SIM118, SIM222, SIM223, SIM300, @@ -876,6 +877,7 @@ pub enum CheckKind { UnaryPrefixIncrement, UnreliableCallableCheck, UnusedLoopControlVariable(String), + UseContextlibSuppress(String), UselessComparison, UselessContextlibSuppress, UselessExpression, @@ -1377,6 +1379,7 @@ impl CheckCode { // flake8-blind-except CheckCode::BLE001 => CheckKind::BlindExcept("Exception".to_string()), // flake8-simplify + CheckCode::SIM105 => CheckKind::UseContextlibSuppress("..".to_string()), CheckCode::SIM118 => CheckKind::KeyInDict("key".to_string(), "dict".to_string()), CheckCode::SIM220 => CheckKind::AAndNotA("...".to_string()), CheckCode::SIM221 => CheckKind::AOrNotA("...".to_string()), @@ -1903,6 +1906,7 @@ impl CheckCode { CheckCode::S106 => CheckCategory::Flake8Bandit, CheckCode::S107 => CheckCategory::Flake8Bandit, // flake8-simplify + CheckCode::SIM105 => CheckCategory::Flake8Simplify, CheckCode::SIM118 => CheckCategory::Flake8Simplify, CheckCode::SIM220 => CheckCategory::Flake8Simplify, CheckCode::SIM221 => CheckCategory::Flake8Simplify, @@ -2154,6 +2158,7 @@ impl CheckKind { CheckKind::SysVersionCmpStr10 => &CheckCode::YTT302, CheckKind::SysVersionSlice1Referenced => &CheckCode::YTT303, // flake8-simplify + CheckKind::UseContextlibSuppress(..) => &CheckCode::SIM105, CheckKind::KeyInDict(..) => &CheckCode::SIM118, CheckKind::AAndNotA(..) => &CheckCode::SIM220, CheckKind::AOrNotA(..) => &CheckCode::SIM221, @@ -2667,6 +2672,9 @@ impl CheckKind { CheckKind::FStringDocstring => "f-string used as docstring. This will be interpreted \ by python as a joined string rather than a docstring." .to_string(), + CheckKind::UseContextlibSuppress(exception) => { + format!("Use 'contextlib.suppress({exception})' instead of try-except-pass") + } CheckKind::UselessContextlibSuppress => { "No arguments passed to `contextlib.suppress`. No exceptions will be suppressed \ and therefore this context manager is redundant" diff --git a/src/registry_gen.rs b/src/registry_gen.rs index 438c1831ea..bc1553f714 100644 --- a/src/registry_gen.rs +++ b/src/registry_gen.rs @@ -515,6 +515,8 @@ pub enum CheckCodePrefix { S107, SIM, SIM1, + SIM10, + SIM105, SIM11, SIM118, SIM2, @@ -801,9 +803,10 @@ impl CheckCodePrefix { CheckCode::YTT301, CheckCode::YTT302, CheckCode::YTT303, + CheckCode::SIM105, + CheckCode::SIM118, CheckCode::SIM220, CheckCode::SIM221, - CheckCode::SIM118, CheckCode::SIM222, CheckCode::SIM223, CheckCode::SIM300, @@ -2612,14 +2615,17 @@ impl CheckCodePrefix { CheckCodePrefix::S106 => vec![CheckCode::S106], CheckCodePrefix::S107 => vec![CheckCode::S107], CheckCodePrefix::SIM => vec![ + CheckCode::SIM105, + CheckCode::SIM118, CheckCode::SIM220, CheckCode::SIM221, - CheckCode::SIM118, CheckCode::SIM222, CheckCode::SIM223, CheckCode::SIM300, ], - CheckCodePrefix::SIM1 => vec![CheckCode::SIM118], + CheckCodePrefix::SIM1 => vec![CheckCode::SIM105, CheckCode::SIM118], + CheckCodePrefix::SIM10 => vec![CheckCode::SIM105], + CheckCodePrefix::SIM105 => vec![CheckCode::SIM105], CheckCodePrefix::SIM11 => vec![CheckCode::SIM118], CheckCodePrefix::SIM118 => vec![CheckCode::SIM118], CheckCodePrefix::SIM2 => vec![ @@ -3583,6 +3589,8 @@ impl CheckCodePrefix { CheckCodePrefix::S107 => SuffixLength::Three, CheckCodePrefix::SIM => SuffixLength::Zero, CheckCodePrefix::SIM1 => SuffixLength::One, + CheckCodePrefix::SIM10 => SuffixLength::Two, + CheckCodePrefix::SIM105 => SuffixLength::Three, CheckCodePrefix::SIM11 => SuffixLength::Two, CheckCodePrefix::SIM118 => SuffixLength::Three, CheckCodePrefix::SIM2 => SuffixLength::One,