From 58793a954f78b451b70bde561bc4ee450e02c18d Mon Sep 17 00:00:00 2001 From: LaBatata101 Date: Tue, 29 Apr 2025 11:15:22 -0300 Subject: [PATCH 1/4] [`flake8-use-pathlib`] Fix `PTH116` false positive when `stat` is passed a file descriptor or bytes string --- .../fixtures/flake8_use_pathlib/full_name.py | 14 +++++++++++ .../rules/replaceable_by_pathlib.rs | 25 ++++++++++++++++--- .../src/analyze/typing.rs | 12 +++++++++ 3 files changed, 47 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py index 20197b9703863..c669973c08196 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -55,3 +55,17 @@ def opener(path, flags): open(x) def foo(y: int): open(y) + +# https://github.com/astral-sh/ruff/issues/17693 +os.stat(b"/foo") +os.stat(1) +os.stat(x) + + +def func() -> int: + return 2 +os.stat(func()) + + +def bar(x: int): + os.stat(x) diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 7d44916c89007..84d605b8bdd74 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -54,7 +54,16 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { // PTH114 ["os", "path", "islink"] => Some(OsPathIslink.into()), // PTH116 - ["os", "stat"] => Some(OsStat.into()), + ["os", "stat"] => { + if call + .arguments + .find_positional(0) + .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) + { + return None; + } + Some(OsStat.into()) + } // PTH117 ["os", "path", "isabs"] => Some(OsPathIsabs.into()), // PTH118 @@ -175,12 +184,12 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool { Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(_), .. - }) + }) | Expr::BytesLiteral(_) ) { return true; } - let Some(name) = expr.as_name_expr() else { + let Some(name) = get_name_expr(expr) else { return false; }; @@ -188,5 +197,13 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool { return false; }; - typing::is_int(binding, semantic) + typing::is_int(binding, semantic) || typing::is_bytes(binding, semantic) +} + +fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> { + match expr { + Expr::Name(name) => Some(name), + Expr::Call(ast::ExprCall { func, .. }) => get_name_expr(func), + _ => None, + } } diff --git a/crates/ruff_python_semantic/src/analyze/typing.rs b/crates/ruff_python_semantic/src/analyze/typing.rs index 9b1f1c43f9a58..c588da272aff8 100644 --- a/crates/ruff_python_semantic/src/analyze/typing.rs +++ b/crates/ruff_python_semantic/src/analyze/typing.rs @@ -639,6 +639,18 @@ pub fn check_type(binding: &Binding, semantic: &SemanticModel) - _ => false, }, + BindingKind::FunctionDefinition(_) => match binding.statement(semantic) { + // ```python + // def foo() -> int: + // ... + // ``` + Some(Stmt::FunctionDef(ast::StmtFunctionDef { returns, .. })) => returns + .as_ref() + .is_some_and(|return_ann| T::match_annotation(return_ann, semantic)), + + _ => false, + }, + _ => false, } } From da14282e77ef8602d95aa5c6a28cf78945790c67 Mon Sep 17 00:00:00 2001 From: LaBatata101 Date: Tue, 29 Apr 2025 16:43:28 -0300 Subject: [PATCH 2/4] update code --- .../rules/replaceable_by_pathlib.rs | 316 ++++++++++-------- 1 file changed, 168 insertions(+), 148 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 84d605b8bdd74..4f8237d05fec9 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -18,156 +18,155 @@ use crate::rules::flake8_use_pathlib::violations::{ use ruff_python_ast::PythonVersion; pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { - if let Some(diagnostic_kind) = checker - .semantic() - .resolve_qualified_name(&call.func) - .and_then(|qualified_name| match qualified_name.segments() { - // PTH100 - ["os", "path", "abspath"] => Some(OsPathAbspath.into()), - // PTH101 - ["os", "chmod"] => Some(OsChmod.into()), - // PTH102 - ["os", "makedirs"] => Some(OsMakedirs.into()), - // PTH103 - ["os", "mkdir"] => Some(OsMkdir.into()), - // PTH104 - ["os", "rename"] => Some(OsRename.into()), - // PTH105 - ["os", "replace"] => Some(OsReplace.into()), - // PTH106 - ["os", "rmdir"] => Some(OsRmdir.into()), - // PTH107 - ["os", "remove"] => Some(OsRemove.into()), - // PTH108 - ["os", "unlink"] => Some(OsUnlink.into()), - // PTH109 - ["os", "getcwd"] => Some(OsGetcwd.into()), - ["os", "getcwdb"] => Some(OsGetcwd.into()), - // PTH110 - ["os", "path", "exists"] => Some(OsPathExists.into()), - // PTH111 - ["os", "path", "expanduser"] => Some(OsPathExpanduser.into()), - // PTH112 - ["os", "path", "isdir"] => Some(OsPathIsdir.into()), - // PTH113 - ["os", "path", "isfile"] => Some(OsPathIsfile.into()), - // PTH114 - ["os", "path", "islink"] => Some(OsPathIslink.into()), - // PTH116 - ["os", "stat"] => { - if call - .arguments - .find_positional(0) - .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) - { - return None; + if let Some(diagnostic_kind) = + checker + .semantic() + .resolve_qualified_name(&call.func) + .and_then(|qualified_name| match qualified_name.segments() { + // PTH100 + ["os", "path", "abspath"] => Some(OsPathAbspath.into()), + // PTH101 + ["os", "chmod"] => Some(OsChmod.into()), + // PTH102 + ["os", "makedirs"] => Some(OsMakedirs.into()), + // PTH103 + ["os", "mkdir"] => Some(OsMkdir.into()), + // PTH104 + ["os", "rename"] => Some(OsRename.into()), + // PTH105 + ["os", "replace"] => Some(OsReplace.into()), + // PTH106 + ["os", "rmdir"] => Some(OsRmdir.into()), + // PTH107 + ["os", "remove"] => Some(OsRemove.into()), + // PTH108 + ["os", "unlink"] => Some(OsUnlink.into()), + // PTH109 + ["os", "getcwd"] => Some(OsGetcwd.into()), + ["os", "getcwdb"] => Some(OsGetcwd.into()), + // PTH110 + ["os", "path", "exists"] => Some(OsPathExists.into()), + // PTH111 + ["os", "path", "expanduser"] => Some(OsPathExpanduser.into()), + // PTH112 + ["os", "path", "isdir"] => Some(OsPathIsdir.into()), + // PTH113 + ["os", "path", "isfile"] => Some(OsPathIsfile.into()), + // PTH114 + ["os", "path", "islink"] => Some(OsPathIslink.into()), + // PTH116 + ["os", "stat"] => { + if call.arguments.find_positional(0).is_some_and(|expr| { + is_file_descriptor_or_bytes_str(expr, checker.semantic()) + }) { + return None; + } + Some(OsStat.into()) } - Some(OsStat.into()) - } - // PTH117 - ["os", "path", "isabs"] => Some(OsPathIsabs.into()), - // PTH118 - ["os", "path", "join"] => Some( - OsPathJoin { - module: "path".to_string(), - joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { - Joiner::Joinpath - } else { - Joiner::Slash - }, - } - .into(), - ), - ["os", "sep", "join"] => Some( - OsPathJoin { - module: "sep".to_string(), - joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { - Joiner::Joinpath - } else { - Joiner::Slash - }, - } - .into(), - ), - // PTH119 - ["os", "path", "basename"] => Some(OsPathBasename.into()), - // PTH120 - ["os", "path", "dirname"] => Some(OsPathDirname.into()), - // PTH121 - ["os", "path", "samefile"] => Some(OsPathSamefile.into()), - // PTH122 - ["os", "path", "splitext"] => Some(OsPathSplitext.into()), - // PTH202 - ["os", "path", "getsize"] => Some(OsPathGetsize.into()), - // PTH203 - ["os", "path", "getatime"] => Some(OsPathGetatime.into()), - // PTH204 - ["os", "path", "getmtime"] => Some(OsPathGetmtime.into()), - // PTH205 - ["os", "path", "getctime"] => Some(OsPathGetctime.into()), - // PTH123 - ["" | "builtins", "open"] => { - // `closefd` and `opener` are not supported by pathlib, so check if they are - // are set to non-default values. - // https://github.com/astral-sh/ruff/issues/7620 - // Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open): - // ```text - // 0 1 2 3 4 5 - // open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, - // 6 7 - // closefd=True, opener=None) - // ^^^^ ^^^^ - // ``` - // For `pathlib` (https://docs.python.org/3/library/pathlib.html#pathlib.Path.open): - // ```text - // Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) - // ``` - if call - .arguments - .find_argument_value("closefd", 6) - .is_some_and(|expr| { - !matches!( - expr, - Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. }) - ) - }) - || call - .arguments - .find_argument_value("opener", 7) - .is_some_and(|expr| !expr.is_none_literal_expr()) - || call + // PTH117 + ["os", "path", "isabs"] => Some(OsPathIsabs.into()), + // PTH118 + ["os", "path", "join"] => Some( + OsPathJoin { + module: "path".to_string(), + joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { + Joiner::Joinpath + } else { + Joiner::Slash + }, + } + .into(), + ), + ["os", "sep", "join"] => Some( + OsPathJoin { + module: "sep".to_string(), + joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { + Joiner::Joinpath + } else { + Joiner::Slash + }, + } + .into(), + ), + // PTH119 + ["os", "path", "basename"] => Some(OsPathBasename.into()), + // PTH120 + ["os", "path", "dirname"] => Some(OsPathDirname.into()), + // PTH121 + ["os", "path", "samefile"] => Some(OsPathSamefile.into()), + // PTH122 + ["os", "path", "splitext"] => Some(OsPathSplitext.into()), + // PTH202 + ["os", "path", "getsize"] => Some(OsPathGetsize.into()), + // PTH203 + ["os", "path", "getatime"] => Some(OsPathGetatime.into()), + // PTH204 + ["os", "path", "getmtime"] => Some(OsPathGetmtime.into()), + // PTH205 + ["os", "path", "getctime"] => Some(OsPathGetctime.into()), + // PTH123 + ["" | "builtins", "open"] => { + // `closefd` and `opener` are not supported by pathlib, so check if they are + // are set to non-default values. + // https://github.com/astral-sh/ruff/issues/7620 + // Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open): + // ```text + // 0 1 2 3 4 5 + // open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, + // 6 7 + // closefd=True, opener=None) + // ^^^^ ^^^^ + // ``` + // For `pathlib` (https://docs.python.org/3/library/pathlib.html#pathlib.Path.open): + // ```text + // Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) + // ``` + if call .arguments - .find_positional(0) - .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) - { - return None; + .find_argument_value("closefd", 6) + .is_some_and(|expr| { + !matches!( + expr, + Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. }) + ) + }) + || call + .arguments + .find_argument_value("opener", 7) + .is_some_and(|expr| !expr.is_none_literal_expr()) + || call + .arguments + .find_positional(0) + .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) + { + return None; + } + Some(BuiltinOpen.into()) } - Some(BuiltinOpen.into()) - } - // PTH124 - ["py", "path", "local"] => Some(PyPath.into()), - // PTH207 - ["glob", "glob"] => Some( - Glob { - function: "glob".to_string(), + // PTH124 + ["py", "path", "local"] => Some(PyPath.into()), + // PTH207 + ["glob", "glob"] => Some( + Glob { + function: "glob".to_string(), + } + .into(), + ), + ["glob", "iglob"] => Some( + Glob { + function: "iglob".to_string(), + } + .into(), + ), + // PTH115 + // Python 3.9+ + ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => { + Some(OsReadlink.into()) } - .into(), - ), - ["glob", "iglob"] => Some( - Glob { - function: "iglob".to_string(), - } - .into(), - ), - // PTH115 - // Python 3.9+ - ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => { - Some(OsReadlink.into()) - } - // PTH208, - ["os", "listdir"] => Some(OsListdir.into()), - _ => None, - }) + // PTH208, + ["os", "listdir"] => Some(OsListdir.into()), + _ => None, + }) { let diagnostic = Diagnostic::new::(diagnostic_kind, call.func.range()); @@ -177,6 +176,10 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { } } +fn is_file_descriptor_or_bytes_str(expr: &Expr, semantic: &SemanticModel) -> bool { + is_file_descriptor(expr, semantic) || is_bytes_string(expr, semantic) +} + /// Returns `true` if the given expression looks like a file descriptor, i.e., if it is an integer. fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool { if matches!( @@ -184,7 +187,7 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool { Expr::NumberLiteral(ast::ExprNumberLiteral { value: ast::Number::Int(_), .. - }) | Expr::BytesLiteral(_) + }) ) { return true; } @@ -197,7 +200,24 @@ fn is_file_descriptor(expr: &Expr, semantic: &SemanticModel) -> bool { return false; }; - typing::is_int(binding, semantic) || typing::is_bytes(binding, semantic) + typing::is_int(binding, semantic) +} + +/// Returns `true` if the given expression is a bytes string. +fn is_bytes_string(expr: &Expr, semantic: &SemanticModel) -> bool { + if matches!(expr, Expr::BytesLiteral(_)) { + return true; + } + + let Some(name) = get_name_expr(expr) else { + return false; + }; + + let Some(binding) = semantic.only_binding(name).map(|id| semantic.binding(id)) else { + return false; + }; + + typing::is_bytes(binding, semantic) } fn get_name_expr(expr: &Expr) -> Option<&ast::ExprName> { From c0234297f1ede6db8d28fc7be4cb89972804af6f Mon Sep 17 00:00:00 2001 From: LaBatata101 Date: Tue, 29 Apr 2025 18:01:07 -0300 Subject: [PATCH 3/4] Fix formatting --- .../flake8_use_pathlib/rules/replaceable_by_pathlib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 5990ab1df798d..2d597db799f16 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -134,10 +134,9 @@ pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { .arguments .find_argument_value("opener", 7) .is_some_and(|expr| !expr.is_none_literal_expr()) - || call - .arguments - .find_positional(0) - .is_some_and(|expr| is_file_descriptor_or_bytes_str(expr, checker.semantic())) + || call.arguments.find_positional(0).is_some_and(|expr| { + is_file_descriptor_or_bytes_str(expr, checker.semantic()) + }) { return None; } From 7e50948d87ec04f1b85fd6c3d043db6089dd0889 Mon Sep 17 00:00:00 2001 From: LaBatata101 Date: Wed, 30 Apr 2025 11:26:42 -0300 Subject: [PATCH 4/4] Remove code for handling bytes string --- .../fixtures/flake8_use_pathlib/full_name.py | 1 - .../rules/replaceable_by_pathlib.rs | 289 +++++++++--------- 2 files changed, 145 insertions(+), 145 deletions(-) diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py index 79b262ffb0580..bd00f5193e3e8 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/full_name.py @@ -71,7 +71,6 @@ def bytes_str_func() -> bytes: open(bytes_str_func()) # https://github.com/astral-sh/ruff/issues/17693 -os.stat(b"/foo") os.stat(1) os.stat(x) diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs index 2d597db799f16..284c203fff8ab 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/replaceable_by_pathlib.rs @@ -18,154 +18,155 @@ use crate::rules::flake8_use_pathlib::violations::{ use ruff_python_ast::PythonVersion; pub(crate) fn replaceable_by_pathlib(checker: &Checker, call: &ExprCall) { - if let Some(diagnostic_kind) = - checker - .semantic() - .resolve_qualified_name(&call.func) - .and_then(|qualified_name| match qualified_name.segments() { - // PTH100 - ["os", "path", "abspath"] => Some(OsPathAbspath.into()), - // PTH101 - ["os", "chmod"] => Some(OsChmod.into()), - // PTH102 - ["os", "makedirs"] => Some(OsMakedirs.into()), - // PTH103 - ["os", "mkdir"] => Some(OsMkdir.into()), - // PTH104 - ["os", "rename"] => Some(OsRename.into()), - // PTH105 - ["os", "replace"] => Some(OsReplace.into()), - // PTH106 - ["os", "rmdir"] => Some(OsRmdir.into()), - // PTH107 - ["os", "remove"] => Some(OsRemove.into()), - // PTH108 - ["os", "unlink"] => Some(OsUnlink.into()), - // PTH109 - ["os", "getcwd"] => Some(OsGetcwd.into()), - ["os", "getcwdb"] => Some(OsGetcwd.into()), - // PTH110 - ["os", "path", "exists"] => Some(OsPathExists.into()), - // PTH111 - ["os", "path", "expanduser"] => Some(OsPathExpanduser.into()), - // PTH112 - ["os", "path", "isdir"] => Some(OsPathIsdir.into()), - // PTH113 - ["os", "path", "isfile"] => Some(OsPathIsfile.into()), - // PTH114 - ["os", "path", "islink"] => Some(OsPathIslink.into()), - // PTH116 - ["os", "stat"] => { - if call.arguments.find_positional(0).is_some_and(|expr| { - is_file_descriptor_or_bytes_str(expr, checker.semantic()) - }) { - return None; - } - Some(OsStat.into()) + if let Some(diagnostic_kind) = checker + .semantic() + .resolve_qualified_name(&call.func) + .and_then(|qualified_name| match qualified_name.segments() { + // PTH100 + ["os", "path", "abspath"] => Some(OsPathAbspath.into()), + // PTH101 + ["os", "chmod"] => Some(OsChmod.into()), + // PTH102 + ["os", "makedirs"] => Some(OsMakedirs.into()), + // PTH103 + ["os", "mkdir"] => Some(OsMkdir.into()), + // PTH104 + ["os", "rename"] => Some(OsRename.into()), + // PTH105 + ["os", "replace"] => Some(OsReplace.into()), + // PTH106 + ["os", "rmdir"] => Some(OsRmdir.into()), + // PTH107 + ["os", "remove"] => Some(OsRemove.into()), + // PTH108 + ["os", "unlink"] => Some(OsUnlink.into()), + // PTH109 + ["os", "getcwd"] => Some(OsGetcwd.into()), + ["os", "getcwdb"] => Some(OsGetcwd.into()), + // PTH110 + ["os", "path", "exists"] => Some(OsPathExists.into()), + // PTH111 + ["os", "path", "expanduser"] => Some(OsPathExpanduser.into()), + // PTH112 + ["os", "path", "isdir"] => Some(OsPathIsdir.into()), + // PTH113 + ["os", "path", "isfile"] => Some(OsPathIsfile.into()), + // PTH114 + ["os", "path", "islink"] => Some(OsPathIslink.into()), + // PTH116 + ["os", "stat"] => { + if call + .arguments + .find_positional(0) + .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) + { + return None; + } + Some(OsStat.into()) + } + // PTH117 + ["os", "path", "isabs"] => Some(OsPathIsabs.into()), + // PTH118 + ["os", "path", "join"] => Some( + OsPathJoin { + module: "path".to_string(), + joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { + Joiner::Joinpath + } else { + Joiner::Slash + }, + } + .into(), + ), + ["os", "sep", "join"] => Some( + OsPathJoin { + module: "sep".to_string(), + joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { + Joiner::Joinpath + } else { + Joiner::Slash + }, } - // PTH117 - ["os", "path", "isabs"] => Some(OsPathIsabs.into()), - // PTH118 - ["os", "path", "join"] => Some( - OsPathJoin { - module: "path".to_string(), - joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { - Joiner::Joinpath - } else { - Joiner::Slash - }, - } - .into(), - ), - ["os", "sep", "join"] => Some( - OsPathJoin { - module: "sep".to_string(), - joiner: if call.arguments.args.iter().any(Expr::is_starred_expr) { - Joiner::Joinpath - } else { - Joiner::Slash - }, - } - .into(), - ), - // PTH119 - ["os", "path", "basename"] => Some(OsPathBasename.into()), - // PTH120 - ["os", "path", "dirname"] => Some(OsPathDirname.into()), - // PTH121 - ["os", "path", "samefile"] => Some(OsPathSamefile.into()), - // PTH122 - ["os", "path", "splitext"] => Some(OsPathSplitext.into()), - // PTH202 - ["os", "path", "getsize"] => Some(OsPathGetsize.into()), - // PTH203 - ["os", "path", "getatime"] => Some(OsPathGetatime.into()), - // PTH204 - ["os", "path", "getmtime"] => Some(OsPathGetmtime.into()), - // PTH205 - ["os", "path", "getctime"] => Some(OsPathGetctime.into()), - // PTH123 - ["" | "builtins", "open"] => { - // `closefd` and `opener` are not supported by pathlib, so check if they are - // set to non-default values. - // https://github.com/astral-sh/ruff/issues/7620 - // Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open): - // ```text - // 0 1 2 3 4 5 - // open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, - // 6 7 - // closefd=True, opener=None) - // ^^^^ ^^^^ - // ``` - // For `pathlib` (https://docs.python.org/3/library/pathlib.html#pathlib.Path.open): - // ```text - // Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) - // ``` - if call + .into(), + ), + // PTH119 + ["os", "path", "basename"] => Some(OsPathBasename.into()), + // PTH120 + ["os", "path", "dirname"] => Some(OsPathDirname.into()), + // PTH121 + ["os", "path", "samefile"] => Some(OsPathSamefile.into()), + // PTH122 + ["os", "path", "splitext"] => Some(OsPathSplitext.into()), + // PTH202 + ["os", "path", "getsize"] => Some(OsPathGetsize.into()), + // PTH203 + ["os", "path", "getatime"] => Some(OsPathGetatime.into()), + // PTH204 + ["os", "path", "getmtime"] => Some(OsPathGetmtime.into()), + // PTH205 + ["os", "path", "getctime"] => Some(OsPathGetctime.into()), + // PTH123 + ["" | "builtins", "open"] => { + // `closefd` and `opener` are not supported by pathlib, so check if they are + // set to non-default values. + // https://github.com/astral-sh/ruff/issues/7620 + // Signature as of Python 3.11 (https://docs.python.org/3/library/functions.html#open): + // ```text + // 0 1 2 3 4 5 + // open(file, mode='r', buffering=-1, encoding=None, errors=None, newline=None, + // 6 7 + // closefd=True, opener=None) + // ^^^^ ^^^^ + // ``` + // For `pathlib` (https://docs.python.org/3/library/pathlib.html#pathlib.Path.open): + // ```text + // Path.open(mode='r', buffering=-1, encoding=None, errors=None, newline=None) + // ``` + if call + .arguments + .find_argument_value("closefd", 6) + .is_some_and(|expr| { + !matches!( + expr, + Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. }) + ) + }) + || call .arguments - .find_argument_value("closefd", 6) - .is_some_and(|expr| { - !matches!( - expr, - Expr::BooleanLiteral(ExprBooleanLiteral { value: true, .. }) - ) - }) - || call - .arguments - .find_argument_value("opener", 7) - .is_some_and(|expr| !expr.is_none_literal_expr()) - || call.arguments.find_positional(0).is_some_and(|expr| { - is_file_descriptor_or_bytes_str(expr, checker.semantic()) - }) - { - return None; - } - Some(BuiltinOpen.into()) + .find_argument_value("opener", 7) + .is_some_and(|expr| !expr.is_none_literal_expr()) + || call.arguments.find_positional(0).is_some_and(|expr| { + is_file_descriptor_or_bytes_str(expr, checker.semantic()) + }) + { + return None; + } + Some(BuiltinOpen.into()) + } + // PTH124 + ["py", "path", "local"] => Some(PyPath.into()), + // PTH207 + ["glob", "glob"] => Some( + Glob { + function: "glob".to_string(), } - // PTH124 - ["py", "path", "local"] => Some(PyPath.into()), - // PTH207 - ["glob", "glob"] => Some( - Glob { - function: "glob".to_string(), - } - .into(), - ), - ["glob", "iglob"] => Some( - Glob { - function: "iglob".to_string(), - } - .into(), - ), - // PTH115 - // Python 3.9+ - ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => { - Some(OsReadlink.into()) + .into(), + ), + ["glob", "iglob"] => Some( + Glob { + function: "iglob".to_string(), } - // PTH208, - ["os", "listdir"] => Some(OsListdir.into()), - _ => None, - }) + .into(), + ), + // PTH115 + // Python 3.9+ + ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => { + Some(OsReadlink.into()) + } + // PTH208, + ["os", "listdir"] => Some(OsListdir.into()), + _ => None, + }) { let diagnostic = Diagnostic::new::(diagnostic_kind, call.func.range());