Skip to content

Commit a38c05b

Browse files
Avoid recommending context manager in __enter__ implementations (#11575)
## Summary Closes #11567.
1 parent ab107ef commit a38c05b

File tree

2 files changed

+27
-3
lines changed

2 files changed

+27
-3
lines changed

crates/ruff_linter/resources/test/fixtures/flake8_simplify/SIM115.py

+12
Original file line numberDiff line numberDiff line change
@@ -46,3 +46,15 @@
4646
# OK (quick one-liner to clear file contents)
4747
open("filename", "w").close()
4848
pathlib.Path("filename").open("w").close()
49+
50+
51+
# OK (custom context manager)
52+
class MyFile:
53+
def __init__(self, filename: str):
54+
self.filename = filename
55+
56+
def __enter__(self):
57+
self.file = open(self.filename)
58+
59+
def __exit__(self, exc_type, exc_val, exc_tb):
60+
self.file.close()

crates/ruff_linter/src/rules/flake8_simplify/rules/open_file_with_context_handler.rs

+15-3
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt};
22

33
use ruff_diagnostics::{Diagnostic, Violation};
44
use ruff_macros::{derive_message_formats, violation};
5-
use ruff_python_semantic::SemanticModel;
5+
use ruff_python_semantic::{ScopeKind, SemanticModel};
66
use ruff_text_size::Ranged;
77

88
use crate::checkers::ast::Checker;
@@ -114,24 +114,27 @@ fn match_exit_stack(semantic: &SemanticModel) -> bool {
114114

115115
/// Return `true` if `func` is the builtin `open` or `pathlib.Path(...).open`.
116116
fn is_open(semantic: &SemanticModel, func: &Expr) -> bool {
117-
// open(...)
117+
// Ex) `open(...)`
118118
if semantic.match_builtin_expr(func, "open") {
119119
return true;
120120
}
121121

122-
// pathlib.Path(...).open()
122+
// Ex) `pathlib.Path(...).open()`
123123
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
124124
return false;
125125
};
126+
126127
if attr != "open" {
127128
return false;
128129
}
130+
129131
let Expr::Call(ast::ExprCall {
130132
func: value_func, ..
131133
}) = &**value
132134
else {
133135
return false;
134136
};
137+
135138
semantic
136139
.resolve_qualified_name(value_func)
137140
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pathlib", "Path"]))
@@ -189,6 +192,15 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr)
189192
return;
190193
}
191194

195+
// Ex) `def __enter__(self): ...`
196+
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) =
197+
&checker.semantic().current_scope().kind
198+
{
199+
if name == "__enter__" {
200+
return;
201+
}
202+
}
203+
192204
checker
193205
.diagnostics
194206
.push(Diagnostic::new(OpenFileWithContextHandler, func.range()));

0 commit comments

Comments
 (0)