Skip to content

Commit

Permalink
Avoid recommending context manager in __enter__ implementations (#1…
Browse files Browse the repository at this point in the history
…1575)

## Summary

Closes #11567.
  • Loading branch information
charliermarsh committed May 28, 2024
1 parent ab107ef commit a38c05b
Show file tree
Hide file tree
Showing 2 changed files with 27 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -46,3 +46,15 @@
# OK (quick one-liner to clear file contents)
open("filename", "w").close()
pathlib.Path("filename").open("w").close()


# OK (custom context manager)
class MyFile:
def __init__(self, filename: str):
self.filename = filename

def __enter__(self):
self.file = open(self.filename)

def __exit__(self, exc_type, exc_val, exc_tb):
self.file.close()
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ use ruff_python_ast::{self as ast, Expr, Stmt};

use ruff_diagnostics::{Diagnostic, Violation};
use ruff_macros::{derive_message_formats, violation};
use ruff_python_semantic::SemanticModel;
use ruff_python_semantic::{ScopeKind, SemanticModel};
use ruff_text_size::Ranged;

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

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

// pathlib.Path(...).open()
// Ex) `pathlib.Path(...).open()`
let Expr::Attribute(ast::ExprAttribute { attr, value, .. }) = func else {
return false;
};

if attr != "open" {
return false;
}

let Expr::Call(ast::ExprCall {
func: value_func, ..
}) = &**value
else {
return false;
};

semantic
.resolve_qualified_name(value_func)
.is_some_and(|qualified_name| matches!(qualified_name.segments(), ["pathlib", "Path"]))
Expand Down Expand Up @@ -189,6 +192,15 @@ pub(crate) fn open_file_with_context_handler(checker: &mut Checker, func: &Expr)
return;
}

// Ex) `def __enter__(self): ...`
if let ScopeKind::Function(ast::StmtFunctionDef { name, .. }) =
&checker.semantic().current_scope().kind
{
if name == "__enter__" {
return;
}
}

checker
.diagnostics
.push(Diagnostic::new(OpenFileWithContextHandler, func.range()));
Expand Down

0 comments on commit a38c05b

Please sign in to comment.