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 0ed75fb834..cd56dc8077 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 @@ -136,4 +136,38 @@ os.chmod("pth1_file", 0o700, None, True, 1, *[1], **{"x": 1}, foo=1) os.rename("pth1_file", "pth1_file1", None, None, 1, *[1], **{"x": 1}, foo=1) os.replace("pth1_file1", "pth1_file", None, None, 1, *[1], **{"x": 1}, foo=1) -os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1) \ No newline at end of file +os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1) + +# See: https://github.com/astral-sh/ruff/issues/21794 +import sys + +if os.rename("pth1.py", "pth1.py.bak"): + print("rename: truthy") +else: + print("rename: falsey") + +if os.replace("pth1.py.bak", "pth1.py"): + print("replace: truthy") +else: + print("replace: falsey") + +try: + for _ in os.getcwd(): + print("getcwd: iterable") + break +except TypeError as e: + print("getcwd: not iterable") + +try: + for _ in os.getcwdb(): + print("getcwdb: iterable") + break +except TypeError as e: + print("getcwdb: not iterable") + +try: + for _ in os.readlink(sys.executable): + print("readlink: iterable") + break +except TypeError as e: + print("readlink: not iterable") diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/helpers.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/helpers.rs index 24d9daee25..8ab1b62b1e 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/helpers.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/helpers.rs @@ -1,4 +1,4 @@ -use ruff_python_ast::{self as ast, Arguments, Expr, ExprCall}; +use ruff_python_ast::{self as ast, Arguments, Expr, ExprCall, Stmt}; use ruff_python_semantic::{SemanticModel, analyze::typing}; use ruff_text_size::Ranged; @@ -93,7 +93,9 @@ pub(crate) fn check_os_pathlib_single_arg_calls( let applicability = match applicability { Applicability::DisplayOnly => Applicability::DisplayOnly, - _ if checker.comment_ranges().intersects(range) => Applicability::Unsafe, + _ if checker.comment_ranges().intersects(range) || is_statement(checker) => { + Applicability::Unsafe + } _ => applicability, }; @@ -174,7 +176,9 @@ pub(crate) fn check_os_pathlib_two_arg_calls( let applicability = match applicability { Applicability::DisplayOnly => Applicability::DisplayOnly, - _ if checker.comment_ranges().intersects(range) => Applicability::Unsafe, + _ if checker.comment_ranges().intersects(range) || is_statement(checker) => { + Applicability::Unsafe + } _ => applicability, }; @@ -213,3 +217,10 @@ pub(crate) fn is_argument_non_default(arguments: &Arguments, name: &str, positio pub(crate) fn is_top_level_expression_call(checker: &Checker) -> bool { checker.semantic().current_expression_parent().is_none() } + +pub(crate) fn is_statement(checker: &Checker) -> bool { + matches!( + checker.semantic().current_statement(), + Stmt::If(_) | Stmt::For(_) | Stmt::While(_) + ) +} diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_getcwd.rs b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_getcwd.rs index 4174b5825a..8e421adc30 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_getcwd.rs +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/rules/os_getcwd.rs @@ -6,7 +6,7 @@ use ruff_text_size::Ranged; use crate::checkers::ast::Checker; use crate::importer::ImportRequest; use crate::preview::is_fix_os_getcwd_enabled; -use crate::rules::flake8_use_pathlib::helpers::is_top_level_expression_call; +use crate::rules::flake8_use_pathlib::helpers::{is_statement, is_top_level_expression_call}; use crate::{FixAvailability, Violation}; /// ## What it does @@ -90,6 +90,7 @@ pub(crate) fn os_getcwd(checker: &Checker, call: &ExprCall, segments: &[&str]) { // Unsafe when the fix would delete comments or change a used return value let applicability = if checker.comment_ranges().intersects(range) || !is_top_level_expression_call(checker) + || is_statement(checker) { Applicability::Unsafe } else { diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__full_name.py.snap b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__full_name.py.snap index f8bdfa55ad..dfc79473c0 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__full_name.py.snap +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__full_name.py.snap @@ -567,5 +567,64 @@ PTH121 `os.path.samefile()` should be replaced by `Path.samefile()` 138 | 139 | os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1) | ^^^^^^^^^^^^^^^^ +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 | help: Replace with `Path(...).samefile()` + +PTH104 `os.rename()` should be replaced by `Path.rename()` + --> full_name.py:144:4 + | +142 | import sys +143 | +144 | if os.rename("pth1.py", "pth1.py.bak"): + | ^^^^^^^^^ +145 | print("rename: truthy") +146 | else: + | +help: Replace with `Path(...).rename(...)` + +PTH105 `os.replace()` should be replaced by `Path.replace()` + --> full_name.py:149:4 + | +147 | print("rename: falsey") +148 | +149 | if os.replace("pth1.py.bak", "pth1.py"): + | ^^^^^^^^^^ +150 | print("replace: truthy") +151 | else: + | +help: Replace with `Path(...).replace(...)` + +PTH109 `os.getcwd()` should be replaced by `Path.cwd()` + --> full_name.py:155:14 + | +154 | try: +155 | for _ in os.getcwd(): + | ^^^^^^^^^ +156 | print("getcwd: iterable") +157 | break + | +help: Replace with `Path.cwd()` + +PTH109 `os.getcwd()` should be replaced by `Path.cwd()` + --> full_name.py:162:14 + | +161 | try: +162 | for _ in os.getcwdb(): + | ^^^^^^^^^^ +163 | print("getcwdb: iterable") +164 | break + | +help: Replace with `Path.cwd()` + +PTH115 `os.readlink()` should be replaced by `Path.readlink()` + --> full_name.py:169:14 + | +168 | try: +169 | for _ in os.readlink(sys.executable): + | ^^^^^^^^^^^ +170 | print("readlink: iterable") +171 | break + | +help: Replace with `Path(...).readlink()` diff --git a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__preview_full_name.py.snap b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__preview_full_name.py.snap index 7292a3b5d2..c8ee502fe7 100644 --- a/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__preview_full_name.py.snap +++ b/crates/ruff_linter/src/rules/flake8_use_pathlib/snapshots/ruff_linter__rules__flake8_use_pathlib__tests__preview_full_name.py.snap @@ -1037,5 +1037,142 @@ PTH121 `os.path.samefile()` should be replaced by `Path.samefile()` 138 | 139 | os.path.samefile("pth1_file", "pth1_link", 1, *[1], **{"x": 1}, foo=1) | ^^^^^^^^^^^^^^^^ +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 | help: Replace with `Path(...).samefile()` + +PTH104 [*] `os.rename()` should be replaced by `Path.rename()` + --> full_name.py:144:4 + | +142 | import sys +143 | +144 | if os.rename("pth1.py", "pth1.py.bak"): + | ^^^^^^^^^ +145 | print("rename: truthy") +146 | else: + | +help: Replace with `Path(...).rename(...)` +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 +142 | import sys +143 + import pathlib +144 | + - if os.rename("pth1.py", "pth1.py.bak"): +145 + if pathlib.Path("pth1.py").rename("pth1.py.bak"): +146 | print("rename: truthy") +147 | else: +148 | print("rename: falsey") +note: This is an unsafe fix and may change runtime behavior + +PTH105 [*] `os.replace()` should be replaced by `Path.replace()` + --> full_name.py:149:4 + | +147 | print("rename: falsey") +148 | +149 | if os.replace("pth1.py.bak", "pth1.py"): + | ^^^^^^^^^^ +150 | print("replace: truthy") +151 | else: + | +help: Replace with `Path(...).replace(...)` +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 +142 | import sys +143 + import pathlib +144 | +145 | if os.rename("pth1.py", "pth1.py.bak"): +146 | print("rename: truthy") +147 | else: +148 | print("rename: falsey") +149 | + - if os.replace("pth1.py.bak", "pth1.py"): +150 + if pathlib.Path("pth1.py.bak").replace("pth1.py"): +151 | print("replace: truthy") +152 | else: +153 | print("replace: falsey") +note: This is an unsafe fix and may change runtime behavior + +PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()` + --> full_name.py:155:14 + | +154 | try: +155 | for _ in os.getcwd(): + | ^^^^^^^^^ +156 | print("getcwd: iterable") +157 | break + | +help: Replace with `Path.cwd()` +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 +142 | import sys +143 + import pathlib +144 | +145 | if os.rename("pth1.py", "pth1.py.bak"): +146 | print("rename: truthy") +-------------------------------------------------------------------------------- +153 | print("replace: falsey") +154 | +155 | try: + - for _ in os.getcwd(): +156 + for _ in pathlib.Path.cwd(): +157 | print("getcwd: iterable") +158 | break +159 | except TypeError as e: +note: This is an unsafe fix and may change runtime behavior + +PTH109 [*] `os.getcwd()` should be replaced by `Path.cwd()` + --> full_name.py:162:14 + | +161 | try: +162 | for _ in os.getcwdb(): + | ^^^^^^^^^^ +163 | print("getcwdb: iterable") +164 | break + | +help: Replace with `Path.cwd()` +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 +142 | import sys +143 + import pathlib +144 | +145 | if os.rename("pth1.py", "pth1.py.bak"): +146 | print("rename: truthy") +-------------------------------------------------------------------------------- +160 | print("getcwd: not iterable") +161 | +162 | try: + - for _ in os.getcwdb(): +163 + for _ in pathlib.Path.cwd(): +164 | print("getcwdb: iterable") +165 | break +166 | except TypeError as e: +note: This is an unsafe fix and may change runtime behavior + +PTH115 [*] `os.readlink()` should be replaced by `Path.readlink()` + --> full_name.py:169:14 + | +168 | try: +169 | for _ in os.readlink(sys.executable): + | ^^^^^^^^^^^ +170 | print("readlink: iterable") +171 | break + | +help: Replace with `Path(...).readlink()` +140 | +141 | # See: https://github.com/astral-sh/ruff/issues/21794 +142 | import sys +143 + import pathlib +144 | +145 | if os.rename("pth1.py", "pth1.py.bak"): +146 | print("rename: truthy") +-------------------------------------------------------------------------------- +167 | print("getcwdb: not iterable") +168 | +169 | try: + - for _ in os.readlink(sys.executable): +170 + for _ in pathlib.Path(sys.executable).readlink(): +171 | print("readlink: iterable") +172 | break +173 | except TypeError as e: +note: This is an unsafe fix and may change runtime behavior