diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py index 46e01a4f84..88af984ce4 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py @@ -264,3 +264,22 @@ def func(filepath, encoding): # OK def func(filepath, encoding): return f(open(filepath, mode="rt", encoding=encoding)) + + +from unittest import IsolatedAsyncioTestCase, TestCase + +# OK +class ExampleClassTests(TestCase): + @classmethod + def setUpClass(cls): + cls.enterClassContext(open("filename")) + +# OK +class ExampleAsyncTests(IsolatedAsyncioTestCase): + async def test_something(self): + await self.enterAsyncContext(open("filename")) + +# OK +class ExampleTests(TestCase): + def setUp(self): + self.enterContext(open("filename")) \ No newline at end of file diff --git a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs index e17aeb59de..fc36a1acfc 100644 --- a/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs +++ b/crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs @@ -114,6 +114,28 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool { false } +/// Return `true` if the current expression is nested in a call to one of the +/// unittest context manager methods: `cls.enterClassContext()`, +/// `self.enterContext()`, or `self.enterAsyncContext()`. +fn match_unittest_context_methods(semantic: &SemanticModel) -> bool { + let Some(expr) = semantic.current_expression_parent() else { + return false; + }; + let Expr::Call(ast::ExprCall { func, .. }) = expr else { + return false; + }; + let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func.as_ref() else { + return false; + }; + let Expr::Name(ast::ExprName { id, .. }) = value.as_ref() else { + return false; + }; + matches!( + (id.as_str(), attr.as_str()), + ("cls", "enterClassContext") | ("self", "enterContext" | "enterAsyncContext") + ) +} + /// Return `true` if the expression is a call to `open()`, /// or a call to some other standard-library function that opens a file. fn is_open_call(semantic: &SemanticModel, call: &ast::ExprCall) -> bool { @@ -229,6 +251,11 @@ pub(crate) fn open_file_with_context_handler(checker: &Checker, call: &ast::Expr return; } + // Ex) `self.enterContext(open("foo.txt"))` + if match_unittest_context_methods(semantic) { + return; + } + // Ex) `def __enter__(self): ...` if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) = &checker.semantic().current_scope().kind