diff --git a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH208.py b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH208.py index 8a31b2b472..3bdf05ae45 100644 --- a/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH208.py +++ b/crates/ruff_linter/resources/test/fixtures/flake8_use_pathlib/PTH208.py @@ -21,3 +21,6 @@ if os.listdir("dir"): if "file" in os.listdir("dir"): ... + +os.listdir(1) +os.listdir(path=1) 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 58e33f040f..a4bce41939 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,152 +18,150 @@ 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"] => 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 - // 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.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(), - } - .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, - }) - { - let diagnostic = Diagnostic::new::(diagnostic_kind, call.func.range()); + let Some(qualified_name) = checker.semantic().resolve_qualified_name(&call.func) else { + return; + }; - if checker.enabled(diagnostic.kind.rule()) { - checker.report_diagnostic(diagnostic); + let diagnostic_kind: DiagnosticKind = match qualified_name.segments() { + // PTH100 + ["os", "path", "abspath"] => OsPathAbspath.into(), + // PTH101 + ["os", "chmod"] => OsChmod.into(), + // PTH102 + ["os", "makedirs"] => OsMakedirs.into(), + // PTH103 + ["os", "mkdir"] => OsMkdir.into(), + // PTH104 + ["os", "rename"] => OsRename.into(), + // PTH105 + ["os", "replace"] => OsReplace.into(), + // PTH106 + ["os", "rmdir"] => OsRmdir.into(), + // PTH107 + ["os", "remove"] => OsRemove.into(), + // PTH108 + ["os", "unlink"] => OsUnlink.into(), + // PTH109 + ["os", "getcwd"] => OsGetcwd.into(), + ["os", "getcwdb"] => OsGetcwd.into(), + // PTH110 + ["os", "path", "exists"] => OsPathExists.into(), + // PTH111 + ["os", "path", "expanduser"] => OsPathExpanduser.into(), + // PTH112 + ["os", "path", "isdir"] => OsPathIsdir.into(), + // PTH113 + ["os", "path", "isfile"] => OsPathIsfile.into(), + // PTH114 + ["os", "path", "islink"] => OsPathIslink.into(), + // PTH116 + ["os", "stat"] => OsStat.into(), + // PTH117 + ["os", "path", "isabs"] => OsPathIsabs.into(), + // PTH118 + ["os", "path", "join"] => 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"] => 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"] => OsPathBasename.into(), + // PTH120 + ["os", "path", "dirname"] => OsPathDirname.into(), + // PTH121 + ["os", "path", "samefile"] => OsPathSamefile.into(), + // PTH122 + ["os", "path", "splitext"] => OsPathSplitext.into(), + // PTH202 + ["os", "path", "getsize"] => OsPathGetsize.into(), + // PTH203 + ["os", "path", "getatime"] => OsPathGetatime.into(), + // PTH204 + ["os", "path", "getmtime"] => OsPathGetmtime.into(), + // PTH205 + ["os", "path", "getctime"] => 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 + .arguments + .find_positional(0) + .is_some_and(|expr| is_file_descriptor_or_bytes_str(expr, checker.semantic())) + { + return; + } + BuiltinOpen.into() + } + // PTH124 + ["py", "path", "local"] => PyPath.into(), + // PTH207 + ["glob", "glob"] => Glob { + function: "glob".to_string(), + } + .into(), + ["glob", "iglob"] => Glob { + function: "iglob".to_string(), + } + .into(), + // PTH115 + // Python 3.9+ + ["os", "readlink"] if checker.target_version() >= PythonVersion::PY39 => OsReadlink.into(), + // PTH208 + ["os", "listdir"] => { + if call + .arguments + .find_argument_value("path", 0) + .is_some_and(|expr| is_file_descriptor(expr, checker.semantic())) + { + return; + } + OsListdir.into() + } + _ => return, + }; + + if checker.enabled(diagnostic_kind.rule()) { + checker.report_diagnostic(Diagnostic::new(diagnostic_kind, call.func.range())); } }